var PE_BENCH = (function()
{
    ////////////////////////////////////////////////////////////////////////////////////////////////
    //                                           PRIVATE                                          //
    //@include "./handler_aep.jsx"
    //@include "./loop.jsx"

    /**
     * The Folder Object attached to the extension root folder location
     * @type {Folder}
     * @private
     */
    var extensionRoot = new Folder();
  



    ////////////////////////////////////////////////////////////////////////////////////////////////
    //                                           PUBLIC                                           //

    /**
     * Set the private extensionRoot variable based on the URI path of this extensions, "index.html"
     * @public
     * @param {string} indexPath The URI encoded path to "index.html", must be an absolute URI path i.e
     * * Windows: "/W/PluginEverything/009_FileHunter/00_Dev/src/index.html"
     * * MacOS:   "/Volumes/Main/Work/PluginEverything/009_FileHunter/00_Dev/src/index.html"
     */
    function setRoot(indexPath) 
    {
        // Since indexPath is an absolute URI it must start with "/"
        if (indexPath[0] !== "/") {
            alert("AE BENCHMARK ERROR\nindexPath does not start with a forward slash, cannot continue\n" + indexPath);
            return;
        }

        // Checking to make sure the path leads to an existing and valid file
        var indexFile = new File(indexPath);
        if (!indexFile.exists) {                     // If it doesn't, something has gone wrong
            alert("AE BENCHMARK ERROR\nInvalid indexPath\n" + indexPath + "\n" + String(indexFile));
            return;
        }

        // If everything is ok then we set extensionRoot to the parent folder of indexFile
        extensionRoot = indexFile.parent;
    }

    /**
     * Gets the specifications of the local machine and returns them as an array
     * @public
     * @returns {array}
     */
    function getSpecs()
    {
        // Checking to make sure the extensionRoot has been set
        if(extensionRoot.fsName === "") {
            alert("Error\nextensionRoot has not been set, cannot continue");
            return ["ERROR"];
        }
        
        // Deriving the library path in URI notation from the extensionRoot
        var libPath = extensionRoot.absoluteURI + "/cpp/SysSpecs";

        // Getting the path of the library with the specific extension for the platform
        // This is just for checking if it exists
        var osLibPath = libPath + ((File.fs === "Windows") ? ".dll" : ".framework")

        // Checking to make sure the library path is valid, if not we exit
        if(!File(osLibPath).exists) {
            alert("Error\nUnable to load external object, not found at path:\n" + osLibPath);
            return ["ERROR"];
        }
        
        // Loading the library and running it
        var lib = new ExternalObject("lib:" + File(libPath).fsName);  // URI converted to platform respective path
        var specs = lib.getSpecs();
        lib.unload();

        var version = app.version.substr(0, app.version.indexOf("x"));
        version = newVersion(version);

        function newVersion(v)
        {
            var num = Number(v.substr(0, v.indexOf(".")));

            if(num >= 17)
            {return "2020"}

            if(num >= 16)
            {return "CC 2019"}

            if(num >= 15)
            {return "CC 2018"}

            if(num >= 14)
            {return "CC 2017"}

            return "UNKOWN"
        }

        specs = specs + "," + version;

        return specs;
    }


    /**
     * Checks the requirements to make sure the app is valid to run and returns the results 
     * Currently only checks if the Benchmark plugin is installed
     * @public
     * @returns {boolean[]}
     */
    function checkRequirements()
    {
        var requirements = [false];

        // Checking if the effect is installed
        for(var i = 0; i < app.effects.length; i++)
        {
            if(app.effects[i].matchName == "PEBM") { 
                requirements[0] = true;
                break;
            }
        }        
             
        return requirements;
    }


    /**
     * Is pinged from the CEP Panel to make sure the connection is working
     * @public
     */
    function ping()
    {
        return;
    }


    /**
     * Runs the test for each requirement, Singlethreading, Multithreading and GPU  
     * @param {boolean} debug Specifies whether we're running in debug mode or not
     * @returns {number[] | ["ERROR"]} Returns the results as an array, or an "ERROR" if something went wrong
     */
    function main(debug)
    {   
        try {
            // Purge all cache to prevent previews affecting render time
            app.purge(PurgeTarget.ALL_CACHES);

            // Load the project, and get the application preferences object
            var projectfile = loadProject(), 
                pref = app.preferences;

            // Checking if the project opened correctly
            if(app.project.file.fsName != projectfile.fsName) {
                alert("ERROR\nue to an unkown error benchmark was unable to load. Sorry, please try again");
                return ["ERROR"];
            }

            // HANDLING PREFERENCES
            // Since preferences can be iffy in AE, we need to check if the preferences exist
            var hasDiskCache = pref.havePref("Disk Cache Controls", "Enabled 2"),
                hasPlaySound = pref.havePref("Misc Section", "Play sound when render finishes", PREFType.PREF_Type_MACHINE_INDEPENDENT),
                hasAutoSave = pref.havePref("Auto Save", "Enable Auto Save RQ2", PREFType.PREF_Type_MACHINE_INDEPENDENT);

            // Getting the existing preferences, or a default option
            var diskCache = hasDiskCache ? pref.getPrefAsString("Disk Cache Controls", "Enabled 2") : "1";
            var playSound = hasPlaySound ? pref.getPrefAsString("Misc Section", "Play sound when render finishes", PREFType.PREF_Type_MACHINE_INDEPENDENT) : "1";
            var autosave = hasAutoSave ? pref.getPrefAsString("Auto Save", "Enable Auto Save RQ2", PREFType.PREF_Type_MACHINE_INDEPENDENT) : "1";

            pref.savePrefAsString("Disk Cache Conrols", "Enabled 2", "0");
            pref.savePrefAsString("Misc Section", "Play sound when render finishes", "0", PREFType.PREF_Type_MACHINE_INDEPENDENT);
            pref.savePrefAsString("Auto Save", "Enable Auto Save RQ2", "0", PREFType.PREF_Type_MACHINE_INDEPENDENT);


            // Here we're mapping out the project structure
            /** @type {CompItem} */ ///@ts-ignore
            var cpuComp = app.project.item(1),
                // gpuComp = app.project.item(2),
                cpuRenderItem = app.project.renderQueue.item(1),
                // gpuRenderItem = app.project.renderQueue.item(2),
                cpuNoise = cpuComp.layer(1).effect(1),
                // gpuNoise = gpuComp.layer(1).effect(1),
                cpuBenchmarkEff = cpuComp.layer(1).effect(2);


            // Now that everything is ready to go, we need to get the actual score
            // To do this we perform a while loop for 1 minute, rendering as many frames as we can
            var scores = []; 

            /**
             * Ok so some quick exposition
             * property(1) of cpuBenchmarkEff is it's mode, we set it to single or multi threading
             * for the CPU tests. 
             * The score function performs a loop rendering as many frames as it can in 60 seconds, 
             * the thing is that unless a value is changed in between frames then AE will cache it
             * even if you tell it not to and clear the cache. 
             * The fix this we have a noise effect in each comp that we want to alter, we pass the 
             * property of this noise effect to the scores function. 
             * In the case of the CPU tests it's the "Amount of Noise" property of a noise effect.
             * In the GPU test it's the "Evolution" property of the Fractal Noise 
             * Finally we push the scores we receive to our scores array and return it. 
             */
                        
            // Singlethreading
            cpuBenchmarkEff.property(1).setValue(1);
            scores.push(score(cpuNoise.property(1), 100, cpuRenderItem));

            // Multithreading
            cpuBenchmarkEff.property(1).setValue(2);
            scores.push(score(cpuNoise.property(1), 100, cpuRenderItem));

            // GPU
            cpuBenchmarkEff.property(1).setValue(3);
            scores.push(score(cpuNoise.property(1), 100, cpuRenderItem));

            // scores.push(score(gpuNoise.property(24), 5000, gpuRenderItem));

            // And finally return the scores
            return scores;
        }
        
        // Catching the error for debugging purposes
        catch (err) {
            // Stop supressing dialogs (Warnings)
            app.endSuppressDialogs(false);

            // Creating an error message with extra information for the user
            var message = "ERROR\n"             // We start with a header
            for(var key in err) {               // Then we loop through each key in the error
                if(key == "source") { continue; }   // If it's source then we skip

                // We add the key and the value to the string
                message += String(key) + ": " + String(err[key]);
                message += "\n";                    // Then a new line
            }

            alert(message);                     // Finally we alert the new error message to the user 
            return ["ERROR"];                   // And return an error indicator the panel
        }

        // We always want to perform cleanup, even if an error occurs
        finally {
            // Stop supressing dialogs (warnings)
            app.endSuppressDialogs(false);

            // Cleanup
            projectfile.remove();
            File("~/aebenchmark_0000.exr").remove();

            // Put the settings back the way they where before
            pref.savePrefAsString("Disk Cache Controls", "Enabled 2", diskCache);
            pref.savePrefAsString("Misc Section", "Play sound when render finishes", playSound, PREFType.PREF_Type_MACHINE_INDEPENDENT);
            pref.savePrefAsString("Auto Save", "Enable Auto Save RQ2", autosave, PREFType.PREF_Type_MACHINE_INDEPENDENT);

            // Purge the cache and memory
            app.purge(PurgeTarget.ALL_CACHES);

            // Close the project file without saving changes
            app.project.close(CloseOptions.DO_NOT_SAVE_CHANGES);
        }
    }
  



    ////////////////////////////////////////////////////////////////////////////////////////////////
    //                                           EXPORTS                                          //
    return {
        setRoot: setRoot,
        getSpecs: getSpecs,
        checkRequirements: checkRequirements,
        ping: ping,
        main: main
    };
})()
