public abstract class RobotBase {
	/**
	 * The VxWorks priority that robot code should work at (so Java code should run at)
	 */
	public static final int ROBOT_TASK_PRIORITY = 101;

	public static final long MAIN_THREAD_ID = Thread.currentThread().getId();
	protected final DriverStation m_ds;

	/**
	 * Constructor for a generic robot program.
	 * User code should be placed in the constructor that runs before the Autonomous or Operator
	 * Control period starts. The constructor will run to completion before Autonomous is entered.
	 *
	 * This must be used to ensure that the communications code starts. In the future it would be
	 * nice to put this code into it's own task that loads on boot so ensure that it runs.
	 */
	protected RobotBase() {
		// TODO: StartCAPI();
		// TODO: See if the next line is necessary
		// Resource.RestartProgram();

		NetworkTable.setNetworkIdentity("Robot");
		NetworkTable.setPersistentFilename(Storage.highestPriority("networktables.ini").getAbsolutePath());
		NetworkTable.setServerMode();// must be before b
		m_ds = DriverStation.getInstance();
		NetworkTable.getTable(""); // forces network tables to initialize
		NetworkTable.getTable("LiveWindow").getSubTable("~STATUS~").putBoolean("LW Enabled", false);
	}

	/**
	 * Free the resources for a RobotBase class.
	 */
	public void free() {
	}

	/**
	 * @return If the robot is running in simulation.
	 */
	public static boolean isSimulation() {
		return false;
	}

	/**
	 * @return If the robot is running in the real world.
	 */
	public static boolean isReal() {
		return true;
	}

	/**
	 * Determine if the Robot is currently disabled.
	 * @return True if the Robot is currently disabled by the field controls.
	 */
	public boolean isDisabled() {
		return m_ds.isDisabled();
	}

	/**
	 * Determine if the Robot is currently enabled.
	 * @return True if the Robot is currently enabled by the field controls.
	 */
	public boolean isEnabled() {
		return m_ds.isEnabled();
	}

	/**
	 * Determine if the robot is currently in Autonomous mode.
	 * @return True if the robot is currently operating Autonomously as determined by the field controls.
	 */
	public boolean isAutonomous() {
		return m_ds.isAutonomous();
	}

	/**
	 * Determine if the robot is currently in Test mode
	 * @return True if the robot is currently operating in Test mode as determined by the driver station.
	 */
	public boolean isTest() {
		return m_ds.isTest();
	}

	/**
	 * Determine if the robot is currently in Operator Control mode.
	 * @return True if the robot is currently operating in Tele-Op mode as determined by the field controls.
	 */
	public boolean isOperatorControl() {
		return m_ds.isOperatorControl();
	}

	/**
	 * Indicates if new data is available from the driver station.
	 * @return Has new data arrived over the network since the last time this function was called?
	 */
	public boolean isNewDataAvailable() {
		return m_ds.isNewControlData();
	}

	/**
	 * Provide an alternate "main loop" via startCompetition().
	 */
	public abstract void startCompetition();

	@SuppressWarnings("JavadocMethod")
	public static boolean getBooleanProperty(String name, boolean defaultValue) {
		String propVal = System.getProperty(name);
		if (propVal == null) {
			return defaultValue;
		}
		if (propVal.equalsIgnoreCase("false")) {
			return false;
		} else if (propVal.equalsIgnoreCase("true")) {
			return true;
		} else {
			throw new IllegalStateException(propVal);
		}
	}

	/**
	 * Common initialization for all robot programs.
	 */
	public static void initializeHardwareConfiguration() {
		int rv = HAL.initialize(0);
		assert rv == 1;

		// Set some implementations so that the static methods work properly
		Timer.SetImplementation(new HardwareTimer());
		HLUsageReporting.SetImplementation(new HardwareHLUsageReporting());
		RobotState.SetImplementation(DriverStation.getInstance());

		// Load opencv
		try {
			System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
		} catch (UnsatisfiedLinkError ex) {
			System.out.println("OpenCV Native Libraries could not be loaded.");
			System.out.println("Please try redeploying, or reimage your roboRIO and try again.");
			ex.printStackTrace();
		}
	}

	/**
	 * Starting point for the applications.
	 */
	public static void main(String args[]) {
		initializeHardwareConfiguration();

		HAL.report(FRCNetComm.tResourceType.kResourceType_Language, FRCNetComm.tInstances.kLanguage_Java);
		RobotBase robot = new Toast();

		try {
			final File file = new File("/tmp/frc_versions/FRC_Lib_Version.ini");

			if (file.exists()) {
				file.delete();
			}

			file.createNewFile();

			try (FileOutputStream output = new FileOutputStream(file)) {
				output.write("Java ".getBytes());
				output.write(WPILibVersion.Version.getBytes());
			}

		} catch (IOException ex) {
			ex.printStackTrace();
		}

		boolean errorOnExit = false;
		try {
			robot.startCompetition();
		} catch (Throwable t) {
			DriverStation.reportError("ERROR Unhandled exception: " + t.toString() + " at " + Arrays.toString(t.getStackTrace()), false);
			errorOnExit = true;
		} finally {
			System.err.println("WARNING: Robots don't quit!");
			if (errorOnExit) {
				System.err.println("---> The startCompetition() method (or methods called by it) should have handled the exception above.");
			} else {
				System.err.println("---> Unexpected return from startCompetition() method.");
			}
		}
		System.exit(1);
	}
} @SuppressWarnings({"AbbreviationAsWordInName", "MethodName"})
public class HAL extends JNIWrapper {
    public static void waitForDSData() {
      try {
        Thread.sleep(20);
      } catch (InterruptedException e) { }
    }

    public static int initialize(int mode) { return 0; }

    public static void observeUserProgramStarting() { }

    public static void observeUserProgramDisabled() { }

    public static void observeUserProgramAutonomous() { }

    public static void observeUserProgramTeleop() { }

    public static void observeUserProgramTest() { }

    public static void report(int resource, int instanceNumber) {
        report(resource, instanceNumber, 0, "");
    }

    public static void report(int resource, int instanceNumber, int context) {
        report(resource, instanceNumber, context, "");
    }

    /**
     * Report the usage of a resource of interest. <br>
     *
     * <p>Original signature: <code>uint32_t report(tResourceType, uint8_t, uint8_t, const
     * char*)</code>
     *
     * @param resource       one of the values in the tResourceType above (max value 51). <br>
     * @param instanceNumber an index that identifies the resource instance. <br>
     * @param context        an optional additional context number for some cases (such as module
     *                       number). Set to 0 to omit. <br>
     * @param feature        a string to be included describing features in use on a specific
     *                       resource. Setting the same resource more than once allows you to change
     *                       the feature string.
     */
    public static int report(int resource, int instanceNumber, int context, String feature) {
        return 0;
    }

    private static int nativeGetControlWord() {
        return 0;   // There is a TODO here, base this off robot state
    }

    @SuppressWarnings("JavadocMethod")
    public static void getControlWord(ControlWord controlWord) {
        RobotState s = SimulationData.currentState;
        controlWord.update(s != RobotState.DISABLED, s == RobotState.AUTONOMOUS, s == RobotState.TEST,
                false, false, true);
    }

    private static int nativeGetAllianceStation() {
        return SimulationData.alliance_station;
    }

    @SuppressWarnings("JavadocMethod")
    public static AllianceStationID getAllianceStation() {
        switch (nativeGetAllianceStation()) {
            case 0:
                return AllianceStationID.Red1;
            case 1:
                return AllianceStationID.Red2;
            case 2:
                return AllianceStationID.Red3;
            case 3:
                return AllianceStationID.Blue1;
            case 4:
                return AllianceStationID.Blue2;
            case 5:
                return AllianceStationID.Blue3;
            default:
                return null;
        }
    }

    public static int kMaxJoystickAxes = 12;
    public static int kMaxJoystickPOVs = 12;

    public static short getJoystickAxes(byte joystickNum, float[] axesArray) {
        if (DriverStationCommunications.connected) {
            short count = DriverStationCommunications.joyaxiscount[joystickNum];
            for (int i = 0; i < count; i++)
                axesArray[i] = (float)DriverStationCommunications.joyaxis[joystickNum][i] / 127.0f;
            return count;
        }
        SimulatedJoystick simJoy = SimulationData.getJoystick(joystickNum);
        for (int i = 0; i < simJoy.getAxis().length; i++)
            axesArray[i] = (float)simJoy.getAxis(i);
        return (short)simJoy.getAxis().length;
    }

    public static short getJoystickPOVs(byte joystickNum, short[] povsArray) {
        if (DriverStationCommunications.connected) {
            short count = DriverStationCommunications.joypovcount[joystickNum];
            for (int i = 0; i < count; i++)
                povsArray[i] = DriverStationCommunications.joypov[joystickNum][i];
            return count;
        }
        SimulatedJoystick simJoy = SimulationData.getJoystick(joystickNum);
        for (int i = 0; i < simJoy.getPOVs().length; i++)
            povsArray[i] = (short)simJoy.getPOV(i);
        return (short)simJoy.getPOVs().length;
    }

    public static int getJoystickButtons(byte joystickNum, ByteBuffer count) {
        if (DriverStationCommunications.connected) {
            count.put(0, DriverStationCommunications.joybuttoncount[joystickNum]);
            return DriverStationCommunications.joybuttons[joystickNum];
        }
        SimulatedJoystick joystick = SimulationData.getJoystick(joystickNum);
        count.put(0, (byte) joystick.getButtons().length);
        return joystick.encodeButtons();
    }

    public static int setJoystickOutputs(byte joystickNum, int outputs, short leftRumble, short rightRumble) {
        return 0;
    }

    public static int getJoystickIsXbox(byte joystickNum) {
        return 0;
    }

    public static int getJoystickType(byte joystickNum) {
        return 0;
    }

    public static String getJoystickName(byte joystickNum) {
        return "Generic";
    }

    public static int getJoystickAxisType(byte joystickNum, byte axis) {
        return 0;
    }

    public static double getMatchTime() {
        return 120;
    }

    public static boolean getSystemActive() {
        return true;
    }

    public static boolean getBrownedOut() {
        return false;
    }

    public static int setErrorData(String error) {
        return 0;
    }

    public static int sendError(boolean isError, int errorCode, boolean isLVCode,
                                       String details, String location, String callStack,
                                       boolean printMsg) {
        return 0;
    }
} public class HALUtil extends JNIWrapper {
	public static final int NULL_PARAMETER = -1005;
	public static final int SAMPLE_RATE_TOO_HIGH = 1001;
	public static final int VOLTAGE_OUT_OF_RANGE = 1002;
	public static final int LOOP_TIMING_ERROR = 1004;
	public static final int INCOMPATIBLE_STATE = 1015;
	public static final int ANALOG_TRIGGER_PULSE_OUTPUT_ERROR = -1011;
	public static final int NO_AVAILABLE_RESOURCES = -104;
	public static final int PARAMETER_OUT_OF_RANGE = -1028;

	public static short getFPGAVersion() {
		return 2009;
	}

	public static int getFPGARevision() {
		return 0;
	}

	public static long getFPGATime() {
		return (System.nanoTime() - ToastBootstrap.startTimeNS) / 1000;
	}

	public static int getHALRuntimeType() {
		return 0;
	}

	public static boolean getFPGAButton() {
		return SimulationData.userButtonPressed;
	}

	public static String getHALErrorMessage(int code) {
		return "";
	}

	public static int getHALErrno() {
		return 0;
	}

	public static String getHALstrerror(int errno) {
		return "";
	}

	public static String getHALstrerror() {
		return getHALstrerror(getHALErrno());
	}
} public class USBMassStorage {

    private static String[] knownSymLinks = new String[] {"/U", "/V", "/W", "/X"};

    public static List<MassStorageDevice> connectedDevices;
    public static List<File> invalidDrives;

    static Logger logger;

    static boolean override = false;

    public static MassStorageDevice config_highest;
    static int config_last = 0;
    public static MassStorageDevice filesystem_highest;
    static int filesystem_last = 0;

    /**
     * Initializes the Logger, File System and required values. This is called in the ToastBootstrap, do
     * not touch this method yourself.
     */
    public static void init() {
        connectedDevices = new Vector<>();
        invalidDrives = new Vector<>();
        if (ToastBootstrap.isSimulation)
            knownSymLinks = new String[] {"usb_U", "usb_V", "usb_W", "usb_X"};
        logger = new Logger("Toast|Mass-Storage", Logger.ATTR_DEFAULT);
        for (String s : knownSymLinks) {
            crawlSym(new File(s));
        }

        if (config_highest != null) {
            logger.info("Using USB Device: " + config_highest.drive_name + " for Configuration Files (highest priority)");
        }

        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
            @Override
            public void run() {
                String date = new SimpleDateFormat("yyyy-MM-dd_hh-mm-ss").format(new Date());
                String filename = "toast_log_" + date + ".txt";
                duplicate(SysLogProxy.recentOut, filename);
                duplicate(SysLogProxy.recentOut, "latest.txt");
                duplicate(SysLogProxy.recentErr, "latestError.txt");
            }
        }));
    }

    /**
     * Duplicates the given Log File across all USB Mass Storage devices
     */
    public static void duplicate(File f, String fn) {
        for (MassStorageDevice device : USBMassStorage.connectedDevices) {
            if (!device.accept_logs) continue;
            File crash = new File(device.toast_directory, "log");
            crash.mkdirs();

            try {
                Files.copy(f.toPath(), new File(crash, fn).toPath());
            } catch (IOException e) { }
        }
    }

    /**
     * Relativize a File path across USB Mass Storage devices.
     */
    public static String relativize(File file) {
        Path them = file.getAbsoluteFile().toPath();
        if (them.startsWith(ToastBootstrap.toastHome.getAbsoluteFile().toPath())) {
            return relativize_0(them, ToastBootstrap.toastHome.getAbsoluteFile().toPath());
        } else {
            for (MassStorageDevice dev : connectedDevices) {
                if (them.startsWith(dev.toast_directory.getAbsoluteFile().toPath()))
                    return relativize_0(them, dev.toast_directory.getAbsoluteFile().toPath());
            }
        }
        return "";
    }

    /**
     * Relativize 2 paths to each other
     */
    private static String relativize_0(Path dest, Path home) {
        return home.relativize(dest).toString();
    }

    /**
     * Crawls the given directory for the toast_autorun.conf file. If the file is found, the USB storage device
     * is configured using it. If it is not found, the user is warned and the device marked as 'Invalid'. Running
     * 'usb generate' will validate any invalid drives by generating this file. A restart will be required after runnning
     * the generate command.
     */
    public static void crawlSym(File file) {
        if (!file.exists())
            return;
        try {
            File canon = file.getCanonicalFile();
            if (!canon.exists()) return;

            File configuration = new File(canon, "toast_autorun.conf");
            if (!configuration.exists()) {
                invalidDrives.add(canon);
                logger.warn("Invalid USB Mass Storage Device Detected -- No toast_autorun.conf file: " + canon);
                logger.warn("Run 'usb generate' to create a new toast_autorun.conf file on any invalid drives");
                return;
            }

            ProfilerEntity profilerEntity = new ProfilerEntity().start();
            ModuleConfig pref = new ModuleConfig(configuration);
            String drive_name = pref.getString("toast.device_name", "Team_####_USB_Device").replace(" ", "_");
            MassStorageDevice device = new MassStorageDevice(canon, pref, drive_name);
            connectedDevices.add(device);
            if (device.override_modules && !device.concurrent_modules) override = true;

            logger.info("USB Mass Storage Device Detected -- Valid! " + canon + " (" + device.drive_name + ")");

            if (device.config_priority >= config_last) {
                config_highest = device;
                config_last = device.config_priority;
            }
            if (device.filesystem_priority >= filesystem_last) {
                filesystem_highest = device;
                filesystem_last = device.filesystem_priority;
            }
            profilerEntity.stop();
            profilerEntity.setName(drive_name);
            Profiler.INSTANCE.section("USB").pushEntity(profilerEntity);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Returns true if a file path is inside of a USB Mass Storage device.
     */
    public static boolean isUSB(String filepath) {
        for (String s : knownSymLinks) {
            if (filepath.startsWith(new File(s).getAbsolutePath())) return true;
        }
        return false;
    }

    /**
     * Returns true if ANY drives are marked to override modules or files on the Toast Local System. Only 1 drive is required
     * to do the overriding procedure, so make sure to check this method if using any File System components where a USB
     * Drive is capable of accessing.
     */
    public static boolean overridingModules() {
        return override;
    }

} public class ClassPatcher extends URLClassLoader {

    public ClassPatcher() {
        this(ClassLoader.getSystemClassLoader());
    }

    public ClassPatcher(ClassLoader parent) {
        super(new URL[0], parent);
    }

    /**
     * Patch a class for the given name. This is called whenever Class.forName is called, or Class.newInstance. This is
     * responsible for loading and defining patches if they exist, otherwise loading the standard file or throwing
     * an exception if it does not exist.
     */
    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        String simulation = "assets/toast/patches/" + name.replace(".", "/") + ".sim";
        String patch = "assets/toast/patches/" + name.replace(".", "/") + ".pat";
        InputStream stream = null;
        if (this.getResource(simulation) != null)
            stream = this.getResourceAsStream(simulation);
        if (this.getResource(patch) != null)
            stream = this.getResourceAsStream(patch);

        if (stream != null) {
            try {
                byte[] buf = new byte[50000];
                int len = stream.read(buf);

                Method m = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
                m.setAccessible(true);
                m.invoke(getParent(), name, buf, 0, len);
            } catch (Exception e) {
                ToastBootstrap.toastLogger.error("Could not load Patch: " + name);
                ToastBootstrap.toastLogger.exception(e);
            }
        }

        return super.loadClass(name);
    }

    /**
     * Identify the patches from assets/toast/patches/patches.txt
     */
    public void identifyPatches(boolean sim) {
        try {
            ProfilerSection section = Profiler.INSTANCE.section("Patcher");
            section.start("Identify");
            ArrayList<String> patches = new ArrayList<>();
            InputStream is = getResourceAsStream("assets/toast/patches/patches.txt");
            BufferedReader reader = new BufferedReader(new InputStreamReader(is));

            String line = null;
            while ((line = reader.readLine()) != null) {
                patches.add(line);
            }

            reader.close();
            section.stop("Identify");

            for (String s : patches) {
                try {
                    if (s.endsWith(".sim") && sim) {
                        section.section("Simulation").start(s);
                        loadClass(s.replace(".sim", "").replace("/", "."));
                        section.section("Simulation").stop(s);
                    } else if (s.endsWith(".pat")) {
                        section.section("Global").start(s);
                        loadClass(s.replace(".pat", "").replace("/", "."));
                        section.section("Global").stop(s);
                    }
                } catch (Exception e) {
                    ToastBootstrap.toastLogger.error("Could not load Simulation Patch: " + s);
                    ToastBootstrap.toastLogger.exception(e);
                }
            }
        } catch (Exception e) {
            ToastBootstrap.toastLogger.error("Could not load patches. Ignoring...");
        }
    }
} public class RobotLoader {

    /* EXTERNALS */
    public static boolean search = false;
    public static ArrayList<String> manualLoadedClasses = new ArrayList<>();
    public static ArrayList<String> coreClasses = new ArrayList<>();
    public static ArrayList<Object> coreObjects = new ArrayList<>();
    public static LinkedList<Method> queuedPrestart = new LinkedList<>();


    /* INTERNALS */
    private static Logger log;
    private static URLClassLoader sysLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
    private static boolean isCorePhase = false;
    private static MethodExecutor exec;
    public static Pattern classFile = Pattern.compile("([^\\s$]+).class$");

    /**
     * Also known as the Core pre-init. All initialization is done here.
     */
    public static void preinit(ProfilerSection profiler) {
        log = new Logger("Toast|Loader", Logger.ATTR_DEFAULT);
        if (search)
            loadDevEnvironment(profiler);

        isCorePhase = true;
        loadCandidates(profiler.section("CoreJava"));
        parseCoreEntries(profiler.section("CoreJava"));
    }

    /**
     * Sets up non-core modules for loading
     */
    public static void init(ProfilerSection section) {
        isCorePhase = false;
        ProfilerSection section1 = section.section("Java");
        loadCandidates(section1);
        parseEntries(section1);

        constructModules(section1);
        resolveBranches(section1);
    }

    /* Loaders */

    /**
     * Load a module candidate from a directory. This is usually used in -sim --search.
     */
    static void loadDirectory(File file, ModuleCandidate candidate) throws IOException {
        File[] files = file.listFiles();
        if (files != null)
            for (File f : files)
                loadSubDirectory(file, f, candidate);
    }

    /**
     * Load a subdirectory into a module candidate. This is recursive in order to search for classfiles
     * and extract their path
     */
    static void loadSubDirectory(File main, File dig, ModuleCandidate candidate) {
        if (dig.isDirectory()) {
            File[] files = dig.listFiles();
            if (files != null)
                for (File f : files)
                    loadSubDirectory(main, f, candidate);
        } else if (classFile.matcher(dig.getName()).matches()) {
            Path pathCurrent = Paths.get(dig.getAbsolutePath());
            Path pathMain = Paths.get(main.getAbsolutePath());
            Path relative = pathMain.relativize(pathCurrent);
            candidate.addClassEntry(relative.toString());
        }
    }

    /**
     * Load a Jar File as a ModuleCandidate. This will automatically parse Manifest attributes
     * and other related functions for module discovery.
     * @param file                  The file of the module.jar
     * @param expandClasspath       Should we expand the classpath to this jarfile if it's a valid module?
     * @throws IOException
     */
    static void loadJar(File file, boolean expandClasspath) throws IOException {
        JarFile jar = new JarFile(file);
        ModuleCandidate container = new ModuleCandidate();

        boolean core = false;
        Manifest mf = jar.getManifest();
        if (mf != null) {
            Attributes attr = mf.getMainAttributes();
            if (attr != null) {
                if (attr.getValue("Toast-Core-Plugin-Class") != null && isCorePhase) {
                    String clazz = (String) attr.getValue("Toast-Core-Plugin-Class");
                    container.setCorePlugin(true, clazz);
                    coreClasses.add(clazz);
                    core = true;
                    log.info("Injected Core Plugin: " + file.getName());
                }
                if (attr.getValue("Toast-Plugin-Class") != null) {
                    log.debug("Bypass Module Detected: " + file.getName());
                    String bypassClass = (String) attr.getValue("Toast-Plugin-Class");
                    container.setBypass(true, bypassClass);
                }
                if (attr.getValue("Robot-Class") != null) {
                    log.debug("Wrapping Module Detected: " + file.getName());
                    String wrapperClazz = attr.getValue("Robot-Class");
                    container.setWrapper(true, wrapperClazz);
                }
            }
        }

        if (core && isCorePhase || !core && !isCorePhase) {
            container.setFile(file);
            for (ZipEntry ze : Collections.list(jar.entries())) {
                if (classFile.matcher(ze.getName()).matches()) {
                    container.addClassEntry(ze.getName());
                }
            }

            log.debug("Preliminary Candidate Added: " + file.getName());
            getCandidates().add(container);
            if (expandClasspath)
                addURL(file.toURI().toURL());
        }
    }

    /* Searchers */

    /**
     * Detect any modules that are present in a development environment (-sim --search)
     */
    static void loadDevEnvironment(ProfilerSection section) {
        section.start("DevEnv");
        for (URL url : sysLoader.getURLs()) {
            try {
                File f = new File(url.toURI());
                if (f.isDirectory()) {
                    ModuleCandidate candidate = new ModuleCandidate();
                    loadDirectory(f, candidate);
                    getCandidates().add(candidate);
                } else if (EnvJars.isLoadable(f)) {
                    loadJar(f, false);
                }
            } catch (Exception e) {
            }
        }
        section.stop("DevEnv");
    }

    /**
     * Search a directory for .jar files to load as modules. Jars will be automatically loaded, however,
     * non .jar files will still be injected into the classpath.
     */
    public static void search(File dir) {
        File[] files = dir.listFiles(new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                return name.endsWith(".jar");
            }
        });

        if (files != null)
            for (File file : files) {
                try {
                    log.debug("Discovering Module Jar: " + file.getName());
                    loadJar(file, true);
                } catch (Exception e) {
                    log.debug("Could not discover module for candidacy: " + file.getName());
                    log.debugException(e);
                }
            }

        File[] otherFiles = dir.listFiles(new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                return !name.endsWith(".jar");
            }
        });

        if (otherFiles != null)
            for (File file : otherFiles) {
                try {
                    log.warn("Non-Jar Module in modules/! This should be in libs/! " + file.getName());
                    addURL(file.toURI().toURL());
                } catch (Exception e) {
                    log.debug("Could not expand classpath for Library: " + file.getName());
                    log.debugException(e);
                }
            }
    }

    /**
     * Similar method to {@link #search(File)}, but will not search for Toast files, but will instead
     * just add it to the load path. This is for libraries that don't load Toast, like Apache Commons,
     * Language Libraries or others.
     */
    public static void search_libs(File dir) {
        File[] files = dir.listFiles(new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                return name.endsWith(".jar");
            }
        });

        if (files != null)
            for (File file : files) {
                try {
                    log.debug("Expanding Classpath for Library: " + file.getName());
                    addURL(file.toURI().toURL());
                } catch (Exception e) {
                    log.debug("Could not expand classpath for Library: " + file.getName());
                    log.debugException(e);
                }
            }

        File[] otherFiles = dir.listFiles(new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                return !name.endsWith(".jar");
            }
        });

        if (otherFiles != null)
            for (File file : otherFiles) {
                try {
                    log.debug("Expanding Classpath for Non-Jar Library: " + file.getName());
                    addURL(file.toURI().toURL());
                } catch (Exception e) {
                    log.debug("Could not expand classpath for Non-Jar Library: " + file.getName());
                    log.debugException(e);
                }
            }
    }

    /* Candidation */

    /**
     * Start discovery for Module Candidates.
     */
    private static void loadCandidates(ProfilerSection section) {
        section.start("Candidate");
        File[] search_dirs = new File[] { new File(ToastBootstrap.toastHome, "modules/") };
        File[] lib_dirs = new File[0];
        if (!isCorePhase) {
            search_dirs = Storage.USB_Module("modules");
            lib_dirs = Storage.USB_Module("libs");
        }
        for (File dir : lib_dirs) {
            dir.mkdirs();
            search_libs(dir);
        }
        for (File dir : search_dirs) {
            dir.mkdirs();
            search(dir);
        }
        section.stop("Candidate");
    }

    /**
     * Parse the core module candidates and attempt to load them into preinit
     */
    private static void parseCoreEntries(ProfilerSection section) {
        section.start("Parse");
        for (String clazz : coreClasses) {
            try {
                log.debug("Parsing Core Entry: " + clazz);
                Class c = Class.forName(clazz);
                Object object = c.newInstance();
                coreObjects.add(object);
                c.getDeclaredMethod("preinit").invoke(object);
            } catch (Throwable e) {
                log.debug("Could not parse core entry: " + clazz);
                log.debugException(e);
            }
        }
        section.stop("Parse");
    }

    /**
     * Returns false if the NoLoad annotation is present, which stops 'api' classes being loaded.
     */
    static boolean classLoadable(Class clazz) {
        return !clazz.isAnnotationPresent(NoLoad.class);
    }

    /**
     * Attempt to load this class as a ToastModule into the container
     */
    static void parseClass(String clazz, ModuleCandidate candidate) {
        try {
            Class c = Class.forName(clazz);
            if (ToastModule.class.isAssignableFrom(c) && classLoadable(c)) {
                log.debug("Toast Module Class Found: " + clazz + " for candidate: " + candidate.getModuleFile());
                ModuleContainer container = new ModuleContainer(c, candidate);
                getContainers().add(container);
            }
        } catch (Throwable e) {
            log.debug("Could not parse module class: " + clazz);
            log.debugException(e);
        }
    }

    /**
     * Attempt to load this class as a ModuleWrapper into a container
     */
    static void parseWrapper(String clazz, ModuleCandidate candidate) {
        try {
            Class c = Class.forName(clazz);
            if (RobotBase.class.isAssignableFrom(c) && classLoadable(c) && !Toast.class.isAssignableFrom(c)) {
                log.debug("WPILib Class Found: " + clazz + " for candidate: " + candidate.getModuleFile() + "... wrapping...");
                ModuleWrapper wrapper = new ModuleWrapper(candidate.getModuleFile(), c, candidate);
                getContainers().add(new ModuleContainer(wrapper, candidate));
            }
        } catch (Throwable e) {
            log.debug("Could not parse wrapper class: " + clazz);
            log.debugException(e);
        }
    }

    /**
     * Parse non-core module candidates into their respective containers.
     */
    private static void parseEntries(ProfilerSection section) {
        section.start("Parse");
        for (ModuleCandidate candidate : getCandidates()) {
            if (candidate.isBypass()) {
                parseClass(candidate.getBypassClass(), candidate);
            } else if (candidate.isWrapper()) {
                parseWrapper(candidate.getWrapperClass(), candidate);
            } else
                for (String clazz : candidate.getClassEntries()) {
                    parseClass(clazz, candidate);
                }
            candidate.freeMemory();
        }

        for (String clazz : manualLoadedClasses) {
            log.debug("Manually Loading Class: " + clazz);
            ModuleCandidate candidate = new ModuleCandidate();
            candidate.addClassEntry(clazz);
            parseClass(clazz, candidate);
        }
        section.stop("Parse");
    }

    /* Construction */

    /**
     * Construct all the candidate modules. This will start parsing the names and versions of all the
     * module container main classes, thus setting them up ready for prestart and start.
     */
    private static void constructModules(ProfilerSection section) {
        for (ModuleContainer container : getContainers()) {
            try {
                log.debug("Constructing Module Container: " + container.getCandidate().getModuleFile());
                ProfilerEntity entity = new ProfilerEntity().start();
                container.construct();
                entity.stop();
                entity.setName("Construct");
                section.section("Module").section(container.getName()).pushEntity(entity);
                log.info("Module Loaded: " + container.getDetails());
            } catch (Exception e) {
                log.debug("Could not Construct Module: " + container.getCandidate().getModuleFile());
                log.debugException(e);
            }
        }
    }

    /**
     * Resolve any dependency branches on the ToastModule class. This will load classes if modules or
     * class definitions are present in the Toast environment, acting as a soft dependency
     * {@link jaci.openrio.toast.core.loader.annotation.Branch}
     */
    private static void resolveBranches(ProfilerSection section) {
        ProfilerSection section1 = section.section("Dependency");
        for (ModuleContainer container : getContainers()) {
            log.debug("Resolving Dependencies for Module: " + container.getName());
            section1.start(container.getName());
            container.resolve_branches();
            section1.stop(container.getName());
        }
    }

    /* Callers */

    /**
     * Initialize core modules by calling their init() method.
     */
    public static void initCore(ProfilerSection section) {
        ProfilerSection section1 = section.section("CoreJava");
        section1.start("Init");
        for (Object core : coreObjects) {
            try {
                core.getClass().getDeclaredMethod("init").invoke(core);
            } catch (Throwable e) {
            }
        }
        section1.stop("Init");
    }

    /**
     * Post initialize core modules by calling their postinit() method.
     */
    public static void postCore(ProfilerSection section) {
        ProfilerSection section1 = section.section("CoreJava");
        section1.start("Post");
        for (Object core : coreObjects) {
            try {
                core.getClass().getDeclaredMethod("postinit").invoke(core);
            } catch (Throwable e) {
            }
        }
        section1.stop("Post");
    }

    /**
     * Prestart modules in order of their {@link jaci.openrio.toast.core.loader.annotation.Priority} annotations
     * (assuming the annotation is present, else treat as normal). Queued prestart methods are also invoked (dependencies)
     */
    public static void prestart(ProfilerSection section) throws InvocationTargetException {
        dispatch("prestart", section);
        for (Method method : queuedPrestart) {
            try {
                method.invoke(null);
            } catch (Exception e) {
                Toast.log().error("Error invoking queued method: " + method.getName());
                Toast.log().exception(e);
            }
        }
        queuedPrestart.clear();
    }

    /**
     * Start modules in order of their {@link jaci.openrio.toast.core.loader.annotation.Priority} annotations
     * (assuming the annotation is present, else treat as normal).
     */
    public static void start(ProfilerSection section) throws InvocationTargetException {
        dispatch("start", section);
    }

    /* Util */

    /**
     * Dispatch a method to all containers
     */
    public static void dispatch(String method, ProfilerSection section) throws InvocationTargetException {
        if (exec == null) {
            ToastModule[] mods = new ToastModule[getContainers().size()];
            for (int i = 0; i < mods.length; i++) {
                mods[i] = getContainers().get(i).getModule();
            }
            exec = new MethodExecutor(mods);
        }
        exec.profile(section);
        exec.call(method);
    }

    /**
     * Expand the classpath to include the provided URL
     */
    public static void addURL(URL u) throws IOException {
        Class sysclass = URLClassLoader.class;
        try {
            Method method = sysclass.getDeclaredMethod("addURL", URL.class);
            method.setAccessible(true);
            method.invoke(sysLoader, u);
        } catch (Throwable t) { }
    }

    /**
     * Returns true if the provided string is a valid class name and a definition is present in the classpath.
     */
    public static boolean classExists(String clazz) {
        try {
            Class.forName(clazz);
            return true;
        } catch (ClassNotFoundException e) {
            return false;
        }
    }

} public class DriverStationCommunications {

    public static ThreadGroup group = new ThreadGroup("driver_station");

    public static void init() {
        if (ToastConfiguration.Property.SIM_DS_ENABLED.asBoolean()) {
            Thread runThread = new Thread(group, DriverStationCommunications::run);
            runThread.start();
        }
    }

    public static void broadcast() {
        String service = "roborio-" + ToastConfiguration.Property.SIM_BROADCAST_TEAM + "-frc";
        String hostname = "roborio-" + ToastConfiguration.Property.SIM_BROADCAST_TEAM + "-frc";
        String ip_str = "127.0.0.1";

        char ip[] = new char[4];
        String arr[] = ip_str.split("\\.");
        for (int i = 0; i < 4; i++) {
            ip[i] = (char) (int) Integer.parseInt(arr[i]);
        }

        char payload_1[] = {
                0x00, 0x00, 0x84, 0x00,        // ID, Response Query
                0x00, 0x00, 0x00, 0x03,        // No Question, 3 Answers
                0x00, 0x00, 0x00, 0x01,        // No Authority, 1 Additional RR
        };

        char payload_2[] = {
                0x03,                            // Len: 3
                0x5f, 0x6e, 0x69,                // _ni
                0x04,                            // Len: 4
                0x5f, 0x74, 0x63, 0x70,            // _tcp
                0x05,                            // Len: 5
                0x6c, 0x6f, 0x63, 0x61, 0x6c,    // local
                0x00,                            // end of string

                0x00, 0x0c, 0x80, 0x01,            // Type: PTR (domain name PoinTeR), Class: IN, Cache flush: true
                0x00, 0x00, 0x00, 0x3C,            // TTL: 60 Sec
                0x00, (char) (0x03 + service.length()),
                (char) service.length()
        };

        char payload_3[] = service.toCharArray();

        char payload_4[] = {
                0xc0, 0x0c,                    // Name Offset (0xc0, 0x0c => 12 =>._ni._tcp.local)

                // Record 2: SRV
                0xc0, 0x26, 0x00, 0x21,        // Name Offset (mdns.service_name), Type: SRV (Server Selection)
                0x80, 0x01,                    // Class: IN, Cache flush: true
                0x00, 0x00, 0x00, 0x3C,        // TTL: 60 sec
                0x00, (char) (0xE + hostname.length()),    // Data Length: 14 + thnl
                0x00, 0x00, 0x00, 0x00,        // Priority: 0, Weight: 0
                0x0d, 0xfc,                    // Port: 3580
                (char) hostname.length()                    // Len: thnl
        };

        char payload_5[] = hostname.toCharArray();

        char payload_6[] = {
                0x05,                            // Len: 5
                0x6c, 0x6f, 0x63, 0x61, 0x6c,    // local
                0x00,                            // end of string

                // Record 3: TXT
                0xc0, 0x26, 0x00, 0x10,        // Name Offset (mdns.service_name), Type: TXT
                0x80, 0x01,                    // Class: IN, Cache flush: true
                0x00, 0x00, 0x00, 0x3C,        // TTL: 60 sec
                0x00, 0x01, 0x00,            // Data Length: 1, TXT Length: 0

                // Additional Record: A
                0xc0, (char) (0x3b + service.length()),    // Name Offset (mdns.target_host_name)
                0x00, 0x01, 0x80, 0x01,        // Type: A, Class: IN, Cache flush: true
                0x00, 0x00, 0x00, 0x3C,        // TTL: 60 sec
                0x00, 0x04,                    // Data Length: 4
                ip[0], ip[1], ip[2], ip[3]    // IP Bytes
        };

        char payload[] = new char[payload_1.length + payload_2.length + payload_3.length + payload_4.length + payload_5.length + payload_6.length];
        System.arraycopy(payload_1, 0, payload, 0, payload_1.length);
        int l = payload_1.length;
        System.arraycopy(payload_2, 0, payload, l, payload_2.length);
        l += payload_2.length;
        System.arraycopy(payload_3, 0, payload, l, payload_3.length);
        l += payload_3.length;
        System.arraycopy(payload_4, 0, payload, l, payload_4.length);
        l += payload_4.length;
        System.arraycopy(payload_5, 0, payload, l, payload_5.length);
        l += payload_5.length;
        System.arraycopy(payload_6, 0, payload, l, payload_6.length);

        try {
            InetAddress group = InetAddress.getByName("224.0.0.251");
            MulticastSocket multicast_socket = new MulticastSocket(5353);
            multicast_socket.joinGroup(group);

            byte[] byte_payload = new byte[payload.length];
            for(int i = 0; i < payload.length; i++) {
                byte_payload[i] = (byte) payload[i];
            }

            Toast.log().info("Driver Station Communications -> Broadcast Running!");
            while (true) {
                DatagramPacket packet = new DatagramPacket(byte_payload, byte_payload.length, group, 5353);
                multicast_socket.send(packet);
                Thread.sleep(5000);
            }
        } catch (Exception e) { }
    }

    public static void run() {
        if (ToastConfiguration.Property.SIM_BROADCAST_MDNS.asBoolean()) {
            Thread broadcastThread = new Thread(group, DriverStationCommunications::broadcast);
            broadcastThread.run();
        }
        try {
            DatagramSocket socket = new DatagramSocket(1110);
            byte[] buffer = new byte[8192];
            DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
            while (true) {
                socket.receive(packet);
                decodePacket(packet, buffer);
                byte[] toSend = encodePacket();
                DatagramPacket sendPacket = new DatagramPacket(toSend, toSend.length, packet.getAddress(), 1150);
                socket.send(sendPacket);
            }
        } catch (Exception e) {
            Toast.log().error("Could not start Toast DriverStation Networking Service: " + e);
            Toast.log().exception(e);
        }
    }

    static byte pi = 0;
    static byte ng = 0;
    static byte control = 0;
    public static boolean connected = false;

    public static short[][] joyaxis = new short[6][12];
    public static short[] joyaxiscount = new short[6];
    public static int[] joybuttons = new int[6];
    public static byte[] joybuttoncount = new byte[6];
    public static short[][] joypov = new short[6][1];
    public static short[] joypovcount = new short[6];

    public static void decodePacket(DatagramPacket packet, byte[] buffer) {
        connected = true;
        pi = buffer[0]; ng = buffer[1];
        if (buffer[2] != 0) {
            // General Packet
            byte ctrl = buffer[3];
            control = ctrl;
            SimulationData.currentState = ctrl == 0 ? RobotState.DISABLED :
                    ctrl == 4 ? RobotState.TELEOP :
                    ctrl == 6 ? RobotState.AUTONOMOUS :
                    ctrl == 5 ? RobotState.TEST :
                    RobotState.DISABLED;
            SimulationData.repaintState();

            SimulationData.alliance_station = buffer[5];

            int i = 6;
            boolean search = true;
            int joyid = 0;

            // Joysticks
            while (i < buffer.length && search) {
                int structure_size = buffer[i];
                search = buffer[i + 1] == 0x0c;
                if (!search) continue;

                int axis_count = buffer[i + 2];
                joyaxiscount[joyid] = (byte) axis_count;
                joyaxis[joyid] = new short[axis_count];
                for (int ax = 0; ax < axis_count; ax++) {
                    int ax_val = buffer[i + 2 + ax + 1];
                    joyaxis[joyid][ax] = (short) ax_val;
                }
                int b = i + 2 + axis_count + 1;

                int button_count = buffer[b];
                joybuttoncount[joyid] = (byte) button_count;

                int button_delta = (button_count / 8 + ((button_count%8 == 0) ? 0 : 1));
                int total_mask = 0;
                for (int bm = 0; bm < button_delta; bm++) {
                    byte button_mask = buffer[b + bm + 1];
                    total_mask = (total_mask << (bm * 8)) | button_mask;
                }
                joybuttons[joyid] = total_mask;

                b = b + button_delta + 1;

                int pov_count = buffer[b];
                joypovcount[joyid] = (byte) pov_count;
                joypov[joyid] = new short[pov_count];
                for (int pv = 0; pv < pov_count; pv++) {
                    int a1 = buffer[b + 1 + (pv*2)];
                    int a2 = buffer[b + 1 + (pv*2) + 1];
                    if (a2 < 0) a2 = 256 + a2;
                    short result = (short) (a1 << 8 | a2);
                    joypov[joyid][pv] = result;
                }

                joyid++;
                i += structure_size + 1;
            }
        } else {
            // Connection Packet
        }
    }

    public static byte[] encodePacket() {
        byte[] buffer = new byte[8];
        buffer[0] = pi; buffer[1] = ng;
        buffer[2] = 0x01;
        buffer[3] = control;
        buffer[4] = 0x10 | 0x20;

        double volts = SimulationData.pdpVoltage;
        // Splits Bat Voltage into two bytes
        buffer[5] = (byte)volts;
        buffer[6] = (byte)((volts * 100 - ((byte)volts) * 100) * 2.5);
        buffer[7] = 0;
        return buffer;
    }

} public class SimulationGUI extends JPanel {
    public static SimulationGUI INSTANCE;

    /**
     * The 'test' main method for testing how the Simulation GUI looks without invoking the rest of the Toast
     * Framework. This is really only used in Development environments when we change the layout or content of the GUI.
     */
    public static void main(String[] args) {
        //Test main method, never invoked outside of development environments
        Profiler.INSTANCE.section("Pre-Initialization").section("Simulation").start("GUI");
        create();
        Profiler.INSTANCE.section("Pre-Initialization").section("Simulation").stop("GUI");
    }

    /**
     * Create the Simulation GUI. This inflates the GUI and sets all the constants. This also
     * hooks into the Toast.shudownSafely method for when the GUI is closed.
     */
    public static JPanel create() {
        JFrame frame = new JFrame("Toast Simulation GUI");
        JPanel panel = new SimulationGUI();
        frame.add(panel);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
        frame.setResizable(false);
        frame.addWindowListener(new WindowAdapter() {
            @Override
            public void windowOpened(WindowEvent e) {
                super.windowOpened(e);
            }

            @Override
            public void windowClosing(WindowEvent e) {
                super.windowClosing(e);
                Toast.getToast().shutdownSafely();
            }
        });
        return panel;
    }

    GuiNumberSpinner[] dioSpinners = new GuiNumberSpinner[10];
    GuiNumberSpinner[] pwmSpinners = new GuiNumberSpinner[10];
    GuiNumberSpinner[] accelSpinners = new GuiNumberSpinner[3];
    GuiRelay[] relays = new GuiRelay[4];

    GuiRobotState disabled, auto, test, teleop;

    public SimulationGUI() {
        INSTANCE = this;

        this.setBackground(new Color(11, 11, 11));
        this.setPreferredSize(new Dimension(700, 500));
        this.setVisible(true);
        this.setLayout(null);

        CommonGUI.setup_keys(this, this::reinitElements);

        initElements();

        CommonGUI.registerKeyCommand(this, "D", () -> { setState(RobotState.DISABLED); });
        CommonGUI.registerKeyCommand(this, "A", () -> { setState(RobotState.AUTONOMOUS); });
        CommonGUI.registerKeyCommand(this, "T", () -> { setState(RobotState.TELEOP); });

        CommonGUI.registerKeyCommand(this, "C", this::openSRX);
        CommonGUI.registerKeyCommand(this, "P", this::openPneumatics);
    }

    /**
     * Set the state and repaint
     */
    public void setState(RobotState state) {
        SimulationData.currentState = state;
        repaintState();
    }

    /**
     * Repaint State Buttons
     */
    public void repaintState() {
        disabled.repaint();
        auto.repaint();
        teleop.repaint();
        test.repaint();
    }

    /**
     * Completely reinflate the GUI. This replaces all JComponents in the GUI.
     */
    public void reinitElements() {
        this.removeAll();
        initElements();
    }

    /**
     * Inflate the GUI. This does everything from adding all the elements, to adding the background image and setting up
     * the GUI. This is where most of the work is done, and is only called once: upon creation of the GUI.
     */
    public void initElements() {
        Image logo;
        try {
            logo = ImageIO.read(SimulationGUI.class.getClassLoader().getResourceAsStream("assets/toast/gui/darkRIOsmall.png")).getScaledInstance(400, 400, Image.SCALE_SMOOTH);
            JLabel imageLabel = new JLabel(new ImageIcon(logo));
            imageLabel.setBounds(150, 70, 400, 400);
            this.add(imageLabel);
        } catch (Exception e) {
            e.printStackTrace();
        }

        for (int i = 0; i < SimulationData.dioValues.length; i++) {
            byte val = SimulationData.dioValues[i];
            byte dir = SimulationData.dioDirections[i];

            GuiNumberSpinner spinner = new GuiNumberSpinner(90, 216 + (22 * i), val, 1, 0, 1, dir == 1, this);
            dioSpinners[i] = spinner;
            final int finalI = i;
            spinner.setCallback(new GuiNumberSpinner.Callback() {
                @Override
                public void callback(double value) {
                    SimulationData.dioValues[finalI] = (byte) value;
                    InterruptContainer container = InterruptContainer.getByPin(finalI);
                    if (container != null)
                        container.trigger(value >= 0.99);
                }
            });
        }

        for (int i = 0; i < SimulationData.pwmValues.length; i++) {
            double val = SimulationData.pwmValues[i];

            GuiNumberSpinner spinner = new GuiNumberSpinner(540, 214 + (22 * i), val, 0.05, -1, 1, false, this);
            spinner.enableProgress();
            pwmSpinners[i] = spinner;
        }

        createLabel("Accelerometer", 20, 10, 100, 14, new Color(180, 180, 180));

        for (int i = 0; i < SimulationData.accelerometer.length; i++) {
            double val = SimulationData.accelerometer[i];

            String l = i == 0 ? "X" : i == 1 ? "Y" : "Z";

            createLabel(l + " Axis", 80, 28 + (15 * i), 100, 10, new Color(150, 150, 150));

            GuiNumberSpinner spinner = new GuiNumberSpinner(20, 33 + (15 * i), val, 0.1, -5, 5, true, this);
            accelSpinners[i] = spinner;
            final int finalI = i;
            spinner.setCallback(new GuiNumberSpinner.Callback() {
                @Override
                public void callback(double value) {
                    SimulationData.accelerometer[finalI] = value;
                }
            });
        }

        createLabel("Power", 210, 10, 100, 14, new Color(180, 180, 180));

        GuiNumberSpinner voltage = new GuiNumberSpinner(205, 30, 12.5, 0.1, 5, 12.8, true, this);
        voltage.setCallback(new GuiNumberSpinner.Callback() {
            @Override
            public void callback(double value) {
                SimulationData.powerVinVoltage = value;
                SimulationData.pdpVoltage = value;
            }
        });

        GuiButton brownout = new GuiButton(205, 45, 50, 20, false, "Brownout", true, this);
        brownout.setFont(new Font("Arial", 0, 10));
        brownout.setActiveColor(new Color(170, 100, 100));
        brownout.setCallback(new GuiButton.ButtonCallback() {
            @Override
            public void onClick() {
            }

            @Override
            public void onToggle(boolean state) {
                SimulationData.powerUserActive6V = !state;
                SimulationData.powerUserActive5V = !state;
                SimulationData.powerUserActive3V3 = !state;
            }
        });

        createLabel("FWD", 235, 467, 40, 9, new Color(150, 150, 150));
        createLabel("REV", 235, 479, 40, 9, new Color(150, 150, 150));
        for (int i = 0; i < SimulationData.relay_fwd.length; i++) {
            boolean isForward = SimulationData.relay_fwd[i];
            boolean isReverse = SimulationData.relay_rvs[i];

            GuiRelay relay = new GuiRelay(267 + (int)(21.5 * i), 472, 10, 22, this);
            relay.setForward(isForward);
            relay.setReverse(isReverse);

            relays[i] = relay;
        }

        disabled = new GuiRobotState(575, 20, RobotState.DISABLED, this);
        auto = new GuiRobotState(575, 50, RobotState.AUTONOMOUS, this);
        teleop = new GuiRobotState(575, 80, RobotState.TELEOP, this);
        test = new GuiRobotState(575, 110, RobotState.TEST, this);

        GuiButton pneumaticsButton = new GuiButton(20, 100, 100, 30, false, "Pneumatics", false, this);
        pneumaticsButton.setCallback(new GuiButton.ButtonCallback() {
            @Override
            public void onClick() {
                openPneumatics();
            }
            public void onToggle(boolean state) { }
        });

        GuiButton srxButton = new GuiButton(20, 140, 100, 30, false, "CAN Talon SRX", false, this);
        srxButton.setCallback(new GuiButton.ButtonCallback() {
            @Override
            public void onClick() {
                openSRX();
            }
            public void onToggle(boolean state) { }
        });
    }

    /**
     * Open the Talon SRX Sub-GUI
     */
    public void openSRX() {
        if (TalonSRX_GUI.INSTANCE == null)
            TalonSRX_GUI.create();
    }

    /**
     * Open the Pneumatics Sub-GUI
     */
    public void openPneumatics() {
        if (PneumaticsGUI.INSTANCE == null)
            PneumaticsGUI.create();
    }

    /**
     * Create a JLabel to place next to various components.
     */
    public JLabel createLabel(String text, int x, int y, int width, int fontSize, Color color) {
        JLabel label = new JLabel(text);
        label.setForeground(color);
        label.setBounds(x, y, width, 20);
        label.setFont(new Font("Arial", 0, fontSize));
        this.add(label);
        return label;
    }

    /**
     * Paint the components on the GUI. This is called whenever a global simulation Refresh is required, however most
     * repainting is done in the actual components themselves when required.
     */
    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        paint((Graphics2D) g);
    }

    /**
     * Paint extra components. This is used for the small 'credit' label near the bottom of the GUI, as well as extra
     * details in the future when they are required.
     */
    public void paint(Graphics2D g) {
        g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
        g.setColor(new Color(180, 180, 180));
        g.setFont(new Font("Arial", Font.ITALIC | Font.BOLD, 9));
        g.drawString("Toast Simulation GUI      -by OpenRIO-", 500, 490);
    }

} public class ToastSessionJoiner {

    static DelegateClient LOGGER_DELEGATE;
    static DelegateClient COMMAND_DELEGATE;

    static DataOutputStream commands_out;
    static BufferedReader logger_in;

    /**
     * Start the joiner. This simply connects to the designated Toast instance on localhost and
     * creates a logger and command bus bridge.
     */
    public static void init() {
        LOGGER_DELEGATE = new DelegateClient("localhost", 5805, "TOAST_logger");
        COMMAND_DELEGATE = new DelegateClient("localhost", 5805, "TOAST_command");

        try {
            LOGGER_DELEGATE.connect();
            COMMAND_DELEGATE.connect();

            commands_out = new DataOutputStream(COMMAND_DELEGATE.getSocket().getOutputStream());
            logger_in = new BufferedReader(new InputStreamReader(LOGGER_DELEGATE.getSocket().getInputStream()));

            new Thread(new Runnable() {
                @Override
                public void run() {
                    String line;
                    try {
                        while ((line = logger_in.readLine()) != null) {
                            System.out.println(line);
                        }
                    } catch (IOException e) {
                        System.err.println("Unexpected error while reading from Logger Delegate");
                        e.printStackTrace();
                        System.exit(-1);
                    }
                }
            }).start();

            Scanner scanner = new Scanner(System.in);
            while (true) {
                String line = scanner.nextLine();
                if (line.trim().equalsIgnoreCase("--exit"))
                    System.exit(0);
                commands_out.writeBytes(line + "\n");
            }
        } catch (Exception e) {
            System.err.println("Something went wrong while establishing a connection to the Toast instance...");
            e.printStackTrace();
            System.exit(-1);
        }
    }

} public class JavaScript {

    static ScriptEngineManager manager;
    static ScriptEngine engine;
    static String engine_type;
    static List<String> loadedScripts;

    /* Load Task Stuff */

    static NonVitalLoadTask loadTask;

    /**
     * Start loading the JavaScript engine. This is done internally
     */
    public static void startLoading() {
        loadTask = new NonVitalLoadTask(new Runnable() {
            @Override
            public void run() {
                init();
                binderInit();
            }
        });
        loadTask.startLoading();
    }

    /**
     * Require the JavaScript engine to be loaded before continuing. Call this
     * if you need JavaScript in upcoming code execution.
     */
    public static void requireLoad() {
        loadTask.requireLoaded();
    }

    /* JS Stuff */

    /**
     * Initialize the ScriptEngine and its manager. This is called by Toast, so don't worry about
     * calling it yourself.
     */
    public static void init() {
        ProfilerSection s = Profiler.INSTANCE.section("JavaScript").section("Init");
        s.start("Manager");
        manager = new ScriptEngineManager();
        s.stop("Manager");
        s.start("Engine");
        engine = manager.getEngineByName("nashorn");
        if (engine == null) {
            engine = manager.getEngineByName("rhino");
            engine_type = "Rhino";
        } else engine_type = "Nashorn";
        s.stop("Engine");
    }

    /**
     * Called when Toast is ready and bindings are ready to be bound.
     */
    public static void binderInit() {
        ProfilerSection sec = Profiler.INSTANCE.section("JavaScript");
        sec.start("LoadSystem");
        loadSystem();
        sec.stop("LoadSystem");
        sec.start("Engine");
        JSEngine.init();
        sec.stop("Engine");
    }

    /**
     * Initialize the Loader. This loads JS files from the FileSystem and imports them into the ScriptEngine. This allows
     * users to code in JavaScript if they wanted to for some hellish reason.
     */
    public static void loaderInit() {
        try {
            ScriptLoader.getScriptDirByType("js").mkdirs();
            loadedScripts = ScriptLoader.loadAll("js", ToastConfiguration.config.getArray("javascript.autoload", new String[] {"main.js"}));
        } catch (Exception e) {
            Toast.log().error("Could not Load JavaScript script files: " + e);
            Toast.log().exception(e);
        }
    }

    /**
     * Load a file with the given name. This loads from the script/js directory
     */
    public static Object loadFile(String file) {
        try {
            requireLoad();
            if (supported()) {
                ScriptLoader.getScriptDirByType("js").mkdirs();
                if (file.contains(".js")) {
                    return ScriptLoader.loadSingle("js", engine, file);
                } else {
                    return ScriptLoader.loadModule("js", engine, file);
                }
            }
        } catch (Exception e) {
            Toast.log().error("Could not Load JavaScript script files: " + e);
            Toast.log().exception(e);
        }
        return null;
    }

    /**
     * Load a file with the given name. This loads from the script/js directory
     */
    public static Object loadFileRelative(String loaddir, String file) {
        try {
            requireLoad();
            if (supported()) {
                return ScriptLoader.loadRelative(loaddir, engine, (file.contains(".") ? file : file + ".js"));
            }
        } catch (Exception e) {
            Toast.log().error("Could not Load JavaScript script files: " + e);
            Toast.log().exception(e);
        }
        return null;
    }

    /**
     * Load a file relative to the current USB mass storage device. This is desired among module makers.
     */
    public static Object loadFileHere(String loaddir, String file) {
        try {
            requireLoad();
            if (supported()) {
                return ScriptLoader.loadHere(loaddir, engine, (file.contains(".") ? file : file + ".js"));
            }
        } catch (Exception e) {
            Toast.log().error("Could not Load JavaScript script files: " + e);
            Toast.log().exception(e);
        }
        return null;
    }

    /**
     * Load Toast's System .JS files before we load anything else.
     */
    private static void loadSystem() {
        try {
            engine.eval("__GLOBAL = this");

            loadSystemLib("Map.js");
            loadSystemLib("Toast.js");
        } catch (ScriptException e) {
            Toast.log().error("Could not load System Library: " + e);
            Toast.log().exception(e);
        }
    }

    /**
     * Evaluate a script. This will execute a script given and return the value of the script, with the default
     * bindings of the ScriptEngine. This is a shortcut for engine.eval()
     */
    public static Object eval(String script) throws ScriptException {
        requireLoad();
        checkSupported();
        return engine.eval(script);
    }

    /**
     * Evaluate a script. This will execute a script given and return the value of the script, with the default
     * bindings of the ScriptEngine. This is a shortcut for engine.eval()
     */
    public static Object eval(Reader reader) throws ScriptException {
        requireLoad();
        checkSupported();
        return engine.eval(reader);
    }

    /**
     * Put an object into the Engine's global bindings. This is the same as registering a 'var' in the JavaScript.
     */
    public static void put(String key, Object o) {
        requireLoad();
        checkSupported();
        engine.put(key, o);
    }

    /**
     * Get an object from the Engine's global bindings. This is the same as getting a variable from the JavaScript
     * engine.
     */
    public static Object get(String key) {
        requireLoad();
        checkSupported();
        return engine.get(key);
    }

    /**
     * Returns true if Scripting is supported on this platform. This will return false if a JavaScript engine is
     * not available (neither Nashorn or Rhino)
     */
    public static boolean supported() {
        return engine != null;
    }

    /**
     * Checks for supported(), and will throw an UnsupportedOperationException if false.
     */
    private static void checkSupported() {
        if (!supported()) throw new UnsupportedOperationException("ScriptEngine not supported in this JDK!");
    }

    /**
     * Get the Engine object itself if the shortcuts provided in this class aren't relevant. This gives full access
     * to all the ScriptEngine's features, including its bindings and other evaluation methods. Use this with caution
     */
    public static ScriptEngine getEngine() {
        requireLoad();
        return engine;
    }

    /**
     * Get the Engine Type. This is used in the Environment log for Crashes and things of the like. This returns either
     * 'Nashorn' or 'Rhino', and 'null' if supported() returns false.
     */
    public static String engineType() {
        return engine_type;
    }

    /**
     * Create a new ScriptEngine. This allows for modules to move away from the Bindings set by Toast and create their own
     * Scripting environment if they wish to. This allows for enhanced control over the JavaScript used in their code.
     */
    public static ScriptEngine createEngine() {
        ScriptEngine engine = manager.getEngineByName("nashorn");
        if (engine == null) {
            engine = manager.getEngineByName("rhino");
        }
        return engine;
    }

    /**
     * Get a Toast System Library. These libraries are included with Toast by default and are found in
     * /assets/toast/script/js (dev env: src/main/resources/assets/toast/script/js/).
     */
    public static Reader getSystemLib(String name) {
        checkSupported();
        return new InputStreamReader(JavaScript.class.getResourceAsStream("/assets/toast/script/js/" + name));
    }

    /**
     * Load a Toast System Library. These libraries are included with Toast by default and are found in
     * /assets/toast/script/js (dev env: src/main/resources/assets/toast/script/js/).
     */
    public static Object loadSystemLib(String name) throws ScriptException {
        checkSupported();
        return engine.eval(getSystemLib(name));
    }

    /**
     * Copy Toast's bindings (loaded classes) to your own Bindings object to access all of Toast's SystemLibs.
     */
    public static void copyBindings(Bindings target) {
        requireLoad();
        Bindings source = engine.getBindings(ScriptContext.ENGINE_SCOPE);
        target.putAll(source);
    }

} public class ToastSecurityManager extends SecurityManager {

    public static ToastSecurityManager INSTANCE;
    private static ThreadGroup group;
    private static ThreadGroup main;

    /**
     * Initialize the security manager. This is done by Toast during runtime and should NEVER be called by a module.
     */
    public static void init() {
        if (SecurityPolicy.get() != SecurityPolicy.NONE) {
            INSTANCE = new ToastSecurityManager();
            System.setSecurityManager(INSTANCE);
            main = Thread.currentThread().getThreadGroup();
            group = new ThreadGroup(Thread.currentThread().getThreadGroup(), "Toasted");
        }
    }

    /**
     * Check a permission. This performs the check statement for the permission type and handles it accordingly
     */
    @Override
    public void checkPermission(Permission perm) {
        if (perm instanceof FilePermission) {
        } else if (perm instanceof SocketPermission) {
            SocketPermission sp = (SocketPermission) perm;
            h_Socket(sp);
        } else if (perm instanceof RuntimePermission) {
            RuntimePermission rp = (RuntimePermission) perm;
            h_Runtime(rp);
        }
    }

    /**
     * Returns the location of a given class or package in a callstack, or -1 if it is not present.
     */
    public int existsInCallStack(String packageorclass, String method) {
        Pattern porc = Pattern.compile(packageorclass);
        Pattern meth = Pattern.compile(method);
        StackTraceElement[] elements = Thread.currentThread().getStackTrace();
        for (int i = 0; i < elements.length; i++) {
            if (porc.matcher(elements[i].getClassName()).matches() && meth.matcher(elements[i].getMethodName()).matches())
                return i;
        }
        return -1;
    }

    /** RUNTIME PERMISSIONS **/

    /**
     * Checks for Runtime violations. This includes calling System.exit() from somewhere OTHER than Toast.shutdownSafely().
     * This exists due to the nature of the Toast shutdownSafely() hook, as it is designed to stop all systems before
     * shutdown, to ensure issues do not arise.
     */
    public void h_Runtime(RuntimePermission perm) {
        if (perm.getName().contains("exitVM")) {
            if (existsInCallStack("jaci.openrio.toast.core.Toast", "shutdown.*") == -1) {
                SecurityPolicy.log().warn("Unregistered System.exit() call - This should be called with Toast.shutdownSafely()!");
                for (int i = 5; i <= 7; i++)
                    SecurityPolicy.log().warn("\tat " + String.valueOf(Thread.currentThread().getStackTrace()[i]));
            }
        }
    }

    /** SOCKET PERMISSIONS **/
    /* Disclaimer: We don't block the sockets, we just warn the user when it's not on a port that FMS can forward, so they don't pull their hair out at competition
     * when their sockets perms are disallowed */

    private String[] exceptionPorts = new String[] {
            "1180",
            "80",
            "443",
            "554",
            "1735",   //NetworkTables
            "0",       //Not yet bound / multicast
            "1110", "1150", "1130", "1140"   // Driver Station
    };
    /**
     * Handles the given SocketPermission. This serves to warn the user if a socket is using a port that FMS cannot forward, but will never
     * block a socket.
     */
    public void h_Socket(SocketPermission perm) {
        if (Thread.currentThread().getThreadGroup().equals(main) && !isPortException(perm)) {
            throw new ThreadingException("Networking On Main Thread!");
        }
        if (perm.getActions().contains("listen")) {
            if (existsInCallStack("sun.management.jmxremote.*", ".*") != -1) return;            //RMI Profiler Routing

            SocketPermission impliee = new SocketPermission("*:5800-5810", "listen");
            if (!impliee.implies(perm) && !isPortException(perm)) {
                switch (SecurityPolicy.get()) {
                    case NONE:
                        break;
                    default:
                        SecurityPolicy.log().warn("Socket Port Warning, not within 5800...5810 (FMS will not forward it!): " + perm.getName());
                        SecurityPolicy.log().exception(new Throwable());
                        break;
                }
            }
        }
    }

    /**
     * Checks if the port is an exception to the 5800-5810 rule (such as NetworkTables)
     */
    private boolean portExceptionCheck(SocketPermission permission, String p) {
        SocketPermission impliee = new SocketPermission("*:" + p, "listen");
        return impliee.implies(permission);
    }
    /**
     * Does the {@link #portExceptionCheck(SocketPermission, String)} on all exceptionPorts listed in the array.
     */
    private boolean isPortException(SocketPermission perm) {
        for (String p : exceptionPorts) if (portExceptionCheck(perm, p)) return true;
        return false;
    }

    /**
     * Make sure new Threads are created on the ToastThreadPool group, to ensure
     * that Networking and other actions don't occur within the main Thread Group.
     */
    public ThreadGroup getThreadGroup() {
        return group;
    }

    /**
     * Thrown when code violates a rule regarding Multithreading. For example, Networking SHOULD NOT be done on the
     * main Thread, but instead delegated elsewhere. If networking does occur on the main Thread, this exception
     * is thrown and the robot program stopped.
     */
    public static class ThreadingException extends RuntimeException {
        public ThreadingException(String s) {
            super(s);
        }
    }

} public class GlobalBlackboard extends HashMap<String, Object> {

    public static GlobalBlackboard INSTANCE;

    public GlobalBlackboard() {
        super();
        INSTANCE = this;
    }

} public class OptionParser {

    public static class Handler {
        public String flagS, flagL, desc;
        public Consumer<List<String>> callback;
        public int expected;
    }

    public List<Handler> handlers;

    public OptionParser(boolean exitOnHelp) {
        handlers = new ArrayList<>();
        on("-h", "--help", "Display Help Message", 0, (list) -> {
            System.out.println(helpMessage());
            if (exitOnHelp) System.exit(0);
        });
    }

    public List<String> parse(String[] args) {
        List<String> passable = new ArrayList<>();
        final int[] index = new int[1];         // Needs to be a final array to be modified inside loop
        for (index[0] = 0; index[0] < args.length; index[0]++) {
            int startI = index[0];
            String argument = args[index[0]];
            handlers.stream().forEach(handler -> {
                if (argument.equals(handler.flagL) || argument.equals(handler.flagS)) {
                    int exp = handler.expected;
                    List<String> cbPass = new ArrayList<String>();
                    for (int expIdx = startI + 1; expIdx < (startI + 1 + exp) && expIdx < args.length; expIdx++) {
                        cbPass.add(args[expIdx]);
                    }
                    handler.callback.accept(cbPass);
                    index[0] += exp;
                }
            });
            if (startI == index[0]) passable.add(argument);
        }
        return passable;
    }

    public List<String> parse(String args, String delim) {
        return parse(args.split(delim));
    }

    public List<String> parse(String args) {
        return parse(args, " ");
    }

    public void on(String flagShort, String flagLong, String flagDescription, int expected, Consumer<List<String>> callback) {
        Handler h = new Handler();
        h.flagS = flagShort;
        h.flagL = flagLong;
        h.desc = flagDescription;
        h.callback = callback;
        h.expected = expected;
        handlers.add(h);
    }

    public void on(String flag, String description, int expected, Consumer<List<String>> callback) {
        on(null, flag, description, expected, callback);
    }

    public String helpMessage() {
        List<String> list = new ArrayList<>();

        for (Handler handler : handlers) {
            if (handler.flagS != null) {
                list.add(String.format("\t %-7s %15s \t-- %10s", handler.flagS, handler.flagL, handler.desc));
            } else {
                list.add(String.format("\t %-7s %15s \t-- %10s", "", handler.flagL, handler.desc));
            }
        }

        return String.join("\n", list);
    }

} public class Async extends ThreadPoolExecutor {

    public static Async INSTANCE;

    /**
     * Start the main Async INSTANCE. This is done for you in Toast initialization.
     */
    public static void init() {
        int size = ToastConfiguration.Property.THREAD_POOL_SIZE.asInt();
        INSTANCE = new Async(size, size, "Toast|Async");
    }

    /**
     * Create a new Async pool for you own usage
     * @param corePoolSize      The starting size of the pool
     * @param maximumPoolSize   The maximum size of the pool
     * @param name              The name of the pool
     */
    public Async(int corePoolSize, int maximumPoolSize, String name) {
        super(corePoolSize, maximumPoolSize, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), new Factory(name));
    }

    /**
     * Submit a task to this Async pool. Calls the {@link AsyncTask#onAddedToQueue(Async, Future)} method
     */
    public Future submit(AsyncTask task) {
        Future f = submit((Runnable)task);
        task.onAddedToQueue(this, f);
        return f;
    }

    /**
     * Submit a task to the default Async pool. Calls the {@link AsyncTask#onAddedToQueue(Async, Future)} method
     */
    public static Future schedule(AsyncTask task) {
        return INSTANCE.submit(task);
    }

    /**
     * Shutdown the thread pool and wait for any pending workers to finish. DO NOT CALL THIS ON THE MAIN THREAD POOL.
     */
    public void finish() {
        shutdown();
    }

    /**
     * Wait until the threadpool is done with all work, forever. DO NOT CALL THIS ON THE MAIN THREAD POOL.
     */
    public void waitForCompletion() {
        try {
            awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
        } catch (Exception e) {}
    }

    // Factory
    private static class Factory implements ThreadFactory {

        private AtomicInteger integer = new AtomicInteger(1);

        String name;

        public Factory(String name) {
            this.name = name;
        }

        /**
         * Creates a new Thread Object for the ThreadFactory. This is given the proper name for the Thread Pool and is not
         * set as a daemon to assure that it can be easily debugged in profilers and other utilities.
         */
        @Override
        public Thread newThread(Runnable r) {
            Thread t = new Thread(r, name + "-" + integer.getAndIncrement());
            if (t.isDaemon())
                t.setDaemon(false);
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }

    }

} public class Version implements Comparable<Version> {

    static String version = "1.3.0";
    static Version vers;
    static boolean known = false;
    static Pattern versionPattern = Pattern.compile("(\\d*).(\\d*).(\\d*)(-(\\d*)([a-z]))?");

    static String full_commit_hash;
    static String short_commit_hash;

    /**
     * Initialize the Version. This reads from the toast.version file in the jar file, parsing the Regex and validating the Toast
     * File's Version for modules to read.
     */
    public static void init() {
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(ClassLoader.getSystemClassLoader().getResourceAsStream("assets/toast/toast.version")));
            version = reader.readLine();
            reader.close();
            ToastBootstrap.toastLogger.info("Toast Version: " + version);
            known = true;
            vers = new Version(version);

            reader = new BufferedReader(new InputStreamReader(ClassLoader.getSystemClassLoader().getResourceAsStream("assets/toast/toast.git_info")));
            full_commit_hash = reader.readLine();
            short_commit_hash = reader.readLine();
            reader.close();

            ToastBootstrap.toastLogger.info("Toast Commit Hash: " + short_commit_hash);
        } catch (Exception e) {
            ToastBootstrap.toastLogger.error("Could not retrieve Toast Version or Git Hash.");
        }
    }

    /**
     * Returns true if we know what version Toast is, or false if we don't. This will only return false if
     * there was an error reading the /assets/toast/toast.version file.
     */
    public static boolean versionKnown() {
        return known;
    }

    /**
     * Get Toast's current version.
     */
    public static Version version() {
        return vers;
    }

    /**
     * Get the full Git commit hash of the Toast build
     */
    public static String getCommitHash() {
        return full_commit_hash;
    }

    /**
     * Get the short Git commit hash of the Toast build
     */
    public static String getShortCommitHash() {
        return short_commit_hash;
    }

    /**
     * Returns true if 'version' is equal to or newer than 'required'
     */
    public static boolean requireOrNewer(Version version, Version required) {
        int compare = version.compareTo(required);
        return compare == 0 || compare == 1;
    }

    // -- inst -- //
    String versString;

    int major;
    int minor;
    int build;
    boolean preRelease;
    char prereleaseType;
    int prebuild;

    boolean parsed;

    /**
     * Create a new Version and Parse it. The version object should be in the form
     * 'major.minor.build[-prebuild]', with everything in square brackets being
     * optional. 'prebuild' should consist of a build number following a letter
     * regarding the build type (A for Alpha, B for Beta, etc).
     *
     * e.g.
     * "1.3.0"
     * "1.3.0-10a"
     */
    public Version(String versionString) {
        versString = versionString;
        parsed = false;
        parse();
    }

    /**
     * Parse the Version String with the Regex pattern
     */
    public void parse() {
        Matcher matcher = versionPattern.matcher(versString);
        int c = matcher.groupCount();
        if (c < 3 || !matcher.matches()) throw new VersionFormatException();
        major = Integer.parseInt(matcher.group(1));
        minor = Integer.parseInt(matcher.group(2));
        build = Integer.parseInt(matcher.group(3));
        if (c == 6 && matcher.group(6) != null) {
            preRelease = true;
            prereleaseType = matcher.group(6).charAt(0);
            prebuild = Integer.parseInt(matcher.group(5));
        }
        parsed = true;
    }

    /**
     * Get the parsed version as a formatted string
     */
    public String get() {
        if (!preRelease)
            return String.format("%s.%s.%s", major, minor, build);
        else
            return String.format("%s.%s.%s-%s%s", major, minor, build, prebuild, prereleaseType);
    }

    /**
     * Get the raw version passed into the constructor (unparsed)
     */
    public String raw() {
        return versString;
    }

    /**
     * Convert the Version to String Format, for use in printing to the console or other implementations
     */
    public String toString() {
        if (!parsed)
            return "Version[unknown]";
        if (!preRelease)
            return String.format("Version[%s.%s.%s]", major, minor, build);
        else
            return String.format("Version[%s.%s.%s-%s%s]", major, minor, build, prebuild, prereleaseType);
    }

    /**
     * Compare 2 versions against each other.
     * @return 0 if versions are equal, -1 if older than 'o', 1 if newer than 'o'
     */
    @Override
    public int compareTo(Version o) {
        if (equals(o)) return 0;
        if (o.major != major)
            return o.major > major ? -1 : 1;
        if (o.minor != minor)
            return o.minor > minor ? -1 : 1;
        if (o.build != build)
            return o.build > build ? -1 : 1;
        if (o.preRelease && preRelease) {
            if (o.prebuild == prebuild)
                return o.prereleaseType > prereleaseType ? -1 : 1;
            return o.prebuild > prebuild ? -1 : 1;
        } else if (o.preRelease)
            return 1;
        else if (preRelease)
            return -1;
        return 0;
    }

    /**
     * Returns true if this version is newer than 'o'
     */
    public boolean newerThan(Version o) {
        return compareTo(o) == 1;
    }

    /**
     * Returns true if this version is older than 'o'
     */
    public boolean olderThan(Version o) {
        return compareTo(o) == -1;
    }

    /**
     * Returns true if versions are equal
     */
    public boolean equals(Version o) {
        return o.preRelease == preRelease && !(o.major != major || o.minor != minor || o.build != build) && (!preRelease || o.prereleaseType == prereleaseType && o.prebuild == prebuild);
    }

    /**
     * Thrown if a supplied version does not pass the Regex Matcher (is malformed).
     * @see {@link jaci.openrio.toast.lib.Version#Version(String)}
     */
    public static class VersionFormatException extends RuntimeException { }

} public class CrashHandler implements Thread.UncaughtExceptionHandler {

    static Vector<CrashInfoProvider> providers;
    static File crashDir;
    static DateFormat dateFormat;
    static CrashHandler instance;

    static int exception_fms_count = 0;
    static long exception_fms_start = 0;

    /**
     * Initialize the handler. This is handled by Toast.
     */
    public static void init() {
        try {
            providers = new Vector<CrashInfoProvider>();
            crashDir = new File(ToastBootstrap.toastHome, "crash");
            crashDir.mkdirs();
        } catch (Exception e) {}
        dateFormat = new SimpleDateFormat("yyyy-MM-dd_hh-mm-ss");
        instance = new CrashHandler();
        Thread.setDefaultUncaughtExceptionHandler(instance);
        Thread.currentThread().setUncaughtExceptionHandler(instance);

        providers.add(new CrashInfoToast());
        providers.add(new CrashInfoEnvironment());
        providers.add(new CrashInfoModules());
    }

    /**
     * Register a provider for the Crash Handler
     */
    public static void registerProvider(CrashInfoProvider prov) {
        providers.add(prov);
    }

    /**
     * Handle an uncaught exception
     */
    public static void handle(Throwable t) {
        try {
            boolean shouldShutdown = !Environment.isCompetition();
            exception_fms_count++;
            if (exception_fms_start == 0) exception_fms_start = System.currentTimeMillis();

            String fn = "crash-" + dateFormat.format(new Date());
            File file = new File(crashDir, fn + ".txt");
            SplitStream split = new SplitStream(System.err, new FileOutputStream(file));
            PrintStream out = new PrintStream(split);

            out.println("**** CRASH LOG ****");
            if (!shouldShutdown && exception_fms_count > 10 && exception_fms_start < 10000) {     // 10 Exceptions in 10 Seconds
                out.println("NOTICE: More than 10 Exceptions have occured in 10 seconds. This is a good sign there is something majorly wrong, and even though you are connected to FMS, we're going to shut down anyway. Goodbye, World.");
                shouldShutdown = true;
            } else if (!shouldShutdown)
                out.println("NOTICE: Your robot is connected to FMS! To save your robot, we won't shut down your robot unless this exception is repeated.");
            out.println("Your robot has crashed. Following is a crash log and more details.");
            out.println("This log has been saved to: " + file.getCanonicalPath());
            out.println("This log will also be duplicated to USB devices, with the filename: " + fn + ".txt");

            out.println(Assets.getAscii("crash"));

            for (CrashInfoProvider provider : providers) {
                String info = provider.getCrashInfoPre(t);
                if (info != null)
                    out.println("\t" + provider.getCrashInfoPre(t) + "\n");
            }
            t.printStackTrace(out);
            out.println();
            out.println("Crash Information: ");
            for (CrashInfoProvider provider : providers) {
                try {
                    out.println("\t" + provider.getName() + ": ");
                    List<String> info = provider.getCrashInfo(t);
                    if (info != null)
                        for (String s : info) {
                            out.println("\t\t" + s);
                        }
                    out.println();
                } catch (Throwable e) {
                    out.println("Provider: " + provider.getClass().getCanonicalName() + " could not finalize output. (" + e.getLocalizedMessage() + ")");
                }
            }
            out.println();
            out.println("*******************");

            out.flush();
            out.close();

            File recentLog = SysLogProxy.recentOut;
            File cpFile = new File(crashDir, fn + "-FULL.txt");
            Files.copy(recentLog.toPath(), cpFile.toPath());

            duplicate(cpFile);
            duplicate(file);

            if (shouldShutdown)
                Toast.getToast().shutdownCrash();
        } catch (Exception e) {
        }
    }

    /**
     * Duplicates the given Crash File across all USB Mass Storage devices
     */
    public static void duplicate(File f) {
        String fn = f.getName();
        boolean copied = false;
        for (MassStorageDevice device : USBMassStorage.connectedDevices) {
            File crash = new File(device.toast_directory, "crash");
            crash.mkdirs();
            copied = true;
            try {
                Files.copy(f.toPath(), new File(crash, fn).toPath());
            } catch (IOException e) { }
        }

        if (copied) {
            f.delete();
        }
    }

    /**
     * Handles an UncaughtException by logging the crash and shutting down the Robot safely. THis is
     * automatically called by the JVM when an exception is uncaught on any thread.
     */
    @Override
    public void uncaughtException(Thread t, Throwable e) {
        handle(e);
    }
} public class Logger {

    public static final int ATTR_TIME = 1;
    public static final int ATTR_THREAD = 2;
    public static final int ATTR_COLOR = 4;

    public static final int ATTR_DEFAULT = ATTR_TIME | ATTR_THREAD | ATTR_COLOR;

    public static final LogLevel INFO = new LogLevel("INFO");
    public static final LogLevel DEBUG = new LogLevel("DEBUG");
    public static final LogLevel WARN = new LogLevel("WARN").setPrintStream(System.err).setColor(Pretty.Colors.RED);
    public static final LogLevel ERROR = new LogLevel("ERROR").setPrintStream(System.err).setColor(Pretty.Colors.RED);
    public static final LogLevel SEVERE = new LogLevel("SEVERE").setPrintStream(System.err).setColor(Pretty.Colors.RED);

    public DateFormat dateFormat = new SimpleDateFormat("dd/MM/yy-hh:mm:ss");

    public static Vector<LogHandler> handlers = new Vector<LogHandler>();

    int attr;
    String name;

    /**
     * Registers a {@link jaci.openrio.toast.lib.log.LogHandler} with all loggers
     */
    public static void addHandler(LogHandler handler) {
        handlers.add(handler);
    }

    /**
     * Create a new Logger Object
     * @param name The name of the logger
     * @param attributes A Binary Or operation of the attributes to use with the logger. This should be
     *                   ATTR_TIME | ATTR_THREAD, or ATTR_DEFAULT by shorthand, unless you want to omit this
     *                   data
     */
    public Logger(String name, int attributes) {
        this.attr = attributes;
        this.name = name;
    }

    /**
     * Formats the current time data of the system into the [dd/MM/yy-hh:mm:ss] format
     */
    String getTime() {
        return dateFormat.format(new Date());
    }

    /* LOGGING BEGIN */

    /**
     * Log a new message on the selected printstream with the given method and level. This is where all other
     * 'log' type methods in this class delegate to.
     */
    private void log(String message, String level, String levelColor, PrintStream ps) {
        StringBuilder builder = new StringBuilder();

        if (!level.equals("raw"))
            builder.append(getPrefix(level, levelColor));

        builder.append(message);

        String ts = builder.toString();

        ps.println(ts);

        for (LogHandler hand : handlers)
            hand.onLog(level, message, ts, this);
    }

    /**
     * Get the prefix for all messages on this logger. This adds the DateTime, as well as the
     * Thread ID and Logger Name if they are enabled in the Logger Attributes
     */
    public String getPrefix(String level, String levelColor) {
        StringBuilder builder = new StringBuilder();

        if ((attr & ATTR_TIME) == ATTR_TIME)
            builder.append(tag(getTime(), "green", "gray"));

        builder.append(tag(name, "green", "magenta"));

        if ((attr & ATTR_THREAD) == ATTR_THREAD)
            builder.append(tag(Thread.currentThread().getName(), "green", "cyan"));

        builder.append(tag(level, "green", levelColor));

        return builder.toString();
    }

    /**
     * Return a tag, maybe formatted with color
     */
    public String tag(String inner, String c1, String c2) {
        boolean color = ((attr & ATTR_COLOR) == ATTR_COLOR) && ToastBootstrap.color;
        String s = "[";
        if (color) s += "<" + c1 + ">";
        s += inner;
        if (color) s += "<" + c2 + ">";
        s += "] ";
        if (color) s += "<" + c1 + ">";
        return color ? Pretty.format(s) : s;
    }

    /**
     * Log a message in Debug Mode (e.g. only if runtime arg)
     */
    public void debug(String message) {
        if (ToastBootstrap.debug_logging)
            log(message, DEBUG);
    }

    /**
     * Log an exception in Debug Mode (e.g. only if debug or exception runtime arg)
     */
    public void debugException(Throwable t) {
        if (ToastBootstrap.exception_info_logging || ToastBootstrap.debug_logging) {
            DEBUG.getPrintSteam().flush();
            log("DEBUG EXCEPTION: " + t, ERROR);
            exception(t);
            ERROR.getPrintSteam().flush();
        }
    }

    /**
     * Format and print a stack trace on the Logger Object. This calls the StackTrace directly, meaning
     * underlying causes and other factors are included in the log. This is the equivalent of calling
     * e.printStackTrace(), but redirecting it to the logger object.
     */
    public void exception(Throwable e) {
        StringWriter writer = new StringWriter();
        e.printStackTrace(new PrintWriter(writer));
        error(writer.toString());
    }

    /**
     * Log a message at the specified level
     */
    public void log(String message, LogLevel level) {
        log(message, level.getName().toUpperCase(), level.getColor().name().toLowerCase(), level.getPrintSteam());
    }

    /**
     * Log a message without any padding or prefixes.
     */
    public void raw(String message) {
        log(message, "raw", "none", System.out);
    }

    /**
     * Log an 'INFO' message. This is the default for logging things
     * that are not errors.
     *
     * This goes to System.out
     */
    public void info(String message) {
        log(message, INFO);
    }

    /**
     * Log a 'WARNING' message. This is for errors that aren't critical
     * or too big of an issue, or if you hurt the robot's feelings
     *
     * This goes to System.err
     */
    public void warn(String message) {
        log(message, WARN);
    }

    /**
     * Log an 'ERROR' message. This is the default for errors that affect
     * robot action.
     *
     * This goes to System.err
     */
    public void error(String message) {
        log(message, ERROR);
    }

    /**
     * Log a 'SEVERE' message. This is for errors that deeply affect robot
     * action and are critical
     *
     * This goes to System.err
     */
    public void severe(String message) {
        log(message, SEVERE);
    }
} public class SysLogProxy {

    static PrintStream sysOut;
    static PrintStream sysErr;

    public static FileOutputStream fileOut;
    public static FileOutputStream fileErr;
    public static PrintStream outStream;
    public static PrintStream errStream;
    public static File logDir;
    public static File recentOut;
    public static File oldOut;
    public static File recentErr;
    public static SplitStream master;
    public static SplitStream masterError;

    static boolean init = false;

    /**
     * Initialize the proxy if it has not already been started. This splits the System.out and System.err streams
     * to Files in the filesystem.
     */
    public static void init() {
        try {
            if (!init) {
                init = true;
                logDir = new File(ToastBootstrap.toastHome, "log");
                logDir.mkdirs();

                recentOut = new File(logDir, "recent.txt");
                recentErr = new File(logDir, "recentErr.txt");

                if (recentOut.exists()) {
                    oldOut = new File(logDir, "last_session.txt");
                    if (oldOut.exists()) oldOut.delete();
                    recentOut.renameTo(oldOut);
                    recentOut = new File(logDir, "recent.txt");
                    recentOut.delete();
                }

                if (recentErr.exists())
                    recentErr.delete();

                fileOut = new FileOutputStream(recentOut);
                fileErr = new FileOutputStream(recentErr);

                sysOut = System.out;
                sysErr = System.err;

                master = new SplitStream(sysOut, fileOut);
                outStream = new ColorPrint(master);
                masterError = new SplitStream(sysErr, fileOut, fileErr);
                errStream = new ColorPrint(masterError);

                System.setOut(outStream);
                System.setErr(errStream);
            }
        } catch (Exception e) {
            System.err.println("System Log Proxy failed...");
            e.printStackTrace();
        }

    }

} public class ModuleConfig {

    static File base_file;
    public static LinkedList<ModuleConfig> allConfigs;

    /**
     * Start the configuration engine. This sets up the Root dir as well as JavaScript bindings for Config.js.
     * This is executed by the Bootstrapper, no need to call it yourself.
     */
    public static void init() {
        ProfilerSection section = Profiler.INSTANCE.section("JavaScript");
        section.start("ModuleConfig");
        base_file = new File(ToastBootstrap.toastHome, "config");
        base_file.mkdirs();
        allConfigs = new LinkedList<>();
        section.stop("ModuleConfig");
    }

    public File parent_file;
    HashMap<String, Object> defaults;
    HashMap<String, Object> full_config;

    public ModuleConfig(String name) {
        try {
            if (USBMassStorage.config_highest != null) {
                base_file = new File(USBMassStorage.config_highest.toast_directory, "config");
                base_file.mkdirs();
            }
            if (!name.contains("."))
                name = name + ".conf";
            parent_file = new File(base_file, name);
            if (!parent_file.exists())
                parent_file.createNewFile();

            allConfigs.add(this);
            load();
        } catch (Exception e) { }
    }

    public ModuleConfig(File file) {
        try {
            this.parent_file = file;
            if (!file.exists())
                file.createNewFile();

            allConfigs.add(this);
            load();
        } catch (Exception e) { }
    }

    /**
     * Load a ModuleConfig. This method will flush the Bindings of the configuration file
     * and reload defaults. This should only be called by the constructor
     */
    private void load() throws IOException, ScriptException {
        defaults = new HashMap<>();
        try {
            reload();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Reloads the configuration file. This will read the config file, add keys if they exist in the Defaults
     * Queue, as well as reformat the config file to PrettyPrint if needed. Call this if Config Files have been
     * changed (by the user) and you want to reload them.
     */
    public void reload() {
        String json_config = "";
        try {
            BufferedReader reader = new BufferedReader(new FileReader(parent_file));
            String ln;
            while ((ln = reader.readLine()) != null)
                json_config += ln;
            reader.close();
        } catch (Exception e) {
        }

        if (json_config.trim().length() == 0)
            json_config = "{}";
        try {
            JsonObject obj = JsonParser.object().from(json_config);
            full_config = deepMerge(defaults, obj);
            PrintStream stream = new PrintStream(parent_file);
            String json = toJSON();
            stream.println(json);
            stream.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Unpack a period delimited string and get the object at that pointer. i.e test.test2.test3 translates to
     * test["test2"]["test3"]
     */
    public static void unpack(HashMap<String, Object> object, String key, Object value) {
        String[] split = key.split("\\.");
        HashMap lobj = object;
        for (int cur = 0; cur < split.length; cur++) {
            String current = split[cur];
            if (!(lobj.containsKey(current) && lobj.get(current) instanceof HashMap)) {
                lobj.put(current, new HashMap<String, Object>());
            }
            if (cur == split.length - 1) {
                lobj.put(current, value);
            } else
                lobj = (HashMap) lobj.get(current);
        }
    }

    /**
     * Merge 2 Maps together deeply, merging inner maps and arrays.
     */
    private static HashMap<String, Object> _deepMerge(HashMap<String, Object> defs, HashMap<String, Object> conf) {
        HashMap<String, Object> d = new HashMap<>();
        HashMap<String, Object> c = new HashMap<>();

        for (Map.Entry<String, Object> entry : defs.entrySet()) {
            unpack(d, entry.getKey(), entry.getValue());
        }

        for (Map.Entry<String, Object> entry : conf.entrySet()) {
            unpack(c, entry.getKey(), entry.getValue());
        }

        for (Map.Entry<String, Object> entryC : c.entrySet()) {
            String kC = entryC.getKey();
            Object vC = entryC.getValue();
            if (d.containsKey(kC)) {
                if (vC instanceof HashMap) {
                    if (vC instanceof HashMap) {
                        d.put(kC, _deepMerge((HashMap<String, Object>) d.get(kC), (HashMap<String, Object>) vC));
                    } else {
                        d.put(kC, vC);
                    }
                } else {
                    d.put(kC, vC);
                }
            } else {
                d.put(kC, vC);
            }
        }
        return d;
    }

    /**
     * Deep merge a JSON object and the defaults array
     */
    public static HashMap<String, Object> deepMerge(HashMap<String, Object> def, JsonObject conf) {
        return _deepMerge(def, JSONUtil.jsonToHash(conf));
    }

    /**
     * Get an object from the configuration file. This will also post-process the data. Will return null if it
     * does not yet exist.
     */
    public Object getObject(String name) {
        try {
            String[] keys = name.split("\\.");
            Object mir = full_config;
            for (String key : keys) {
                mir = ((HashMap) mir).get(key);
            }
            return mir;
        } catch (Exception e) { }
        return null;
    }

    /**
     * Convert the entire configuration into a JSON string. This does NOT post-process any information in the
     * configuration file, but will instead return the raw data as a JSONified string. This will also be pretty-printed
     */
    public String toJSON() throws ScriptException {
        return JsonWriter.indent("\t").string().value(full_config).done();
    }

    /**
     * The hash-code of the file of the configuration, preventing duplicates.
     */
    @Override
    public int hashCode() {
        return parent_file.hashCode();
    }

    /**
     * Checks whether a key exists in the configuration file. Alias to a null check on {@link #getObject(String)}
     */
    public boolean has(String name) {
        return getObject(name) != null;
    }

    /**
     * Put a default key in the configuration. This is automatically called in most 'get' methods if the
     * key doesn't already exist in the configuration file.
     */
    public void putDefault(String key, Object value) {
        defaults.put(key, value);
        reload();
    }

    /**
     * Get a key from the config. If the key doesn't already exist, it will be created with the default value in the
     * configuration file.
     */
    public Object getOrDefault(String name, Object def) {
        if (!has(name)) {
            putDefault(name, def);
        }
        Object get = getObject(name);
        return get == null ? def : get;         //Just in case the File Write is locked for some reason
    }

    /**
     * Alias for {@link #getOrDefault(String, Object)}, coercing to a Number
     */
    public Number getNumber(String name, Number def) {
        return (Number) getOrDefault(name, def);
    }

    /**
     * Alias for {@link #getOrDefault(String, Object)}, coercing to an Integer
     */
    public int getInt(String name, int def) {
        return getNumber(name, def).intValue();
    }

    /**
     * Alias for {@link #getOrDefault(String, Object)}, coercing to a Double
     */
    public double getDouble(String name, double def) {
        return getNumber(name, def).doubleValue();
    }

    /**
     * Alias for {@link #getOrDefault(String, Object)}, coercing to a Float
     */
    public float getFloat(String name, float def) {
        return getNumber(name, def).floatValue();
    }

    /**
     * Alias for {@link #getOrDefault(String, Object)}, coercing to a Long
     */
    public long getLong(String name, long def) {
        return getNumber(name, def).longValue();
    }

    /**
     * Alias for {@link #getOrDefault(String, Object)}, coercing to a Byte
     */
    public byte getByte(String name, byte def) {
        return getNumber(name, def).byteValue();
    }

    /**
     * Alias for {@link #getOrDefault(String, Object)}, coercing to a String
     */
    public String getString(String name, String def) {
        return (String) getOrDefault(name, def);
    }

    /**
     * Alias for {@link #getOrDefault(String, Object)}, coercing to a Boolean
     */
    public boolean getBoolean(String name, boolean def) {
        return (boolean) getOrDefault(name, def);
    }

    /**
     * Get an array from the Configuration File
     */
    public <T> T[] getArray(String name, T[] def) {
        Object[] obj = ((Object[]) getOrDefault(name, def));
        T[] objnew = (T[]) Array.newInstance(def.getClass().getComponentType(), obj.length);
        for (int i = 0; i < obj.length; i++)
            objnew[i] = (T) obj[i];
        return objnew;
    }

    /**
     * Alias for {@link #getOrDefault(String, Object)}
     */
    public Object get(String name, Object def) {
        return getOrDefault(name, def);
    }

} public class Profiler {

    public static final Profiler INSTANCE = new Profiler("Main");

    String name;
    Logger log;

    HashMap<String, ProfilerSection> sections = new LinkedHashMap<>();

    /**
     * Create a new Profiler
     *
     * @param name   The name of the profiler. This is used in saving the profiler log to file.
     */
    public Profiler(String name) {
        this.name = name;
    }

    /**
     * Get (or create) a profiler section. This is the subset of the profiler action everything fits under, and is
     * shown in the root of the profiler tree.
     * @param name The name of the section. Short n' sweet
     */
    public ProfilerSection section(String name) {
        if (!sections.containsKey(name)) {
            ProfilerSection sec = new ProfilerSection(name, this);
            sections.put(name, sec);
            return sec;
        }
        return sections.get(name);
    }

    /**
     * @return A list of all the sections currently in the profiler
     */
    public ProfilerSection[] sections() {
        Collection<ProfilerSection> values = sections.values();
        return values.toArray(new ProfilerSection[values.size()]);
    }

    /**
     * The name of the profiler, set in the Constructor.
     */
    public String name() {
        return name;
    }

    /**
     * Export the profiler log to file, using the name of the profiler as the filename.
     * Saved in standard JSON pretty print format
     */
    public void export() {
        export(null);
    }

    /**
     * Export the profiler log to file. Filename format goes #profilername#-#param1#.json
     * Saved in standard JSON pretty print format.
     */
    public void export(String s) {
        String json = JsonWriter.indent("\t").string().value(toJSON()).done();
        try {
            File dir = new File(ToastBootstrap.toastHome, "system/profiler");
            dir.mkdirs();
            File exp = new File(dir, name + ".json");
            if (s == null) {
                if (exp.exists()) {
                    File last = new File(dir.getAbsoluteFile(), name + "_old.json");
                    if (last.exists())
                        last.delete();
                    exp.renameTo(last);
                    exp = new File(dir, name + ".json");
                }
            } else {
                exp = new File(dir, name + "-" + s + ".json");
                if (exp.exists()) exp.delete();
            }
            FileWriter writer = new FileWriter(exp);
            writer.write(json);
            writer.close();
        } catch (Exception e) {
            log.error("Exception whilst exporting profiler log: " + e);
            log.exception(e);
        }
    }

    /**
     * Convert the profiler to a JSON Object. All times are stored in Nanoseconds.
     */
    public JsonObject toJSON() {
        JsonBuilder<JsonObject> obj = JsonObject.builder();

        for (ProfilerSection section : sections()) {
            if (section != null) {
                JsonObject object = section.toJSON();

                if (section.name() != null && object != null)
                    obj.value(section.name(), object);
            }
        }
        return obj.done();
    }

} public class ProfilerSection {

    String name;
    Profiler profiler;
    ProfilerSection parent;

    HashMap<String, ProfilerSection> sections = new LinkedHashMap<>();
    HashMap<String, ProfilerEntity> entities = new LinkedHashMap<>();

    /**
     * Create a new ProfilerSection, with a profiler parent
     * @param name      The name for the section
     * @param parent    The profiler to use as the parent
     */
    public ProfilerSection(String name, Profiler parent) {
        this.name = name;
        this.profiler = parent;
    }

    /**
     * Set this section as a child of the section provided in the parameters
     * @param section       The desired parent section
     */
    protected ProfilerSection setChild(ProfilerSection section) {
        this.parent = section;
        return this;
    }

    /**
     * The name of the section provided in the constructor
     */
    public String name() {
        return name;
    }

    /**
     * Create a new child section. This will automatically add the section as a child. If this section already exists, no
     * new section will be created.
     * @param name  The desired name for the section. Short n' sweet.
     */
    public ProfilerSection section(String name) {
        if (!sections.containsKey(name)) {
            sections.put(name, new ProfilerSection(name, profiler).setChild(this));
        }
        return sections.get(name);
    }

    /**
     * @return A list of all the sections currently in the section
     */
    public ProfilerSection[] sections() {
        Collection<ProfilerSection> values = sections.values();
        return values.toArray(new ProfilerSection[values.size()]);
    }

    /**
     * Start a new ProfilerEntity under this section. An Entity is what actually measures the time between the starting and
     * stopping of the entity. This will be added to the profiler. Alternatively, you can instantiate a new ProfilerEntity
     * yourself and add it with {@link #pushEntity(ProfilerEntity)}
     */
    public void start(String name) {
        if (!entities.containsKey(name)) {
            entities.put(name, new ProfilerEntity(name));
        }
        entities.get(name).start();
    }

    /**
     * Stop a ProfilerEntity that has been added to the section.
     */
    public void stop(String name) {
        entities.get(name).stop();
    }

    /**
     * Push an Entity to the section. This is like {@link #start(String)} and {@link #stop(String)} methods, but for Entities
     * that you have created yourself.
     */
    public void pushEntity(ProfilerEntity entity) {
        entities.put(entity.name(), entity);
    }

    /**
     * Get a ProfilerEntity that is currently in the section
     */
    public ProfilerEntity entity(String name) {
        if (!entities.containsKey(name)) {
            entities.put(name, new ProfilerEntity(name));
        }
        return entities.get(name);
    }

    /**
     * @return A list of all the entities currently in the section
     */
    public ProfilerEntity[] entities() {
        Collection<ProfilerEntity> values = entities.values();
        return values.toArray(new ProfilerEntity[values.size()]);
    }

    /**
     * Get the total time of the section. This is the sum of all the entities and sections currently in this section.
     * This value is measured in Nanoseconds.
     */
    public long totalTime() {
        long time = 0;
        for (ProfilerSection sec : sections()) {
            time += sec.totalTime();
        }
        for (ProfilerEntity ent : entities()) {
            time += ent.getDuration();
        }
        return time;
    }

    /**
     * Get the total time of the section. This is the sum of all the entities and sections currently in this section.
     * This value is measured in Milliseconds.
     */
    public long totalTimeMS() {
        return totalTime() / 1000000;
    }

    /**
     * Get the total time of the section. This is the sum of all the entities and sections currently in this section.
     * This value is measured in Seconds.
     */
    public long totalTimeS() {
        return totalTimeMS() / 1000;
    }

    /**
     * Convert the section to a JSON Object. All times are stored in Nanoseconds.
     */
    public JsonObject toJSON() {
        JsonBuilder<JsonObject> obj = JsonObject.builder();

        for (ProfilerSection section : sections()) {
            if (section != null) {
                JsonObject object = section.toJSON();

                if (section.name() != null && object != null)
                    obj.value(section.name(), object);
            }
        }
        for (ProfilerEntity entity : entities()) {
            if (entity != null && entity.name() != null)
                obj.value(entity.name(), entity.getDuration());
        }
        return obj.done();
    }

} public enum LoadPhase {
    /**
     * The Bootstrap Phase is where the Robot is first started. This handles parsing of command line arguments, as well
     * as setting up things such as the Logger, Global Blackboard, as well as creating directories.
     */
    BOOTSTRAP("Bootstrap"),

    /**
     * Called when the Core Modules are starting to be loaded into the classpath. This is between the absolute necessities
     * of Toast being loaded, and the Pre-WPILib State of Toast's Loading.
     */
    CORE_PREINIT("Core-Pre-Initialization"),

    /**
     * Called when all the Core Modules are loaded into the classpath, instantiated and are now doing their post-loading
     * process. All core modules will be loaded before this step, meaning Core Modules can now use their dependencies.
     */
    CORE_INIT("Core-Initialization"),

    /**
     * The Pre-Initialization phase is used to instantiate classes and objects. This is run before any external modules
     * or files are loaded so that no NullPointers are triggered when trying to access static variables
     */
    PRE_INIT("Pre-Initialization"),

    /**
     * The initialization Phase is used to load WPILib. Everything that happens in this phase is a result of WPILib's
     * {@link edu.wpi.first.wpilibj.RobotBase} class and is out of our control. Keep in mind during Simulation and
     * Verification, this class is Patched.
     */
    INIT("Initialization"),

    /**
     * Prestart is a standard phase of WPILib, and is where the robot is "getting ready". This is where modules are
     * loaded and things like the CommandBus instantiated.
     */
    PRE_START("Pre-Start"),

    /**
     * The Start Phase is another feature of WPILib. This is where the robot is seen as "Ready To Go". For our purposes,
     * we use this to start tools like the PDP Poll and SocketManagers.
     */
    START("Start"),

    /**
     * The Complete Phase is instantiated when all other phases are done. The robot is now fully ready to go.
     */
    COMPLETE("Main");

    String threadName;
    public static LoadPhase currentPhase;

    LoadPhase(String threadName) {
        this.threadName = threadName;
    }

    /**
     * Get the localized, human-readable name of the LoadPhase.
     */
    public String getLocalized() {
        return threadName;
    }

    /**
     * Transition into the given load phase. This method changes the current Thread Name, sets the Current Phase and
     * updates any callbacks registered
     */
    public void transition() {
        Thread.currentThread().setName(threadName);
        currentPhase = this;
        for (Function<LoadPhase, Void> callback : callbacks)
            callback.apply(this);

    }

    /**
     * Register a callback on the LoadPhase callback handler system. A callback is called when the Robot undergoes a change
     * of LoadPhase. The intended use for this is with CoreModules, which may choose to use the LoadPhase system to trigger
     * Indicator LEDs, or certain methods where a method hook is not provided.
     */
    public static void addCallback(Function<LoadPhase, Void> callback) {
        callbacks.add(callback);
    }

    static Vector<Function<LoadPhase, Void>> callbacks = new Vector<>();
}
import edu.wpi.first.wpilibj.RobotBase;
import edu.wpi.first.wpilibj.hal.FRCNetComm;
import edu.wpi.first.wpilibj.hal.HAL;
import edu.wpi.first.wpilibj.hal.HALUtil;
import jaci.openrio.toast.core.io.usb.USBMassStorage;
import jaci.openrio.toast.core.loader.ClassPatcher;
import jaci.openrio.toast.core.loader.RobotLoader;
import jaci.openrio.toast.core.loader.simulation.DriverStationCommunications;
import jaci.openrio.toast.core.loader.simulation.SimulationGUI;
import jaci.openrio.toast.core.network.ToastSessionJoiner;
import jaci.openrio.toast.core.script.js.JavaScript;
import jaci.openrio.toast.core.security.ToastSecurityManager;
import jaci.openrio.toast.core.shared.GlobalBlackboard;
import jaci.openrio.toast.core.shared.OptionParser;
import jaci.openrio.toast.core.thread.Async;
import jaci.openrio.toast.lib.Assets;
import jaci.openrio.toast.lib.Version;
import jaci.openrio.toast.lib.crash.CrashHandler;
import jaci.openrio.toast.lib.log.Logger;
import jaci.openrio.toast.lib.log.SysLogProxy;
import jaci.openrio.toast.lib.module.ModuleConfig;
import jaci.openrio.toast.lib.profiler.Profiler;
import jaci.openrio.toast.lib.profiler.ProfilerSection;
import jaci.openrio.toast.lib.state.LoadPhase;
import jaci.openrio.toast.lib.state.RobotState;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
            try {
                String component = list.get(0);
                if (!component.equals(".")) {
                    if (component.equalsIgnoreCase("--search")) {
                        RobotLoader.search = true;
                    } else RobotLoader.manualLoadedClasses.add(component);
                }
            } catch (Exception e) { }
        });

        parser.on("-vf", "-verify", "Start a Headless Verification", 0, list -> {
            isSimulation = true;
            isVerification = true;
        });

        parser.on("-core", "Load a Core Module class", 1, list -> {
            try {
                String component = list.get(0);
                if (!component.equals(".")) {
                    RobotLoader.coreClasses.add(component);
                    RobotLoader.manualLoadedClasses.add(component);
                }
            } catch (Exception e) { }
        });

        parser.on("--color", "Enable Color Output", 0, list -> { color = true; });

        parser.on("-ide", "Set the IDE you are currently using. This will be either ECLIPSE or IDEA, usually.", 1, list -> {
            try {
                String component = list.get(0);
                if (component.equalsIgnoreCase("IDEA")) color = true;
            } catch (Exception e) {  }
        });

        parser.on("-j", "--join", "Join a currently running Toast Logging session", 0, list -> {
            ToastSessionJoiner.init();
            System.exit(0);
        });

        parser.on("-nogui", "--headless", "Run the Simulation Headless (without a GUI)", 0, list -> {
            isHeadless = true;
        });

        parser.on("--stub", "Exit the program immediately. This is mostly used to profile memory usage over time.", 0, list -> {
            compareStub = true;
        });

        parser.on("-d", "--debug", "Launch in Debug Mode. More logging output and exception printing to aid in debugging issues.", 0, list -> {
            debug_logging = true;
            exception_info_logging = true;
        });

        parser.on("--MOAR", "Launch in Debug Mode. More logging output and exception printing to aid in debugging issues.", 0, list -> {
            debug_logging = true;
            exception_info_logging = true;
        });

        parser.on("-e", "--exceptions", "Debug Mode for Runtime Exceptions. More exception printing to aid in debugging issues", 0, list -> {
            exception_info_logging = true;
        });

        parser.parse(args);
        profiler.stop("ParseArgs");

        if (compareStub) {
            System.out.println("Immediately exiting -- we've been told to exit before initialization for memory and utilization measurement purposes. Type something to end the program.");
            try {
                System.in.read();
            } catch (IOException e) {
                return;
            }
            return;
        }

        if (isSimulation) {
            toastHome = new File("toast/").getAbsoluteFile();
            robotHome = new File("./").getAbsoluteFile();
        }
        toastHome.mkdirs();

        LoadPhase.BOOTSTRAP.transition();

        profiler.start("Logger");
        SysLogProxy.init();
        profiler.stop("Logger");
        profiler.start("Crash");
        CrashHandler.init();
        profiler.stop("Crash");

        profiler.start("Misc");
        System.out.println(Assets.getAscii("splash"));
        toastLogger = new Logger("Toast", Logger.ATTR_DEFAULT);
        new GlobalBlackboard();
        GlobalBlackboard.INSTANCE.put("runtime_args", args);
        profiler.stop("Misc");

        try {
            Thread.sleep(20);       // To avoid splash screen flush racing
        } catch (InterruptedException e) { }

        profiler.start("Version");
        Version.init();
        profiler.stop("Version");

        // -------- NEW PHASE -------- //
        LoadPhase.CORE_PREINIT.transition();
        RobotLoader.preinit(Profiler.INSTANCE.section("RobotLoader"));

        // -------- NEW PHASE -------- //
        LoadPhase.CORE_INIT.transition();
        RobotLoader.initCore(Profiler.INSTANCE.section("RobotLoader"));

        if (args.length > 0)
            toastLogger.info("Toast Started with Run Arguments: " + Arrays.toString(args));

        ModuleConfig.init();

        // -------- NEW PHASE -------- //
        LoadPhase.PRE_INIT.transition();
        toastLogger.info("Slicing Loaf...");
        USBMassStorage.init();