001/*
002 * Copyright (C) Photon Vision.
003 *
004 * This program is free software: you can redistribute it and/or modify
005 * it under the terms of the GNU General Public License as published by
006 * the Free Software Foundation, either version 3 of the License, or
007 * (at your option) any later version.
008 *
009 * This program is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
012 * GNU General Public License for more details.
013 *
014 * You should have received a copy of the GNU General Public License
015 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
016 */
017
018package org.photonvision.common.scripting;
019
020import java.io.IOException;
021import java.nio.file.Files;
022import java.nio.file.Path;
023import java.nio.file.Paths;
024import java.util.ArrayList;
025import java.util.List;
026import java.util.concurrent.LinkedBlockingDeque;
027import org.photonvision.common.hardware.Platform;
028import org.photonvision.common.logging.LogGroup;
029import org.photonvision.common.logging.Logger;
030import org.photonvision.common.util.TimedTaskManager;
031import org.photonvision.common.util.file.JacksonUtils;
032
033public class ScriptManager {
034    private static final Logger logger = new Logger(ScriptManager.class, LogGroup.General);
035
036    private ScriptManager() {}
037
038    private static final List<ScriptEvent> events = new ArrayList<>();
039    private static final LinkedBlockingDeque<ScriptEventType> queuedEvents =
040            new LinkedBlockingDeque<>(25);
041
042    public static void initialize() {
043        ScriptConfigManager.initialize();
044        if (ScriptConfigManager.fileExists()) {
045            for (ScriptConfig scriptConfig : ScriptConfigManager.loadConfig()) {
046                ScriptEvent scriptEvent = new ScriptEvent(scriptConfig);
047                events.add(scriptEvent);
048            }
049
050            TimedTaskManager.getInstance().addTask("ScriptRunner", new ScriptRunner(), 10);
051
052        } else {
053            logger.error("Something went wrong initializing scripts! Events will not run.");
054        }
055    }
056
057    private static class ScriptRunner implements Runnable {
058        @Override
059        public void run() {
060            try {
061                handleEvent(queuedEvents.takeFirst());
062            } catch (InterruptedException e) {
063                logger.error("ScriptRunner queue interrupted!", e);
064            }
065        }
066
067        private void handleEvent(ScriptEventType eventType) {
068            var toRun =
069                    events.parallelStream()
070                            .filter(e -> e.config.eventType == eventType)
071                            .findFirst()
072                            .orElse(null);
073            if (toRun != null) {
074                try {
075                    toRun.run();
076                } catch (IOException e) {
077                    logger.error("Failed to run script for event \"" + eventType.name() + "\"", e);
078                }
079            }
080        }
081    }
082
083    protected static class ScriptConfigManager {
084        //        protected static final Path scriptConfigPath =
085        // Paths.get(ConfigManager.SettingsPath.toString(), "scripts.json");
086        static final Path scriptConfigPath = Paths.get(""); // TODO: Waiting on config
087
088        private ScriptConfigManager() {}
089
090        static boolean fileExists() {
091            return Files.exists(scriptConfigPath);
092        }
093
094        public static void initialize() {
095            if (!fileExists()) {
096                List<ScriptConfig> eventsConfig = new ArrayList<>();
097                for (var eventType : ScriptEventType.values()) {
098                    eventsConfig.add(new ScriptConfig(eventType));
099                }
100
101                try {
102                    JacksonUtils.serialize(scriptConfigPath, eventsConfig.toArray(new ScriptConfig[0]), true);
103                } catch (IOException e) {
104                    logger.error("Failed to initialize!", e);
105                }
106            }
107        }
108
109        static List<ScriptConfig> loadConfig() {
110            try {
111                var raw = JacksonUtils.deserialize(scriptConfigPath, ScriptConfig[].class);
112                if (raw != null) {
113                    return List.of(raw);
114                }
115            } catch (IOException e) {
116                logger.error("Failed to load scripting config!", e);
117            }
118            return new ArrayList<>();
119        }
120
121        protected static void deleteConfig() {
122            try {
123                Files.delete(scriptConfigPath);
124            } catch (IOException e) {
125                //
126            }
127        }
128    }
129
130    public static void queueEvent(ScriptEventType eventType) {
131        if (Platform.isLinux()) {
132            try {
133                queuedEvents.putLast(eventType);
134                logger.info("Queued event: " + eventType.name());
135            } catch (InterruptedException e) {
136                logger.error("Failed to add event to queue: " + eventType.name(), e);
137            }
138        }
139    }
140}