Source

DuSan_api.jsxinc


// ==================== |-----| ====================
// ==================== | api | ====================
// ==================== |-----| ====================

// API

// ==================== |-------------| ====================
// ==================== | api_version | ====================
// ==================== |-------------| ====================

/**
 * <h3>Sanity tests for After Effects</h3>
 * <p>DuSan requires <i>DuAEF</i>, the <i>Duduf After Effects Framework</i>. Two builds of the <i>DuSan API</i> are available:<br />
 * <ul><li><code>DuSan_api.jsxinc</code> does not include <i>DuAEF</i>, and can be used to compine multiple <i>Duduf APIs</i> with a single copy of <i>DuAEF</i>.<br />
 * Be careful to grab the right version of <i>DuAEF</i> in this case.</li>
 * <li><code>DuAEF_DuSan_api.jsxinc</code> includes all dependencies, with <i>DuAEF</i>, and is easier to include in your scripts.</li></ul></p>
 * @namespace
 * @author Nicolas Dufresne and contributors
 * @copyright 2022 Nicolas Dufresne, RxLaboratory
 * @version 2.0.6-dev
 * @requires DuAEF>=1.0.0
 * @license GPL-3.0 <br />
 * DuSan is free software: you can redistribute it and/or modify<br />
 * it under the terms of the GNU General Public License as published by<br />
 * the Free Software Foundation, either version 3 of the License, or<br />
 * (at your option) any later version.<br />
 *<br />
 * DuSan is distributed in the hope that it will be useful,<br />
 * but WITHOUT ANY WARRANTY; without even the implied warranty of<br />
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the<br />
 * GNU General Public License for more details.<br />
 *<br />
 * You should have received a copy of the GNU General Public License<br />
 * along with DuSan. If not, see {@link http://www.gnu.org/licenses/}.
 * @category DuSanity
 */
var DuSanity = {}


// ==================== |-------| ====================
// ==================== | icons | ====================
// ==================== |-------| ====================


// ==================== |-----------| ====================
// ==================== | w12_check | ====================
// ==================== |-----------| ====================

var w12_check = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\f\x00\x00\x00\f\b\x06\x00\x00\x00Vu\\\u00E7\x00\x00\x00\tpHYs\x00\x00\x05\u0089\x00\x00\x05\u0089\x01mh\u009D\u00FA\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x01;IDAT(\u0091}\u00CF;KCA\x10\u0086\u00E1wv\u00D7\u00A4\u00B0\x10T\x14+\u008Bt^j\u00AD\u00C5\u00C6\u00C2\u0080\u009D\u00A0\u0085U\b\u00EAo\u0090t\u00FE\u0089\x18Q\x14\u0084\u00ED\x04\x0BAP\x10/\u00C5Q\x0BE{\t\x16V\u00D9\u0080 \u00E6\u009CY\u009B\x13\u0090\u00A0~\u00DD\u00CC<30B\u009EJ\u00A5\u00D2\x17c\\\u008B1\u00AE\x02\x13\u0080\x01^\u0080\u00C3\x10\u00C2\u008E\u00F7\u00FE\x0B@\x00\u00AA\u00D5\u00EAH\u00A7\u00D39\x16\u0091\x19~\u00CF\u00BD\u00AA.6\x1A\u008D7\u00A9\u00D5j\u00AE\u00D9l^\x02\u00B3\x7F\u00E0n\x1EB\b\u00B3\u00B6T*\u00AD\u0089\u00C8\u00C6?0\x01\u00DA\u00C0t\u00B1X|7\"\u00B2\u00F2\x0F\u00BER\u00D59U]\x07\x10\u0091\x15\u0093?\b\u00F0\b\u009C\u00F6\u00E0\u0085B\u00A10d\u008C\u00D9\u00CD{\u0093\x0E\u0088\x001\u00C6-k\u00ED\u0089\u00AA\x1E\x01\u00A3]\u009C\u00A6\u00E9\x050\u00DE\u00BD\u00E2\u0080g`LDv\u00B2,\u009B\u00B7\u00D6.;\u00E7\u008A\u00AA:\u00DC\u008B\u0081'\x03\x1C\u00E6\u00C5\u00A0\u0088\u009CeY6\u00F5\x07FD\x0EL\ba\x1F\u00B8\u00FE\u00B1t\u009E\u00A6\u00E9m/\x06\u00EEZ\u00ADV\u00DDx\u00EF3\u00E7\u00DC\x12p\u0093\x0F\x06\u0080\u00D1\x1E\u009C\u00A8j\u00D9{\u00FFe\x01\u0092$\u00F9(\u0097\u00CB{\u00EDv\u00FB5_\u00E8\x07>\u0081\u00BB\x18\u00E3\u00B6\u00B5v\u00B3^\u00AF\x07\u0080o\u008E\u00F9\u008B\t\x16\x10\u00F0~\x00\x00\x00\x00IEND\u00AEB`\u0082", "w12_check.png", "icons" );
w12_check;

// ==================== |-------------| ====================
// ==================== | w12_check_g | ====================
// ==================== |-------------| ====================

var w12_check_g = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\f\x00\x00\x00\f\b\x06\x00\x00\x00Vu\\\u00E7\x00\x00\x00\tpHYs\x00\x00\x05\u0089\x00\x00\x05\u0089\x01mh\u009D\u00FA\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x01FIDAT(\u0091}\u00D2\u00BF+\u00C4q\x1C\u00C7\u00F1\u00E7\u00EB\u00E3\u00B8\u00FC\u00BA\u00F3+b\u00B1\u00CAY\u0094\u00A2\x1BM\f\u0094E\u00CA\u00CF\u0089Rf\u009B\u00BE\u00BB?@\x06,n\u00B9\u00C1`S\u00EAD\u00B2\x1C\x06\"e\u00B1\u00B0\x11\u00CEq\u00A7\u00EF\u00F7m\u00F0\u00BD\x12\u0097\u00D7\u00F6y\u00BD\x1F\u00BD\u0087w\x1F\x11f=\u00BB^\u00F9R\u00FF>\x17\x04\u009A\u0092\u00E8\x06\x1C\u00D85\u00B8T\u00DE\x7F\u00DC\u00F0\x12^\x11@\x00\u00AB\u00B7k\u00AD\u00F6Y\u00DCE\u00F4S6:34\u00B2\u00DC\u00B5t//\u00E3Ej:\x1A\u008F0\x06\u00CA\u00E3\u00EF\u0098q\u00FE\x1E<\rDj\u00DA\u009Bf0\u00FB\x07[\x16T'\u00D1[\u00ED\x1A\u00E6\x1D\x04\u0093\u00FF\u00E0c\u00A30h\u00B0\b \u00A7I\x07\u00EA\x0E\u00A7\x17B{\u00BF\u00F0p`U\u00CD\u0082\u00AD\u00EF\u008A\u008430\x00S\u00B0\x12\u00CBEG$\u00DB\u00F9\u0089+\u00E4\x0E\u0080\u00CE\u00D2\x1A'\u00B8\x02\u0090\u00B9\u008D\\\u00BC\u00D0\x13{\u00AD\u009E\u00C8\u00FB\u0095C\u00E50\u00E8\u00D2\x19\u00A4\u00C2W\u0093\u00EF\u00DB~.^\u00E8\u0089\u00BAb\u00CB_\f\u00A6`[iKW\u00DC\u00DD<\x1C\x1A$\u00C3\u00FE\x19\u00F1\u0081\u00D1\u00F6\u00EB\x02\u00A7y\u00FF)\u00E9\u00C65\u00EE\x13\u00A9\x1A\x03\u009D\u0084\u0083\u00F8_lY\u00C3\u008Dz\t\u00AF\u00A8R\u00E5e\u00BCHm{\u00C3\u00AC\u00A1i(}\r\u00AEdJ\u00C5\u00DE\u00A2\u009B\x0B}\x0B\u009F\x00_\x01#\u0080\u00CF\u00FC\u0086\u00D2\u00B1\x00\x00\x00\x00IEND\u00AEB`\u0082", "w12_check_g.png", "icons" );
w12_check_g;

// ==================== |--------------| ====================
// ==================== | w12_critical | ====================
// ==================== |--------------| ====================

var w12_critical = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\f\x00\x00\x00\f\b\x06\x00\x00\x00Vu\\\u00E7\x00\x00\x00\tpHYs\x00\x00\x05\u0089\x00\x00\x05\u0089\x01mh\u009D\u00FA\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x01\x03IDAT(\u0091m\u008F\u00B1J\x03Q\x10E\u00CFl^\x04\x15D0\u00F86/F[k\u00C1B\u008C\u009D\u00DF\u00A0\x06\u00EC\u00F4\x0F\u0084\u00F8\x0B\x16~\u0082\u009D\u0085~B\u00FEA\u00B0\u00B6U!,\u00B8\u00BB\u008A\u0085\u0085d\u00DFX$\u00CA\u00EE\u00F3M3\u00CC\u00BDs\u00EF\u00DC\u0081H\u00BDu:.\u00B3v=\u00C6\u0099\x10P\u0090\u00D2\u0098{\x01\u00A3\u00B0/\u00A0u>\t\x05\u00B9\u00B5C\u0085\x03`/\u00B7\u00F6$\u00E4\u00A5>L\u009C[Z\u00F0\u00FE\t\u00B8\u009BC\u00C3\u00EF$\u00D9v\u0093\u00C9W\u00F4B\u00DB\u00FBK\u00A0\u00AF0V\x18\x03\u00FD\u00B6\u00F7\u00A3h\u00A4\u00A2\u00D7\u00DB\x10\u00B8\u0088D\x18\u00BDw\u00BB[\u00FF\x04\u00BE\u00AA\u00AE\u0081\u00E5P\x00,z\u00D5\u00AB\u0086\u00A0tn p\u00F4\u00E7\u00AA\u00DA\x12\u00D5\u00D6\u00EF\u00ACp\\:7\u0098q\u0090\x14i\u00FA\x00\u00EC\u00D4\\_\u00E6}\u00B3\u0086=\u00AEe\u00D9\u00AE\x14\u00D6\u009E\u00A9\u00C8M=\u0083\u008A\u009C\n\b\u00AA\u00B7\u008D\x7FT\u00CF%O\u00D3\u00E7\u00C0\tT?f\x1B\u00B2\x1A\u00FC\u00F3j|U\x1D\u008A1+\rX\u0084X\u00E9t\u00FA\u00F9\x03\u0081\u008EM\u00EC\x18N\u00BBB\x00\x00\x00\x00IEND\u00AEB`\u0082", "w12_critical.png", "icons" );
w12_critical;

// ==================== |-----------| ====================
// ==================== | w12_fatal | ====================
// ==================== |-----------| ====================

var w12_fatal = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\f\x00\x00\x00\f\b\x06\x00\x00\x00Vu\\\u00E7\x00\x00\x00\tpHYs\x00\x00\x05\u0089\x00\x00\x05\u0089\x01mh\u009D\u00FA\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x00\u00F7IDAT(\u0091\u008D\u008F\u00B1N\x02\x01\x10D\u00DFl\x0ECaa\u00E1'\u0098\u00F3\x1B\u008C\u00A7\t\u0089\u0095\r\u0095\u00C6 ~\x04\u009DFCB\u00FC\x06\x1Bb\t\u009D\u0091\u0086\u00C4\u0082\u00C2\u00C2\u008B\u00BD\u0085\t\x02\x7F \u0085\u008D\x16\u00A2\u00B7\x16x\x06\u00BD\u00C30\u00D5\u00EE\u00EC\u00EC\u00CC\u00AE\u00DA\u00EBq\t|\u0095\u0085\u00A0q\u0080\u00D3\x04\u00AD\u00CD\u00D2\u008E\u00DFK\x12\u00CE\u00C6/\u00BDkh.\u009Dd|\u00A43\u00E04\u009B\u00E0\u00C7V}\u00DA\u00BC\x02z\x0B\u00DCs{8\u008C\u00AE\r@\t5\u00E0\u00E3\x1F\u00F1g\u0082\u00D7\x00\f\u00A02\u008A\x1Eq.\u00E7\u00CA\u009D\u00E6\u00D1`\u00EB\x01 H\u00B9\u0082'\u00F5\u0089\u00EC\x00X\u00C19\u009F\u0091\u00BFH\u0093z\u00DAXZ\u00EC\u008F\u00B6\u009F]4\u00A6\u0086\u00DEq\u00BC\x03\u00E0\u00A2Q\x19\u0094\u00C6\u0099\x05\u0080\u00D7\u00E5\u00E2\x05\u00D07T6T\x06\u00FA\u00DF\u00DC\x0F\u00F4\u00F7\u00DCvx\u00B7\x03\u00EAM\u0093\u00D8\u00AD\x0E\u00A2\u009B\u00B9\u00BF\u00A5h\u0085q\u00B7\x15\u00C6\u00DD\u00BCY\u0090G.\u00BD\u00D9\u00DE{\u00D1\ny\u00B3/\u00ECZJ\x18\u00D9\u009A'\x17\x00\x00\x00\x00IEND\u00AEB`\u0082", "w12_fatal.png", "icons" );
w12_fatal;

// ==================== |-----------------| ====================
// ==================== | w12_information | ====================
// ==================== |-----------------| ====================

var w12_information = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\f\x00\x00\x00\f\b\x06\x00\x00\x00Vu\\\u00E7\x00\x00\x00\tpHYs\x00\x00\x05\u0089\x00\x00\x05\u0089\x01mh\u009D\u00FA\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x01\"IDAT(\u0091\u008D\u0092;NBa\x10\u0085\u00BF\u00F3\u00FF\x17\x03X\u00AB[\u00E0\u00D5\u0098X`\u00E9\x024\u00C6\u0082\x061\u00B1\u00D1m\u00DC-hE\u00D4X\u0081\x056\u00BA\x02\x0B\u00EC4\x1A\x13Q\u00D8\u0080\x1D\u0095\t\x10\u00E1\u00DE\u00B1\u00F0\x06\u0085h\u00E2iNr\x1E\u0093\u00C9dD\u0082\u00F0\u00C6\x02\u00BF4\u00D8\u00C7\u00B4\x0B\x14\x00\x07\u00BC\u009A\u00EC\"V\u00F6,,\u00EA\x03@\x00\u00E1\u00D3\u00FB\u00B2w\u00FE\x1A('\u00FD\u00AB\u0084\u00B7\x13~\u0088R\u00B6\x19\u00E6\x16\u00DF\u00D4j\u0099\u00EF\u00E5\u0087m`=1\u00C7Q)\u0093.\\\u00A2^~8\x02\x02\x00\u008C\u00C7\u00C8g\u00CAA77\u00AA\u00E9;\f\u0090\u00F2\u00CF\u0083\u0093^\x1E@\u00C1T\x15\u00AB.\x1A\x1E\x04R\\M6\u009B\u00C2L}\x00\u00CD\u00CAHT\x1D\u00A8\u00C8\x1C\\\u00CCq\u00BC`G\u00F3:Pr\x18\u00F6\u008B\u00F1\x07,v\u0092u\u00FE_P\u00C7\u00C5r\u008D\x7F\u00CF\u00975\\\u00EE%\u00DDD\u00B4g\x1C\u008F\x0B\u00C6\u00F2s\u00F9\u00FB\u0095Q\u00F6\u00D4U*\u008A\u00A2\u00D4d\x07\u00B8\u009DNBuC\u00F5\x1F\u00E1;\u00E7m\u00EBpM\u00E3\u00E9\u00E1Z-\u00F3\u00DD\u00DC\u00A8\u00E6d{\u00F6\u00F5\x1A\x02\u00BAf\u00D6\u008C\u00FB\u00D9\u00F3pC\x13\u0080O\u00DA\u00B9a\u00B0+\x13\u00D1O\x00\x00\x00\x00IEND\u00AEB`\u0082", "w12_information.png", "icons" );
w12_information;

// ==================== |-------------| ====================
// ==================== | w12_warning | ====================
// ==================== |-------------| ====================

var w12_warning = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\f\x00\x00\x00\f\b\x06\x00\x00\x00Vu\\\u00E7\x00\x00\x00\tpHYs\x00\x00\x05\u0089\x00\x00\x05\u0089\x01mh\u009D\u00FA\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x01\x05IDAT(\u0091\u008D\u008F\u00BF/\x03q\x18\u00C6?\u00CF7W\u008B\u00CD\u00A1\u00ADYB\u00B4\x06\x12!$&\u00B1\x19\u00ACr1\u00E1_\u00F1?\u0088A\u009A\u00F6\x0F\u0090\u00D8\f$\x12]\u00DAI\u00DA\x1A\u00BA;\u00EAl\x16\u00CE\u00BD\x06\x17\u00B9\u00D3J\u00FA\u008C\u00CF\u00FB>\u00BFD\n\u00BB\u00C1{+\x17\x0F\u00CD\x14\x00\u008B)\u00DD\u0093\u00A8O-\u00845\u0089/\x00\x01\u0084\u00FD\u00E2\u00AC\u00F7\u00A9K`\u0083\u0091\u00D0}\\H\u00F6K\u00F3\u00CF/\u00B2\x16\u0085h\u00B2t\x07\u00ACg\x1E\u00AE!1\u00D0nF\u00D5\u00F4\u00C3p[\u0083^\u00F9X\u00D8Y\u00CE0\u00B1\x1Ds\u008A\x05\u00B7\u00B9\x1C\u00B3#\u00E7\u00B0`t\u008Da\u0098\x148\u0083\u00EA\u00D8\x02\u00A8:\u00D2\u00E1\u00E3@ \u00CF\u00B0\u00AE\u00D0V\u00EE\u00E2t*H\u0086\x04\u00A2\u00EB$5F\u0098\u00D5L\u00BA\u00F8K&\u00A6\u0086\u00ACS\u0099\u0088\\\u00D4\x04V3\u00B76?\tk\x19\u00AE\u00E5\u00BF\u0087\u009B\x02\x18<N\u00CF\u00C9\u00BC+`\u00E5\u009F\u00FAm\u00B3xof\u00E9\u00F5\u00E9wp\u009At\x02\x1C\x00\u00CB)\u00FD\u0080Q\u00F7\u00CD?W\u00A5\u00F3\x01\u00F0\r\u00D4FV\u00F60\u00B8l\u00C2\x00\x00\x00\x00IEND\u00AEB`\u0082", "w12_warning.png", "icons" );
w12_warning;

// ==================== |------------| ====================
// ==================== | w12_danger | ====================
// ==================== |------------| ====================

var w12_danger = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\f\x00\x00\x00\f\b\x06\x00\x00\x00Vu\\\u00E7\x00\x00\x00\tpHYs\x00\x00\x05\u0089\x00\x00\x05\u0089\x01mh\u009D\u00FA\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x01\x1CIDAT(\u0091\u008D\u0091=K\u00C3P\x14\u0086\u009Fs\u009B,\u0081B\x03\u00C9hGu\x11\n\"\u00FE\x01\u00F1'8\u008B 86\u00BB\x1F\u00E0 \bb\x1D\n\"8\u0088\u00A3\x7F\u00C1\u00C5]\u0087\u00A2S\x1D\u00AB[\x02\x06\n\x19L\u009A\u00E3\u00D04\u00DCJ\x05\u00DF\u00E9\u00DE\u00F7>\u00EF\u00F9\u00E0\n\u0095\u00F4\u0081F2\u00F2:,P\u00D0\u00CE\x06\u00B2\u00C3\x04\u00C0\u0099\u0099\u00C9\u00A7\u00B7/F\u00AE\x17\x05\u0092\x0F\u00EF\x00\u00B2\x1B\x00\x01\u00F8\u00EA\u00B5Z\x13\u00CD\u00DF\u0081\u0090\u00A9\u00FB\u0088\u00A2\u00C0v\u0095\u0089\x1B\u00E2.\u00FBQ\u009A\x1A\u0080\u0082\u00FC\u00A4\u0086\x01J\u00CE\x159\u00B3\u009A\u0084\u0085\u00E6\u00C7\x00\u0092\\4W1\u00E5+\u00E0\u00D6\u00CF\u00CA\u0096\u008A\x14\u0082>Y\u00A1B\u0090\u008EQS^\u00CE\u00C1\x7F\u00CBQ\u00D1\u009E1\u00A0\u00FF\u0080\u00A7\u00AB)\u00DF\x12_5W\u00A4,\u00DF~\u008D\u00F4\u008CP\x02\u009B\x16\u009F\u00AB1k&\u00EC\u008E\u0087\n\u00FD\u00F9Rr\u00AF\u00E8\u009Dm)\u00F4\u00C3\u00EExh\x00\x1CqO\u0081\u00D8\u00EA\u00BD+*{\x16\x1FW\f\x06\u00C0\u008F\u00D2TU\u008F\u00ACr\u00EB\b\x1B\u00F5U\u00F4\u00D0\u008F\u00D2\x14\u00AC\u009F\x0E\u0096\u00B2\u00DBd\u00E4\u00BD,Z6hg\u0083\u00D9\u00F9\x07\u00C1Ia\u009F\u00C3$\u00BB\u00CC\x00\x00\x00\x00IEND\u00AEB`\u0082", "w12_danger.png", "icons" );
w12_danger;


// ==================== |----------| ====================
// ==================== | w16_file | ====================
// ==================== |----------| ====================

var w16_file = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\b\x06\x00\x00\x00\x1F\u00F3\u00FFa\x00\x00\x00\tpHYs\x00\x00\x07k\x00\x00\x07k\x01\u00CE\u00B7sZ\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x00\u0090IDAT8\u008Dc\u00DC\u00B8q\u00E3Q\x06\x06\x06+\x06\u00FC \fJg\u00FF\u00FC\u00F9\u00D3?,,\u00EC#L\u0082\u0089\b\u00CD\u00C8\u00C0\u009E\u0083\u0083c\u00EF\u0096-[\x04\u0091\r \t\u00FC\u00FF\u00FF\u00DF\u00F8\u00DF\u00BF\x7F\u00BBW\u00ADZ%D\u0096\x010C\u00D8\u00D9\u00D9w\u00AFZ\u00B5J\u0088,\x03\u00A0\u00C0\u0088\u009D\u009D}\x1D\x0B\u0091\u008A\u0099\u00FF\u00FC\u00F9s\u0084\u0085\u0085%\f]\u0082(\x03\u00FE\u00FF\u00FF\u009F\u00C7\u00CA\u00CA\u00CA\u00F8\u00FF\u00FF\u00FF?d\x19\u00C0\u00C8\u00C8h\u00F9\u00FF\u00FF\x7FKlr\u0094\u0084\u00C1\u00A8\x01\u0083\u00CA\u0080\u00A3\x14\u00E8?\x02\x00\u008B\u00BC-'\u00FCN\u00B0\u00CA\x00\x00\x00\x00IEND\u00AEB`\u0082", "w16_file.png", "icons" );
w16_file;

// ==================== |------------| ====================
// ==================== | w16_file_d | ====================
// ==================== |------------| ====================

var w16_file_d = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\b\x06\x00\x00\x00\x1F\u00F3\u00FFa\x00\x00\x00\tpHYs\x00\x00\x07k\x00\x00\x07k\x01\u00CE\u00B7sZ\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x00\u0092IDAT8\u008D\u00ED\u00D0\u00B1\r\u00C20\x10F\u00E1\u00F7\x1F,@F!Uh\x18\u0081\u008E\x05\u009C&Bb\u00A0\f\u00C1\x02\x11]2\x10\u00B5\u0091\u008F*R\x14\x04\x04W\x14\u00BC\u00F6|\u009FNV]\u00D7\x03\u00B0\u00E3M\u00EE~\x04\u0090t2\u00B3C\u00DB\u00B6\u00B7qf\u009F\u0096g\u00EDSJ\u00D7\u00A6i6S\u00E0\u00DB\u00CA\x18c\x17B(r\x01\u0080RR\x17B(r\x01\u0080\u00AD\u00A4\u00CBz\u00C9K3[\u00B9{?~\u00E6\u00B4E\u0080\u00BB\u009F\x01\x01\u00F7,\x00\u00A8\u0080J\u00D2\u00F3u\x0B\u0081\u0097\u00FD\u0081_\x01\u0086\u00DCew\u00EF\x1F\u00FB\u00F1'KC\u00B6\u0093\u0086\x00\x00\x00\x00IEND\u00AEB`\u0082", "w16_file_d.png", "icons" );
w16_file_d;

// ==================== |---------| ====================
// ==================== | w16_fix | ====================
// ==================== |---------| ====================

var w16_fix = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\b\x06\x00\x00\x00\x1F\u00F3\u00FFa\x00\x00\x00\tpHYs\x00\x00\x07a\x00\x00\x07a\x01\u0095\u00C3\u00B8\u00B6\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x01\x1EIDAT8\u008D\u0095\u0091\u00B1J\x041\x10\u0086\u00FF\t\x07a\u00F1\x01|\x05\x11\x05[+\u00AFK\u00E3&\x0B\u00C2\u00956\u00B6W\u00A8\u008D\u00F7\bg\u00E5\u00E1\x1B\u00D8\u00AE\u0088\u0091h\u00B1\u009D\x07\u0096\u0082\u00D5\u00F9\x1A\u0082\u00B0\u00904\x13\u009B\x13\u00D6\u00F5n7\u00F7\u00973\u00F9\u00BE\x19&\u0084\rS\u0096e&\u00A5\u009C\u00C5\x18\u00BF\u008A\u00A2\u0098\u00D0&\u00B0sn\u0087\u0099K\x00\u00FB\x00\x10c\u00BC\x16\u00A9\u00B0\u00B5\u00F6\u0094\u0099\u00DF\x7F\u00E1e\u00B6\x07\u00A9\x02\"\u00BAk\u0095>\u00B3,\x1B'o\u00D0JMD#\u00A5T\u009D$\u00B0\u00D6N[\u00DB\u008C\u00B5\u00D6\x0B\x00\u00E8=\u00A2\u00B5vJDW\x00f\u00C6\u0098\u008Bv\u00BFS\u00E0\u009C+\u0098\u00F9\u0091\u0088n\u00B4\u00D6\u0097\u00AB\u00DE\u00FC\x11TU\u00B5\u00E5\u00BD\u00BF\x05`\x00,\x00\u009C\t!\u00F6\u00F2<\u00B7\u00EB\u0086P\x13\x0E!<\u00C7\x18\u0087\u008D\u00FE\u00DC\x183\u00FCG52hL~\x01p\u00D4\u00EA\u00EFv\u00C1\x00 :`\x10\u00D1S\u00AF\u00C0{\x7F\u00BF\x06~\u0095R\u009E\u00F7\n\x00\x1C\u00AC\u00A8\u00CF\u00A5\u0094\u00C7J\u00A9\u00BAW \u00848\x01\u00F0\u00DD\u00A8\u00BD\u0085\x10\u0092``\u00F9\x0B\u00CE\u00B9Cf~ \u00A2\x0F)\u00E5(\x15\x06\u0080\x1F\u00A1\u00D8hU\u00F50k\u0098\x00\x00\x00\x00IEND\u00AEB`\u0082", "w16_fix.png", "icons" );
w16_fix;

// ==================== |--------------| ====================
// ==================== | w16_live_fix | ====================
// ==================== |--------------| ====================

var w16_live_fix = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\b\x06\x00\x00\x00\x1F\u00F3\u00FFa\x00\x00\x00\tpHYs\x00\x00\x07a\x00\x00\x07a\x01\u0095\u00C3\u00B8\u00B6\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x01\u009DIDAT8\u008D\u008D\u0093=k\x14Q\x14\u0086\u009F\u00F7\x06\u00C6\u00D4\u0082\u00B5\u009D\u009D\u00C5\x0E\u00F8\x07\u00ACd\u00C3\u009D\x19\u008B@\x1AE$\u00A0\"h0\u00A2\u008D\u00E0h\n\u0083\x1FlH\u0097F\u0089V\x11\\\u00E7\u00CE\u00C8v\u00B2\x7F\u00C2? \u0096b\u00B3\u00D5\u00EA\u00C2\x1C\u009B\u0099e\u00D8(\u00BB\u00A7\u00B9\u0097\u00F7\u00DC\u00E7=\u00E7^\u00CE\u0085\u0085\u00C8\u00F3\u00DC\u0085\x10n/\u00EAf\u00A6\x10\u00C2\u00EB\u00B2,\x7F\u0085\x106Z\u00DD-\x1E\u008C\u00E3\u00F8:0\x18\x0E\u0087\x17\u00BApUU\x07\u00C0\u00AE\u0099\u009D\x05>\x15E\u00D1\x07P\u00B7r\x1C\u00C7Gf\u00B6\u00DDH\x7F$\u00DD\u00F2\u00DE\x1FWUu`f\u00F7\u00CC\u00EC\u009Bsn\u00CF\u00CC\u00DE\x01k\u0092\u00AE\u00BA\u008EA=\u009DN\u00EF\x02/\u0081\x1A\u00D8\u00F4\u00DE\x1F\u0097e9h\u00E1(\u008A.'I\u00F2\x11\u00B8\x01\u009C1\u00B3\x0F\u00F3\x0E\u00DA\x18\u008F\u00C7\u00EB\u0093\u00C9\u00E4E\u0092$\x0F\u00CA\u00B2\x1C\x00\u00F7\u00CD\u00EC{\x14E\u0097\u00FA\u00FD\u00FE\u00CF\u00D1htn6\u009B}\x05.\x02oN\x19\u00B4wn\u00E1F\u00AA\u00CDl;\u008A\u00A2/-,\u00E9\u00D0{\u00BFs\u00CA\u00E0\x1F0\x1D\u0093\x1F\u0092\u00CE\u00CFa\u00C9\u00B4\"<\x0FIG\u00DE\u00FB;\u0092\f`\u00AD\u009B\u00EC\u00F5z\u00FB\u0092vW\u0085\u00A13\x07EQ\u00ECKz\u00BC\n\x1CB\u00D8\u009B\x1B4\x136X\x02\x1Fv\u00E0M\u00E0I\u00B3\u00A2\u00A2(^Iz\u00B8\x04\u00DEi\u00E0\x1Cx\u00DAI?s\u00CE\u00B9\u009B\u00AB\u00C0\x00i\u009A\u00E6f\u00B6\x05`f[i\u009A\u00E6\u00AE\u00AE\u00EBk\u00C0\u00EFep\x1BY\u0096\u009DHz\u009Ee\u00D9\t4\x7F!\u0084p\x05\u00F8\f\u00AC/>\u00D8\u00FF\u00BA\u009B\x17j7!\u0084\rI\u00EF\u00EB\u00BA~\u009B\u00A6\u00E9\u00A3U`\u0080\u00BFqE\u00D9\u00BCQ\x18\u00D39\x00\x00\x00\x00IEND\u00AEB`\u0082", "w16_live_fix.png", "icons" );
w16_live_fix;

// ==================== |----------------| ====================
// ==================== | w16_live_fix_d | ====================
// ==================== |----------------| ====================

var w16_live_fix_d = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\b\x06\x00\x00\x00\x1F\u00F3\u00FFa\x00\x00\x00\tpHYs\x00\x00\x07a\x00\x00\x07a\x01\u0095\u00C3\u00B8\u00B6\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x01\u00A8IDAT8\u008D\u0095\u0093\u00BF\u008B\x13A\x18\u0086\u009Fw\u00B7\u00D0\x14\u00D7\bbi\u0091X\x1A\u0098t\u0082\u0095U\u00B0\x13\x11\u00CE\u00E2\u008A8\x1B\u0083\u0085z\u00A0`\u00E9\u00EA\x15\n\nwx\u0088\u00CAL\bVr\u0082\\cc!\u00A4\u00B0\x13\x19\u008B3\u00CD\u00D9\u00C8\u0096\u00FE\x05\n{cq\x1B\u008Dk\u00C2\u009DO\u00F9}\u00F3\u00BC\x1F\u00F3+\u00A5F\u009E\u00E7I\u00B3\u00D9\u00BC\x1AB\u00F8Tk)\u00CB\u00B2\u00C7\u009DN\u00E7\u00951\u00E6K\ba\x17 \u00A9\x07\x14E\u00B1\"ic0\x18\u009C\u00AA\u00C9\x1B\u00C0-\u00E0\u0098\u00A47Y\u0096\u009D\x07\u00D0\u00EC\u00E4\u00A2(\u009EK\u00EAW\u00A5\u009F\u0092\x06\u00CE\u00B9\u0097\u0095|\x03\u00D8\u0091\u00B4\x16c\x1C\x01\u00A9\u00A4\x0B\u00E94`<\x1E\u00C7V\u00AB\u00F5\u00AE\u00D1h\x1C\x01\u00CEH\u00BA\u00E8\u009C{m\u00AD]\u0097t\x13\u00D8)\u00CB\u00F2\u00DCp8\u00FCh\u008C\u00D9\u0095t\x19\u00E8\u00FE\x0E\x00\u0098L&e\u00BB\u00DD\u00FE\u0090$\u00C9\u0092\u00F7~sF\u00FEV\u0096\u00E5\u00D9\u00D1h\u00F4\u00BD\u00D7\u00EB\x1DO\u0092d\b\u009C\x00\u009E\u0089\u00F9hF\x06\u00D8\x03\u00B2\u00B2,\u00DF\u00A6i\u00FA\x1E8\r<\u00F1\u00DE\u00AF\u00CE\x0B\u00A8\u00CBS\u00F6\u0080\x0289\u0095\u0081X\x0FX$\u00FFY \u00BDp\u00CE]\x03\"\u00D4\u00AE\u00D1Z\u00FB\u00E0\x7F\u00E4\u00BF\x02\u00AC\u00B5\x0F%\u00DD9\u008Cl\u00AD]\u009B\u00D6S\u00F6\x1F\u00C9\u00BA\u00A4\u00DB\u008Bd\u00F6\u00F7|\u00BD\u0092/I\u00DA4\u00C6LB\b\x13\u00F5\u00FB\u00FDG1\u00C6\u0083\u00E4U fY\u0096\x03wgz\u00F7\u0092\x18\u00E3\u0095\u00C3\u00C8\x00\u00DE\u00FB\x1CX\u00AEz\u00CB\u00DE\u00FB<\x01V\u0080\x1F\x07\u00C9S\u00BC\u00F7[\u0092\u00EE{\u00EF\u00B7\u00A0\u00FA\x0B\u00D6\u00DA\u00AE\u00A4m\u00E0(\u00CC?\u00EDE\u00A4\x00!\u0084\u00AF\u00C6\u0098\u00CF\u0092\u00BA\u0092\u009E:\u00E7\u00FE\u0099\u00BC\u0088_X;\u00B1\u008F\teN\u00CC\x00\x00\x00\x00IEND\u00AEB`\u0082", "w16_live_fix_d.png", "icons" );
w16_live_fix_d;

// ==================== |------| ====================
// ==================== | core | ====================
// ==================== |------| ====================

/**
 * The sanity levels.
 * @enum {number}
 * @readonly
 */
DuSanity.Level = {
    UNKNOWN: -1,
    OK: 0,
    INFO: 1,
    WARNING: 2,
    DANGER: 3,
    CRITICAL: 4,
    FATAL: 5
}

/**
 * The current sanity level
 * @type {DuSanity.Level}
 * @readonly
 */
DuSanity.currentLevel = DuSanity.Level.UNKNOWN;

/**
 * Fixes the issues for the given test, if possible
 * @param {DuSanity.Test} test The test to fix
 * @return {DuSanity.Level} The level after the fix
 */
DuSanity.fix = function(test) {
    if (!DuSanity.needsFix(test)) return test.currentLevel;
    test.fix();
    return test(true);
}

/**
 * Checks if the the live fix is enabled for the given test
 * @param {DuSanity.Test} test The test
 * @return {boolean} True if the test live fix is enabled
 */
DuSanity.isLiveFixEnabled = function (test)
{
    var fix = DuESF.settings.get("sanity/fix/" + test.stringId, false);
    return fix && test.hasAutoFix;
}

/**
 * Enables or disables the live fix for the given test
 * @param {DuSanity.Test} test The test
 * @param {boolean} [enabled=true]
 */
DuSanity.setLiveFixEnabled = function (test, enabled)
{
    DuESF.settings.set("sanity/fix/" + test.stringId, enabled);
    DuESF.settings.save();
}

/**
 * Checks if the fiven test needs a fix
 * @param {DuSanity.Test} test The test to fix
 * @return {boolean} True if the test needs a fix
 */
DuSanity.needsFix = function ( test ) {
    
    var liveFix = DuESF.settings.get("sanity/fix/" + test.stringId, false);
    return liveFix && test.currentLevel >= DuSanity.Level.DANGER && test.hasFix;
}

/**
 * Checks if the fiven test is enabled for the current project
 * @param {DuSanity.Test} test The test
 * @return {boolean} True if the test is enabled for the current project
 */
DuSanity.isProjectEnabled = function( test ) {
    //Init project settings
    DuAEProject.settings.update();
    DuAEProject.settings.data.sanity = def(DuAEProject.settings.data.sanity, {});
    DuAEProject.settings.data.sanity[test.stringId] = def(DuAEProject.settings.data.sanity[test.stringId], true);
    return DuAEProject.settings.data.sanity[test.stringId];
}

/**
 * Checks if the fiven test is globally enabled
 * @param {DuSanity.Test} test The test
 * @return {boolean} True if the test is enabled
 */
DuSanity.isGloballyEnabled = function (test)
{
    return DuESF.settings.get("sanity/" + test.stringId, true);
}

/**
 * Globally enables or disables the test
 * @param {DuSanity.Test} test The test
 * @param {boolean} [enabled=true]
 */
DuSanity.setGloballyEnabled = function (test, enabled)
{
    DuESF.settings.set("sanity/" + test.stringId, enabled);
    DuESF.settings.save();
}

/**
 * Checks if a Sanity Test is enabled
 * @param {DuSanity.Test} test The test
 * @return {boolean}
 */
DuSanity.isEnabled = function(test)
{
    var project = DuSanity.isProjectEnabled(test)

    if (project) 
    {
        //check DuAEF settings
        test.enabled = DuSanity.isGloballyEnabled(test);
    }
    else 
    {
        test.enabled = false;
    }
    
    return test.enabled;
}

/**
 * Enables or disables the test
 * @param {DuSanity.Test} test The test
 * @param {boolean} [enabled=true]
 */
DuSanity.setEnabled = function (test, enabled)
{
    if (enabled) 
    {
        //First run
        test();
        test.enabled = true;
    }
    else 
    {
        test.enabled = false;
    }
}

/**
 * Enables or disables the test for the current project only
 * @param {DuSanity.Test} test The test
 * @param {boolean} [enabled=true]
 */
DuSanity.setProjectEnabled = function (test, enabled)
{
    //Init project settings
    DuAEProject.settings.update();
    DuAEProject.settings.data.sanity = def(DuAEProject.settings.data.sanity, {});
    DuAEProject.settings.data.sanity[test.stringId] = enabled;
    DuAEProject.settings.save();
}

/**
 * Sets the timeout for the test
 * @param {DuSanity.Test} test The test
 * @param {int} timeOut The time out in milliseconds.
 */
DuSanity.setTimeOut = function (test, timeOut)
{
    var enabled = test.enabled;
    DuSanity.setEnabled(test, false);
    DuESF.settings.set("sanity/timeOut/" + test.stringId, timeOut);
    DuESF.settings.save();
    DuSanity.setEnabled(test, enabled);
}

/**
 * Runs a test
 * @param {boolean} [dontFix=false] If false, will automatically fix the issue.
 * @param {boolean} [force=false] To improve performance, the test may be automatically paused. Set this to true to force it to run if calling this method.
 * @return {DuSanity.Level} The level of the result of the test.
 */
DuSanity.runTest = function(test, dontFix, force) {
    dontFix = def(dontFix, false);
    force = def(force, false);

    var elapsed = Date.now() - test.lastRun;
    var timedOut = elapsed > test.timeOut;

    var result = test.currentLevel;

    if (force || timedOut) {
        result = test(dontFix, force);
        test.lastRun = Date.now();
    }

    if(DuSanity.currentLevel < result) DuSanity.currentLevel = result;
    
    return result;
}

//low-level undocumented function: checks a value against a limit and sets the results in the UI
//returns the level
DuSanity.checkLevel = function(value, limit)
{
    if (value < limit*0.66)
    {
        return DuSanity.Level.OK;
    }

    if (value < limit*0.75)
    {
        return DuSanity.Level.INFO;
    }
    
    if (value < limit)
    {
        return DuSanity.Level.WARNING;
    }
    
    if (value < limit * 1.5)
    {
        return DuSanity.Level.DANGER;
    }
    
    return DuSanity.Level.CRITICAL;
}

/**
 * All the available tests.
 * @namespace
 * @category DuSanity
 */
DuSanity.Test = {};

/**
 * Checks if some compositions share the same name.
 * @param {boolean} [dontFix=false] If false, will automatically fix the issue.
 * @param {boolean} [force=false] To improve performance, this test may be automatically paused. Set this to true to force it to run if calling this method.
 */
DuSanity.Test.compNames = function ( dontFix, force ) {
    dontFix = def(dontFix, false);
    force = def(force, false);

    if ( DuSanity.Test.projectSize.currentLevel > DuSanity.Level.WARNING ) {
        DuSanity.Test.compNames.info = "Project too big";
        DuSanity.Test.compNames.tip = "The project is too big to run this test automatically. Use the 'Refresh' button to run it now.";
        DuSanity.Test.compNames.paused = true;
    }
    else if ( DuSanity.Test.projectItems.currentLevel > DuSanity.Level.WARNING ) {
        DuSanity.Test.compNames.info = "Too many items";
        DuSanity.Test.compNames.tip = "The project contains too many items to run this test automatically.\nUse the 'Refresh' button to run it now.";
        DuSanity.Test.compNames.paused = true;
    }
    else {
        DuSanity.Test.compNames.paused = false;
    }

    if (!force && DuSanity.Test.compNames.paused) {
        DuSanity.Test.compNames.currentLevel = DuSanity.Level.UNKNOWN;
        return DuSanity.Test.compNames.currentLevel;
    }

    var duplicatedNames = DuAEProject.checkCompNames();
    if (duplicatedNames.length == 0)
    {
        DuSanity.Test.compNames.info = "";
        DuSanity.Test.compNames.tip = "";
        DuSanity.Test.compNames.currentLevel = DuSanity.Level.OK;
    }
    else
    {
        DuSanity.Test.compNames.info = duplicatedNames.length;
        DuSanity.Test.compNames.tip = "Some compositions have the same name:";
        for (name in duplicatedNames)
        {
            if (name == 'length') continue;
            if (duplicatedNames.hasOwnProperty(name))
            {
                DuSanity.Test.compNames.tip += "\n- " + name;
            }
        }
        DuSanity.Test.compNames.currentLevel = DuSanity.Level.DANGER;
    }

    if (dontFix) return DuSanity.Test.compNames.currentLevel;
    return DuSanity.fix( DuSanity.Test.compNames );

}
DuSanity.Test.compNames.lastRun = 0;
DuSanity.Test.compNames.stringId = 'compNames';
DuSanity.Test.compNames.info = '';
DuSanity.Test.compNames.tip = '';
DuSanity.Test.compNames.currentLevel = DuSanity.Level.UNKNOWN;
DuSanity.Test.compNames.enabled = true;
DuSanity.Test.compNames.id = 0;
DuSanity.Test.compNames.hasFix = true;
DuSanity.Test.compNames.hasAutoFix = true;
DuSanity.Test.compNames.timeOut = 600000;
DuSanity.Test.compNames.paused = false;
DuSanity.Test.compNames.testName = ""; // Added during init
DuSanity.Test.compNames.options = {}; // Added during init
DuSanity.Test.compNames.fix = function() {
    DuAE.beginUndoGroup( i18n._("Fix") + ': ' + DuSanity.Test.compNames.testName);

    var duplicatedNames = DuAEProject.checkCompNames();
    for (name in duplicatedNames)
    {
        if (name == 'length') continue;
        if (duplicatedNames.hasOwnProperty(name))
        {
            DuAEProject.setUniqueCompNames( duplicatedNames[name] );
        }
    }

    DuAE.endUndoGroup();
}

/**
 * Checks if some layers share the same name in the current comp.
 * @param {boolean} [dontFix=false] If false, will automatically fix the issue.
 */
DuSanity.Test.layerNames = function ( dontFix ) {
    dontFix = def(dontFix, false);

    var duplicatedNames = DuAEComp.checkLayerNames();
    if (duplicatedNames.length == 0)
    {
        DuSanity.Test.layerNames.info = ""
        DuSanity.Test.layerNames.tip = ""
        DuSanity.Test.layerNames.currentLevel = DuSanity.Level.OK;
    }
    else
    {
        DuSanity.Test.layerNames.info = duplicatedNames.length;
        DuSanity.Test.layerNames.tip = "Some layers have the same name:";
        for (name in duplicatedNames)
        {
            if (name == 'length') continue;
            if (duplicatedNames.hasOwnProperty(name))
            {
                DuSanity.Test.layerNames.tip += "\n- " + name;
                for (var i = 0, n = duplicatedNames[name].length; i < n; i++)
                {
                    DuSanity.Test.layerNames.tip += " | " + duplicatedNames[name][i].index;
                }
            }
        }
        DuSanity.Test.layerNames.currentLevel = DuSanity.Level.DANGER;
    }

    if (dontFix) return DuSanity.Test.layerNames.currentLevel;
    return DuSanity.fix( DuSanity.Test.layerNames );

}
DuSanity.Test.layerNames.lastRun = 0;
DuSanity.Test.layerNames.stringId = 'layerNames';
DuSanity.Test.layerNames.info = '';
DuSanity.Test.layerNames.tip = '';
DuSanity.Test.layerNames.currentLevel = DuSanity.Level.UNKNOWN;
DuSanity.Test.layerNames.enabled = true;
DuSanity.Test.layerNames.id = 0;
DuSanity.Test.layerNames.hasFix = true;
DuSanity.Test.layerNames.hasAutoFix = true;
DuSanity.Test.layerNames.timeOut = 60000;
DuSanity.Test.layerNames.testName = ""; // Added during init
DuSanity.Test.layerNames.options = {}; // Added during init
DuSanity.Test.layerNames.fix = function() {
    DuAE.beginUndoGroup( i18n._("Fix") + ': ' + DuSanity.Test.layerNames.testName);

    var duplicatedNames = DuAEComp.checkLayerNames();
    for (name in duplicatedNames)
    {
        if (name == 'length') continue;
        if (duplicatedNames.hasOwnProperty(name))
        {
            DuAEComp.setUniqueLayerNames( duplicatedNames[name] );
        }
    }

    DuAE.endUndoGroup();
}

/**
 * Checks the expression engine.
 * @param {boolean} [dontFix=false] If false, will automatically fix the issue.
 */
DuSanity.Test.expressionEngine = function ( dontFix ) {
    dontFix = def(dontFix, false);

    var e = DuAEProject.expressionEngine();
    if (e.indexOf("javascript") == 0)
    {
        DuSanity.Test.expressionEngine.info = "";
        DuSanity.Test.expressionEngine.tip = "";
        DuSanity.Test.expressionEngine.currentLevel = DuSanity.Level.OK;
    }
    else
    {
        DuSanity.Test.expressionEngine.info = "ES";
        DuSanity.Test.expressionEngine.tip = "The expression engine is set to 'ExtendScript Legacy'\n'JavaScript' improves performance, you can change it in the project settings.";
        DuSanity.Test.expressionEngine.currentLevel = DuSanity.Level.DANGER;
    }

    if (dontFix) return  DuSanity.Test.expressionEngine.currentLevel;
    return DuSanity.fix( DuSanity.Test.expressionEngine );

}
DuSanity.Test.expressionEngine.lastRun = 0;
DuSanity.Test.expressionEngine.stringId = 'expressionEngine';
DuSanity.Test.expressionEngine.info = '';
DuSanity.Test.expressionEngine.tip = '';
DuSanity.Test.expressionEngine.currentLevel = DuSanity.Level.UNKNOWN;
DuSanity.Test.expressionEngine.enabled = true;
DuSanity.Test.expressionEngine.id = 0;
DuSanity.Test.expressionEngine.hasFix = true;
DuSanity.Test.expressionEngine.hasAutoFix = true;
DuSanity.Test.expressionEngine.timeOut = 1800000;
DuSanity.Test.expressionEngine.testName = ""; // Added during init
DuSanity.Test.expressionEngine.options = {}; // Added during init
DuSanity.Test.expressionEngine.fix = function() {
    DuAE.beginUndoGroup( i18n._("Fix") + ': ' + DuSanity.Test.expressionEngine.testName);

    app.project.expressionEngine = 'javascript-1.0';

    DuAE.endUndoGroup();
}

/**
 * Checks the project size
 * @param {boolean} [dontFix=false] If false, will automatically fix the issue.
 */
DuSanity.Test.projectSize = function ( dontFix ) {
    dontFix = def(dontFix, false);

    var size = DuAEProject.getSize();
    
    var sizeLimit = DuESF.settings.get("sanity/options/" + DuSanity.Test.projectSize.stringId + "/sizeLimit", 100) * 1000000;

    if (size < 0 && app.project.numItems > 0)
    {
        DuSanity.Test.projectSize.info = "Not saved";
        DuSanity.Test.projectSize.tip = "You should save this project right now!";
        DuSanity.Test.projectSize.currentLevel = DuSanity.Level.FATAL;
        return DuSanity.Test.projectSize.currentLevel;
    }
    else if (size < 0)
    {
        DuSanity.Test.projectSize.info = "";
        DuSanity.Test.projectSize.tip = "";
        DuSanity.Test.projectSize.currentLevel = DuSanity.Level.OK;
    }
    else
    {
        DuSanity.Test.projectSize.info = DuString.fromSize(size);
        DuSanity.Test.projectSize.tip = "Try to keep the project small (e.g. do not animate several shots in the same project).";
        DuSanity.Test.projectSize.currentLevel = DuSanity.checkLevel(size, sizeLimit);
    }

    if (dontFix) return  DuSanity.Test.projectSize.currentLevel;
    return DuSanity.fix( DuSanity.Test.projectSize );
}
DuSanity.Test.projectSize.lastRun = 0;
DuSanity.Test.projectSize.stringId = 'projectSize';
DuSanity.Test.projectSize.info = '';
DuSanity.Test.projectSize.tip = '';
DuSanity.Test.projectSize.currentLevel = DuSanity.Level.UNKNOWN;
DuSanity.Test.projectSize.enabled = true;
DuSanity.Test.projectSize.id = 0;
DuSanity.Test.projectSize.hasFix = false;
DuSanity.Test.projectSize.hasAutoFix = false;
DuSanity.Test.projectSize.testName = ""; // Added during init
DuSanity.Test.projectSize.options = {}; // Added during init
DuSanity.Test.projectSize.timeOut = 60000;

/**
 * Checks the project size
 * @param {boolean} [dontFix=false] If false, will automatically fix the issue.
 */
DuSanity.Test.projectItems = function ( dontFix ) {
    dontFix = def(dontFix, false);

    var n = app.project.numItems;
    var limit = DuESF.settings.get("sanity/options/" + DuSanity.Test.projectItems.stringId + "/itemsLimit", 1000);

    DuSanity.Test.projectItems.info = n + " items";
    DuSanity.Test.projectItems.tip = "Try to keep the project small (e.g. do not animate several shots in the same project).";
    DuSanity.Test.projectItems.currentLevel = DuSanity.checkLevel(n, limit);

    if (dontFix) return  DuSanity.Test.projectItems.currentLevel;
    return DuSanity.fix( DuSanity.Test.projectItems );
}
DuSanity.Test.projectItems.lastRun = 0;
DuSanity.Test.projectItems.stringId = 'projectItems';
DuSanity.Test.projectItems.info = '';
DuSanity.Test.projectItems.tip = '';
DuSanity.Test.projectItems.currentLevel = DuSanity.Level.UNKNOWN;
DuSanity.Test.projectItems.enabled = true;
DuSanity.Test.projectItems.id = 0;
DuSanity.Test.projectItems.hasFix = false;
DuSanity.Test.projectItems.hasAutoFix = false;
DuSanity.Test.projectItems.testName = ""; // Added during init
DuSanity.Test.projectItems.options = {}; // Added during init
DuSanity.Test.projectItems.timeOut = 600000;

/**
 * Checks if some items have the same source file
 * @param {boolean} [dontFix=false] If false, will automatically fix the issue.
 * @param {boolean} [force=false] To improve performance, this test may be automatically paused. Set this to true to force it to run if calling this method.
 */
DuSanity.Test.itemSources = function ( dontFix, force ) {
    dontFix = def(dontFix, false);
    force = def(force, false);

    if ( DuSanity.Test.projectSize.currentLevel > DuSanity.Level.WARNING ) {
        DuSanity.Test.itemSources.info = "Project too big";
        DuSanity.Test.itemSources.tip = "The project is too big to run this test automatically. Use the 'Refresh' button to run it now.";
        DuSanity.Test.itemSources.paused = true;
    }
    else if ( DuSanity.Test.projectItems.currentLevel > DuSanity.Level.WARNING ) {
        DuSanity.Test.itemSources.info = "Too many items";
        DuSanity.Test.itemSources.tip = "The project contains too many items to run this test automatically.\nUse the 'Refresh' button to run it now.";
        DuSanity.Test.itemSources.paused = true;
    }
    else {
        DuSanity.Test.itemSources.paused = false;
    }

    if (!force && DuSanity.Test.itemSources.paused) {
        DuSanity.Test.itemSources.currentLevel = DuSanity.Level.UNKNOWN;
        return DuSanity.Test.itemSources.currentLevel;
    }

    var duplicatedSources = {};
    duplicatedSources.length = 0;
    var sources = {};
    
    for (var i = 1, n = app.project.numItems; i <= n; i++)
    {
        var item = app.project.item(i);
        if (!(item instanceof FootageItem)) continue;
        var source  = item.mainSource;
        if (!(source instanceof FileSource)) continue;
        var file = source.file.fsName;
  
        if (DuAE.isLayeredFile(file)) continue;

        if (duplicatedSources[file])
        {
            duplicatedSources[file].push(item);
            continue;
        }
        if ( sources[file]) 
        {
            duplicatedSources[file] = [sources[file], item];
            duplicatedSources.length++;
            continue;
        }

        sources[file] = item;
    }

    if (duplicatedSources.length == 0)
    {
        DuSanity.Test.itemSources.info = "";
        DuSanity.Test.itemSources.tip = "";
        DuSanity.Test.itemSources.currentLevel = DuSanity.Level.OK;
    }
    else
    {
        DuSanity.Test.itemSources.info = duplicatedSources.length + " items";
        DuSanity.Test.itemSources.tip = "Some footages share the same sources:";
        for (var file in duplicatedSources)
        {
            if (file == 'length') continue;
            if (duplicatedSources.hasOwnProperty(file))
            {
                DuSanity.Test.itemSources.tip += "\n- " + file;
            }
        }
        DuSanity.Test.itemSources.currentLevel = DuSanity.Level.DANGER;
    }

    if (dontFix) return  DuSanity.Test.itemSources.currentLevel;
    return DuSanity.fix( DuSanity.Test.itemSources );
}
DuSanity.Test.itemSources.lastRun = 0;
DuSanity.Test.itemSources.stringId = 'itemSources';
DuSanity.Test.itemSources.info = '';
DuSanity.Test.itemSources.tip = '';
DuSanity.Test.itemSources.currentLevel = DuSanity.Level.UNKNOWN;
DuSanity.Test.itemSources.enabled = true;
DuSanity.Test.itemSources.id = 0;
DuSanity.Test.itemSources.hasFix = false;
DuSanity.Test.itemSources.hasAutoFix = false;
DuSanity.Test.itemSources.timeOut = 1800000;
DuSanity.Test.itemSources.testName = ""; // Added during init
DuSanity.Test.itemSources.options = {}; // Added during init
DuSanity.Test.itemSources.paused = false;

/**
 * Checks if some items (footages) are not used
 * @param {boolean} [dontFix=false] If false, will automatically fix the issue.
 */
DuSanity.Test.unusedItems = function ( dontFix ) {
    dontFix = def(dontFix, false);

    var limit = 10;
    var unused = DuAEProject.getUnusedFootages();

    DuSanity.Test.unusedItems.currentLevel = DuSanity.checkLevel(unused.length, limit);

    if (DuSanity.Test.unusedItems.currentLevel > DuSanity.Level.OK) 
    {
        DuSanity.Test.unusedItems.info = unused.length + " footages";
        DuSanity.Test.unusedItems.tip = "Found some footages which are unused. You should remove them.";
        for (var i = 0, n = unused.length; i < n; i++)
        {
            DuSanity.Test.unusedItems.tip += "\n- " + unused[i].name;
        }
    }
    else
    {
        DuSanity.Test.unusedItems.info = "";
        DuSanity.Test.unusedItems.tip = "";
    }

    if (dontFix) return  DuSanity.Test.unusedItems.currentLevel;
    return DuSanity.fix( DuSanity.Test.unusedItems );
}
DuSanity.Test.unusedItems.lastRun = 0;
DuSanity.Test.unusedItems.stringId = 'unusedItems';
DuSanity.Test.unusedItems.info = '';
DuSanity.Test.unusedItems.tip = '';
DuSanity.Test.unusedItems.currentLevel = DuSanity.Level.UNKNOWN;
DuSanity.Test.unusedItems.enabled = true;
DuSanity.Test.unusedItems.id = 0;
DuSanity.Test.unusedItems.hasFix = true;
DuSanity.Test.unusedItems.hasAutoFix = true;
DuSanity.Test.unusedItems.timeOut = 1800000;
DuSanity.Test.unusedItems.testName = ""; // Added during init
DuSanity.Test.unusedItems.options = {}; // Added during init
DuSanity.Test.unusedItems.fix = function () {
    DuAE.beginUndoGroup( i18n._("Fix") + ': ' + DuSanity.Test.unusedItems.testName);

    var unused = DuAEProject.getUnusedFootages();
    for (var i = unused.length-1; i >= 0; i--)
    {
        unused[i].remove();
    }

    DuAE.endUndoGroup();
};

/**
 * Checks if some precomps are in the root of the project
 * @param {boolean} [dontFix=false] If false, will automatically fix the issue.
 */
DuSanity.Test.precomps = function ( dontFix ) {
    dontFix = def(dontFix, false);

    var precomps = DuAEProject.getPrecompsAtRoot();
    var limit = DuESF.settings.get("sanity/options/" + DuSanity.Test.precomps.stringId + "/maxPrecompsAtRoot", 1);

    DuSanity.Test.precomps.currentLevel = DuSanity.checkLevel(precomps.length, limit);
    DuSanity.Test.precomps.tip = "";
    DuSanity.Test.precomps.info = "";

    if (DuSanity.Test.precomps.currentLevel > DuSanity.Level.OK)
    {
        DuSanity.Test.precomps.info = precomps.length + " comps";
        DuSanity.Test.precomps.tip = "Some precompositions are at the root of the project.\n" + 
            "They should be moved in a subfolder.";
        for (var i = 0, n = precomps.length; i<n; i++)
        {
            DuSanity.Test.precomps.tip += "\n- " + precomps[i].name;
        }
    }

    if (dontFix) return  DuSanity.Test.precomps.currentLevel;
    return DuSanity.fix( DuSanity.Test.precomps );
}
DuSanity.Test.precomps.lastRun = 0;
DuSanity.Test.precomps.stringId = 'precomps';
DuSanity.Test.precomps.info = '';
DuSanity.Test.precomps.tip = '';
DuSanity.Test.precomps.currentLevel = DuSanity.Level.UNKNOWN;
DuSanity.Test.precomps.enabled = true;
DuSanity.Test.precomps.id = 0;
DuSanity.Test.precomps.hasFix = true;
DuSanity.Test.precomps.hasAutoFix = true;
DuSanity.Test.precomps.timeOut = 600000;
DuSanity.Test.precomps.testName = ""; // Added during init
DuSanity.Test.precomps.options = {}; // Added during init
DuSanity.Test.precomps.fix = function () {
    DuAE.beginUndoGroup( i18n._("Fix") + ': ' + DuSanity.Test.precomps.testName);

    var precomps = DuAEProject.getPrecompsAtRoot();
    var precompFolderName = DuESF.settings.get("sanity/options/" + DuSanity.Test.precomps.stringId + "/precompsFolder", "Precomps");
    var precompFolder = DuAEProject.getFolderItem( precompFolderName );
    if (!precompFolder) precompFolder = app.project.items.addFolder(precompFolderName);
    for (var i = 0, n = precomps.length; i < n; i++)
    {
        precomps[i].parentFolder = precompFolder;
    }

    DuAE.endUndoGroup();
};

/**
 * Checks if there are multiple comps in the root of the project
 * @param {boolean} [dontFix=false] If false, will automatically fix the issue.
 */
DuSanity.Test.unusedComps = function ( dontFix ) {
    dontFix = def(dontFix, false);

    var folderName = DuESF.settings.get("sanity/options/" + DuSanity.Test.unusedComps.stringId + "/mainCompsFolder", "" );
    if (folderName == 'Project root') folderName = '';
    var unusedFolder = DuAEProject.getFolderItem( folderName );
    var comps = DuAEProject.getUnusedComps(unusedFolder);
    var limit = DuESF.settings.get("sanity/options/" + DuSanity.Test.unusedComps.stringId + "/maxUnusedComps", 1);

    DuSanity.Test.unusedComps.currentLevel = DuSanity.checkLevel(comps.length, limit);
    DuSanity.Test.unusedComps.tip = "";
    DuSanity.Test.unusedComps.info = "";

    if (DuSanity.Test.unusedComps.currentLevel > DuSanity.Level.OK)
    {
        DuSanity.Test.unusedComps.info = comps.length + " comps";
        DuSanity.Test.unusedComps.tip = "Some main compositions are stored in subfolders.\n" + 
            "It is easier to keep them at the root of the project, or they should be removed if they're not needed anymore.";
        for (var i = 0, n = comps.length; i<n; i++)
        {
            DuSanity.Test.unusedComps.tip += "\n- " + comps[i].name;
        }
    }

    if (dontFix) return  DuSanity.Test.unusedComps.currentLevel;
    return DuSanity.fix( DuSanity.Test.unusedComps );
}
DuSanity.Test.unusedComps.lastRun = 0;
DuSanity.Test.unusedComps.stringId = 'unusedComps';
DuSanity.Test.unusedComps.info = '';
DuSanity.Test.unusedComps.tip = '';
DuSanity.Test.unusedComps.currentLevel = DuSanity.Level.UNKNOWN;
DuSanity.Test.unusedComps.enabled = true;
DuSanity.Test.unusedComps.id = 0;
DuSanity.Test.unusedComps.hasFix = true;
DuSanity.Test.unusedComps.hasAutoFix = false;
DuSanity.Test.unusedComps.timeOut = 1800000;
DuSanity.Test.unusedComps.testName = ""; // Added during init
DuSanity.Test.unusedComps.options = {}; // Added during init
DuSanity.Test.unusedComps.fix = function () {
    
    var unusedFolderName = DuESF.settings.get("sanity/options/" + DuSanity.Test.unusedComps.stringId + "/mainCompsFolder", "Project root");
    var unusedFolder = DuAEProject.getFolderItem( unusedFolderName );
    var comps = DuAEProject.getUnusedComps(unusedFolder);
    if (comps.length > 0)
    {
        var dlg = DuScriptUI.popUp("Fix Unused Compositions", 'column', true);
        DuScriptUI.staticText(dlg.content, comps.length + " seemingly unused comps. have been found in this project.\nWhat do you want to do with them?");
        var slctr = DuScriptUI.selector(dlg.content);
        slctr.addButton("Remove unused compositions");
        slctr.addButton("Collect unused comps. into folder:");
        slctr.setCurrentIndex(1);
        var folderEdit =  DuScriptUI.editText(dlg.content,unusedFolderName, undefined, undefined, "Project Root");
        folderEdit.alignment = ['fill','top'];
        var validGrp = DuScriptUI.group(dlg.content, 'row');
        validGrp.alignment = ['fill','bottom'];
        var cnclBtn = DuScriptUI.button(validGrp, "Cancel");
        var okBtn = DuScriptUI.button(validGrp, "Fix");
        slctr.onChange = function() {
            folderEdit.enabled = slctr.index == 1;
        }
        cnclBtn.onClick = dlg.cancel;
        okBtn.onClick = function() {

            DuAE.beginUndoGroup( i18n._("Fix") + ': ' + DuSanity.Test.unusedComps.testName);
            
            if (slctr.index == 1)
            {
                var unusedFolderName = folderEdit.text;
                var unusedFolder = DuAEProject.getFolderItem( unusedFolderName );
            
                if (!unusedFolder)
                {
                    unusedFolder = app.project.items.addFolder(unusedFolderName);
                }
                for (var i = 0, n = comps.length; i < n ; i++)
                {
                    comps[i].parentFolder = unusedFolder;
                }
            }
            else
            {
                for (var i = 0, n = comps.length; i < n ; i++)
                {
                    comps[i].remove();
                }
            }
            
            DuAE.endUndoGroup();
            dlg.cancel();
        };

        dlg.show();
    }    
};

/**
 * Checks the memory in use
 * @param {boolean} [dontFix=false] If false, will automatically fix the issue.
 */
DuSanity.Test.memory = function ( dontFix ) {
    dontFix = def(dontFix, false);

    var mem = app.memoryInUse ;
    var limit = DuESF.settings.get("sanity/options/" + DuSanity.Test.memory.stringId + "/memoryLimit", 8) * 1073741824;

    DuSanity.Test.memory.info = DuString.fromSize(mem);
    DuSanity.Test.memory.tip = "If the memory used gets too high, it may be good to purge the cache and free some space.";
    DuSanity.Test.memory.currentLevel = DuSanity.checkLevel(mem, limit);

    if (dontFix) return  DuSanity.Test.memory.currentLevel;
    return DuSanity.fix( DuSanity.Test.memory );
}
DuSanity.Test.memory.lastRun = 0;
DuSanity.Test.memory.stringId = 'memory';
DuSanity.Test.memory.info = '';
DuSanity.Test.memory.tip = '';
DuSanity.Test.memory.currentLevel = DuSanity.Level.UNKNOWN;
DuSanity.Test.memory.enabled = true;
DuSanity.Test.memory.id = 0;
DuSanity.Test.memory.hasFix = true;
DuSanity.Test.memory.hasAutoFix = false;
DuSanity.Test.memory.timeOut = 300000;
DuSanity.Test.memory.testName = ""; // Added during init
DuSanity.Test.memory.options = {}; // Added during init
DuSanity.Test.memory.fix = function() {
    app.purge(PurgeTarget.SNAPSHOT_CACHES);
    app.purge(PurgeTarget.IMAGE_CACHES);

    var ok = confirm("We've purged what can be purged without impacting your workflow. We can now purge more memory, but you will lose your undo history.\n\nDo you want to continue?");
    if (ok) {
        app.purge(PurgeTarget.UNDO_CACHES);
        app.purge(PurgeTarget.ALL_CACHES);
    }

    DuSanity.Test.memory();
}

/**
 * Checks the number of essential properties in the current comp
 * @param {boolean} [dontFix=false] If false, will automatically fix the issue.
 */
DuSanity.Test.essentialProperties = function ( dontFix ) {
    dontFix = def(dontFix, false);

    var n = DuAEComp.numMasterProperties();
    var limit = DuESF.settings.get("sanity/options/" + DuSanity.Test.essentialProperties.stringId + "/maxEssentialProps", 40);

    DuSanity.Test.essentialProperties.info = n;
    DuSanity.Test.essentialProperties.tip = "Having a lot of Master Properties in the current composition has a very bad impact on performance.";
    DuSanity.Test.essentialProperties.currentLevel = DuSanity.checkLevel(n, limit);

    if (dontFix) return  DuSanity.Test.essentialProperties.currentLevel;
    return DuSanity.fix( DuSanity.Test.essentialProperties );
}
DuSanity.Test.essentialProperties.lastRun = 0;
DuSanity.Test.essentialProperties.stringId = 'essentialProperties';
DuSanity.Test.essentialProperties.info = '';
DuSanity.Test.essentialProperties.tip = '';
DuSanity.Test.essentialProperties.currentLevel = DuSanity.Level.UNKNOWN;
DuSanity.Test.essentialProperties.enabled = true;
DuSanity.Test.essentialProperties.id = 0;
DuSanity.Test.essentialProperties.hasFix = false;
DuSanity.Test.essentialProperties.hasAutoFix = false;
DuSanity.Test.essentialProperties.timeOut = 300000;
DuSanity.Test.essentialProperties.testName = ""; // Added during init
DuSanity.Test.essentialProperties.options = {}; // Added during init

/**
 * Checks the elapsed time since last save
 * @param {boolean} [dontFix=false] If false, will automatically fix the issue.
 */
DuSanity.Test.save = function ( dontFix ) {
    dontFix = def(dontFix, false);

    var proj = app.project;
    var f = app.project.file;
    var limit = DuESF.settings.get("sanity/options/" + DuSanity.Test.save.stringId + "/timeout", 30) * 60000;

    if (!app.project.dirty) {
        DuSanity.Test.save.info = "";
        DuSanity.Test.save.tip = "";
        DuSanity.Test.save.currentLevel = DuSanity.Level.OK;
        return DuSanity.Test.save.currentLevel;
    }
    if (!(f instanceof File) && app.project.numItems > 0)
    {
        DuSanity.Test.save.info = "Not saved";
        DuSanity.Test.save.tip = "This project has not been saved yet. You should save it right now!";
        DuSanity.Test.save.currentLevel = DuSanity.Level.FATAL;
        return DuSanity.Test.save.currentLevel;
    }
    else if (!(f instanceof File))
    {
        DuSanity.Test.save.info = "";
        DuSanity.Test.save.tip = "";
        DuSanity.Test.save.currentLevel = DuSanity.Level.OK;
        return DuSanity.Test.save.currentLevel;
    }
    
    if (!f.exists)
    {
        DuSanity.Test.save.info = "Missing file";
        DuSanity.Test.save.tip = "It looks like the file for this project has disappeared. You should save it right now!";
        DuSanity.Test.save.currentLevel = DuSanity.Level.FATAL;
        return DuSanity.Test.save.currentLevel;
    }

    var date = f.modified.getTime();
    var now = new Date().getTime();
    var elapsed =  now - date;
    var elapsedStr = Math.round(elapsed/60000) + "mn";

    DuSanity.Test.save.info = elapsedStr;
    DuSanity.Test.save.tip = "You should save the project regularly.";
    DuSanity.Test.save.currentLevel = DuSanity.checkLevel(elapsed, limit);

    if (dontFix) return  DuSanity.Test.save.currentLevel;
    return DuSanity.fix( DuSanity.Test.save );
}
DuSanity.Test.save.lastRun = 0;
DuSanity.Test.save.stringId = 'save';
DuSanity.Test.save.info = '';
DuSanity.Test.save.tip = '';
DuSanity.Test.save.currentLevel = DuSanity.Level.UNKNOWN;
DuSanity.Test.save.enabled = true;
DuSanity.Test.save.id = 0;
DuSanity.Test.save.hasFix = true;
DuSanity.Test.save.hasAutoFix = true;
DuSanity.Test.save.timeOut = 900000;
DuSanity.Test.save.testName = ""; // Added during init
DuSanity.Test.save.options = {}; // Added during init
DuSanity.Test.save.fix = function() {
    app.project.save();
}

/**
 * Checks the up time of After Effects
 * @param {boolean} [dontFix=false] If false, will automatically fix the issue.
 */
DuSanity.Test.upTime = function ( dontFix ) {
    dontFix = def(dontFix, false);

    var limit = DuESF.settings.get("sanity/options/" + DuSanity.Test.upTime.stringId +"/timeout", 180);

    $.global.DuSan = def($.global.DuSan, {});

    $.global.DuSan.AEStartTime = def($.global.DuSan.AEStartTime, Date.now());
    // Check time elapsed and convert to minutes
    var elapsed = Date.now() - $.global.DuSan.AEStartTime;
    elapsed /= 60000;
    elapsed = Math.round(elapsed);
    
    DuSanity.Test.upTime.currentLevel = DuSanity.checkLevel(elapsed, limit);

    DuSanity.Test.upTime.info = elapsed + ' mn';
    DuSanity.Test.upTime.tip = "When After Effects stays up for too long, it gets tired.\n\nRestarting After Effects regularly helps keep the memory usage low and improves performance.";

    if (dontFix) return  DuSanity.Test.upTime.currentLevel;
    return DuSanity.fix( DuSanity.Test.upTime );
}
DuSanity.Test.upTime.lastRun = 0;
DuSanity.Test.upTime.stringId = 'upTime';
DuSanity.Test.upTime.info = '';
DuSanity.Test.upTime.tip = '';
DuSanity.Test.upTime.currentLevel = DuSanity.Level.UNKNOWN;
DuSanity.Test.upTime.enabled = true;
DuSanity.Test.upTime.id = 0;
DuSanity.Test.upTime.hasFix = true;
DuSanity.Test.upTime.hasAutoFix = false;
DuSanity.Test.upTime.timeOut = 900000;
DuSanity.Test.upTime.testName = ""; // Added during init
DuSanity.Test.upTime.options = {}; // Added during init
DuSanity.Test.upTime.fix = function() {
    app.quit();
}

// ==================== |----------| ====================
// ==================== | scriptui | ====================
// ==================== |----------| ====================

/**
 * UI tools to show sanity levels and settings
 * @namespace
 * @memberof DuSanity
 * @category DuSanity
 */
DuSanity.UI = {}

// The list of icons and labels in the ui, kept to update them
DuSanity.UI.items = [];

/**
 * Creates an icon to show the current sanity level
 * @param {Group} container A ScriptUI group where to add the icon
 * @param {boolean} [addLabel=true] Adds a label next to the icon
 * @param {boolean} [autoUpdate=true] If true, the icon will be automatically updated according to the current sanity level. Otherwise, call setLevel to change the level.
 * @return {Group} The ScriptUI Group containing the icon and its label
 */
DuSanity.UI.icon = function( container, addLabel, autoUpdate ) {
    addLabel = def(addLabel, true);
    autoUpdate = def(autoUpdate, true);

    // Add status
    var statusGroup = DuScriptUI.group(container, 'row');
    statusGroup.spacing = 3;

    var statusIconGroup = DuScriptUI.group(statusGroup, 'stacked');
    statusIconGroup.alignment = ['left', 'center'];

    var statusUnknownIcon = statusIconGroup.add('image', undefined, w12_check.binAsString );
    statusUnknownIcon.helpTip = i18n._("Sanity status: Unknown");
    var statusOKIcon = statusIconGroup.add('image', undefined, w12_check_g.binAsString );
    statusOKIcon.helpTip = i18n._("Sanity status: OK");
    var statusInfoIcon = statusIconGroup.add('image', undefined, w12_information.binAsString );
    statusInfoIcon.helpTip = i18n._("Sanity status: Information");
    var statusWarningIcon = statusIconGroup.add('image', undefined, w12_warning.binAsString );
    statusWarningIcon.helpTip = i18n._("Sanity status: Warning");
    var statusDangerIcon = statusIconGroup.add('image', undefined, w12_danger.binAsString );
    statusDangerIcon.helpTip = i18n._("Sanity status: Danger");
    var statusCriticalIcon = statusIconGroup.add('image', undefined, w12_critical.binAsString );
    statusCriticalIcon.helpTip = i18n._("Sanity status: Critical");
    var statusFatalIcon = statusIconGroup.add('image', undefined, w12_fatal.binAsString );
    statusFatalIcon.helpTip = i18n._("Sanity status: Fatal");

    statusIconGroup.setLevel = function( level ) {
        statusUnknownIcon.visible = level == DuSanity.Level.UNKNOWN;
        statusOKIcon.visible = level == DuSanity.Level.OK;
        statusInfoIcon.visible = level == DuSanity.Level.INFO;
        statusWarningIcon.visible = level == DuSanity.Level.WARNING;
        statusDangerIcon.visible = level == DuSanity.Level.DANGER;
        statusCriticalIcon.visible = level == DuSanity.Level.CRITICAL;
        statusFatalIcon.visible = level == DuSanity.Level.FATAL;
    };

    if (addLabel) {
        var statusLabel = DuScriptUI.staticText( statusGroup, {
            text: i18n._("OK"),
            color: DuColor.Color.LIGHT_GREEN
        });
        statusLabel.alignment = ['fill', 'center'];
        statusLabel.setLevel = function( level ) {
            if (level == DuSanity.Level.UNKNOWN) {
                statusLabel.text = i18n._("Unknown");
                DuScriptUI.setTextColor( statusLabel, DuColor.Color.APP_TEXT_COLOR.darker());
            }
            else if (level == DuSanity.Level.OK) {
                statusLabel.text = i18n._("OK");
                DuScriptUI.setTextColor( statusLabel, DuColor.Color.LIGHT_GREEN);
            }
            else if (level == DuSanity.Level.INFO) {
                statusLabel.text = i18n._("Information");
                DuScriptUI.setTextColor( statusLabel, DuColor.Color.LIGHT_BLUE);
            }
            else if (level == DuSanity.Level.WARNING) {
                statusLabel.text = i18n._("Warning");
                DuScriptUI.setTextColor( statusLabel, DuColor.Color.YELLOW);
            }
            else if (level == DuSanity.Level.DANGER) {
                statusLabel.text = i18n._("Danger");
                DuScriptUI.setTextColor( statusLabel, DuColor.Color.ORANGE);
            }
            else if (level == DuSanity.Level.CRITICAL) {
                statusLabel.text = i18n._("Critical");
                DuScriptUI.setTextColor( statusLabel, DuColor.Color.RAINBOX_RED);
            }
            else if (level == DuSanity.Level.FATAL) {
                statusLabel.text = i18n._("Fatal");
                DuScriptUI.setTextColor( statusLabel, DuColor.Color.RX_PURPLE);
            }
        };
    }

    statusGroup.setLevel = function( level ) {
        if (addLabel) statusLabel.setLevel( level );
        statusIconGroup.setLevel( level );
    }
    statusGroup.setInfo = function( info ) {
        if (!addLabel) return;
        if (info != "") statusLabel.text = statusLabel.text + ' - ' + info;
    }
    statusGroup.setHelpTip = function( tip ) {
        if (addLabel) statusLabel.helpTip = tip;
    }

    statusGroup.setLevel(DuSanity.Level.UNKNOWN);

    if (autoUpdate) DuSanity.UI.items.push(statusGroup);

    return statusGroup;
}

/**
 * Creates a button to show the current sanity level
 * @param {Group} container A ScriptUI group where to add the button
 * @param {boolean} [addLabel=true] Adds a label next to the icon
 * @param {boolean} [autoUpdate=true] If true, the button will be automatically updated according to the current sanity level. Otherwise, call setLevel to change the level.
 * @return {DuButton} The DuButton
 */
DuSanity.UI.button = function( container, addLabel, autoUpdate ) {
    addLabel = def(addLabel, true);
    autoUpdate = def(autoUpdate, true);

    var options = {};
    if (addLabel) options.text = i18n._("Unknown");
    else options.text = '';
    options.image = w12_check;
    options.helpTip = i18n._("Sanity status: Unknown");

    var button = DuScriptUI.button( container, options);

    button.setLevel = function( level ) {
        if (level == DuSanity.Level.UNKNOWN) {
            button.setText( i18n._("Unknown") );
            button.setTextColor( DuColor.Color.APP_TEXT_COLOR.darker());
            button.setHelpTip( i18n._("Sanity status: Unknown") );
            button.setImage( w12_check );
        }
        else if (level == DuSanity.Level.OK) {
            button.setText( i18n._("OK") );
            button.setTextColor( DuColor.Color.APP_TEXT_COLOR.LIGHT_GREEN);
            button.setHelpTip( i18n._("Sanity status: OK") );
            button.setImage( w12_check_g );
        }
        else if (level == DuSanity.Level.INFO) {
            button.setText( i18n._("Information") );
            button.setTextColor( DuColor.Color.APP_TEXT_COLOR.LIGHT_BLUE);
            button.setHelpTip( i18n._("Sanity status: Information") );
            button.setImage( w12_information );
        }
        else if (level == DuSanity.Level.WARNING) {
            button.setText( i18n._("Warning") );
            button.setTextColor( DuColor.Color.APP_TEXT_COLOR.YELLOW);
            button.setHelpTip( i18n._("Sanity status: Warning") );
            button.setImage( w12_warning );
        }
        else if (level == DuSanity.Level.DANGER) {
            button.setText( i18n._("Danger") );
            button.setTextColor( DuColor.Color.APP_TEXT_COLOR.ORANGE);
            button.setHelpTip( i18n._("Sanity status: Danger") );
            button.setImage( w12_danger );
        }
        else if (level == DuSanity.Level.CRITICAL) {
            button.setText( i18n._("Critical") );
            button.setTextColor( DuColor.Color.APP_TEXT_COLOR.RAINBOX_RED);
            button.setHelpTip( i18n._("Sanity status: Critical"));
            button.setImage( w12_critical );
        }
        else if (level == DuSanity.Level.FATAL) {
            button.setText( i18n._("Fatal") );
            button.setTextColor( DuColor.Color.APP_TEXT_COLOR.RX_PURPLE);
            button.setHelpTip( i18n._("Sanity status: Fatal") );
            button.setImage( w12_fatal);
        }
    };

    if (autoUpdate) DuSanity.UI.items.push(button);

    return button;
}

/**
 * Creates a panel showing all tests and current status
 * @param {Group} container The ScriptUI Group containing the DuSanity panel
 * @return {Group} The panel
 */
DuSanity.UI.panel = function(container) {
    // Add tests
    for (var k in DuSanity.Test) {
        if (DuSanity.Test.hasOwnProperty(k)) DuSanity.UI.test( container, DuSanity.Test[k] );
    }

    DuScriptUI.separator(container);
    // Run all button
    var runAllButton = DuScriptUI.button( container, {
        text: i18n._("Run all tests"),  /// TRANSLATORS: sanity test
        helpTip: i18n._("Run all the tests and displays the results."), /// TRANSLATORS: sanity test
        image: DuScriptUI.Icon.UPDATE,
        alignment: 'center',
        orientation: 'row'
    });
    runAllButton.alignment = ['fill', 'top'];
    runAllButton.onClick = function() { DuSanity.run(true); };
}

/**
 * Adds the UI to display a test in the UI
 * @param {Group} container A ScriptUI group where to add the test report
 * @param {DuSanity.Test} test The test to show
 * @return {Group} The ScriptUI Group containing the test report
 */
DuSanity.UI.test = function (container, test) {
    // Util: updates the UI for the current test
    function updateUI() {
        group.setChecked( DuSanity.isGloballyEnabled(test) );
        projectEnableButton.setChecked( DuSanity.isProjectEnabled(test) );
        liveFixButton.setChecked( DuSanity.isLiveFixEnabled(test) );

        if (!DuSanity.isEnabled(test))
        {
            statusIcon.setLevel(DuSanity.Level.UNKNOWN);
            return;
        }

        statusIcon.setLevel( test.currentLevel );
        statusIcon.setInfo( test.info );
        statusIcon.setHelpTip( test.tip );
    }

    var group = DuScriptUI.settingField( container, test.testName, 250 );
    group.onClick = function() {
        var c = group.checked;
        
        if (!c) projectEnableButton.setChecked(false);
        else projectEnableButton.setChecked( DuSanity.isProjectEnabled( test ) );
        projectEnableButton.enabled = c;

        DuSanity.setGloballyEnabled(test, c);
        DuSanity.setEnabled(test, c && projectEnableButton.checked);
        updateUI();
    };

    var projectEnableButton = DuScriptUI.checkBox(group, {
        text: '',
        image: w16_file_d,
        imageChecked: w16_file,
        helpTip: i18n._("Toggle this test for the current project only.") /// TRANSLATORS: sanity test
    });
    projectEnableButton.alignment = ['left', 'center'];
    projectEnableButton.onClick = function () {
        var c = projectEnableButton.checked;
        DuSanity.setProjectEnabled(test, c)
        DuSanity.setEnabled(test, c);
        updateUI();
    };

    var liveFixButton = DuScriptUI.checkBox(group, {
        text: '',
        image: w16_live_fix_d,
        imageChecked: w16_live_fix,
        helpTip: i18n._("Enable or disable automatic live-fix.") /// TRANSLATORS: (automatic)live fix for a sanity test
    });
    liveFixButton.alignment = ['left', 'center'];
    liveFixButton.visible = test.hasAutoFix;
    liveFixButton.onClick = function() {
        DuSanity.setLiveFixEnabled(test, liveFixButton.checked);
    };

    var fixButton = DuScriptUI.button(group, {
        text: '',
        image: w16_fix,
        helpTip: i18n._("Fix now.") /// TRANSLATORS: fix an issue detected by sanity tests
    });
    fixButton.alignment = ['left', 'center'];
    fixButton.visible = test.hasFix;
    fixButton.onClick = function() {
        test.fix();
        test();
        updateUI();
    };

    var statusIcon = DuSanity.UI.icon(group, true, false);
    statusIcon.alignment = ['fill', 'center'];

    var refreshButton = DuScriptUI.button( group, {
        text: '',
        image: DuScriptUI.Icon.UPDATE,
        helpTip: i18n._("Run the current test.") /// TRANSLATORS: sanity test
    });
    refreshButton.alignment = ['right', 'center'];
    refreshButton.onClick = function() { DuSanity.runTest( test, false, true ); updateUI(); };

    var optionsButton = DuScriptUI.button(group, {
        text: '',
        image: DuScriptUI.Icon.OPTIONS,
        helpTip: i18n._("Change the test settings.") /// TRANSLATORS: sanity test
    });
    optionsButton.alignment = ['right', 'center'];

    var optionsPopup = DuScriptUI.popUp( i18n._("Options") + ' (' + test.testName + ')');

    var timeG = DuScriptUI.group(optionsPopup.content, 'row');
    DuScriptUI.staticText(timeG, i18n._("Test every:")); /// TRANSLATORS: sanity test, label for duration between two tests
    var timeOut = DuESF.settings.get("sanity/timeOut" + test.stringId, test.timeOut);
    var timeOutStr = timeOut.toString();
    var unit = "ms";
    if (timeOut > 1000)
    {
        timeOutStr = (timeOut / 1000).toString();
        unit = "s";
    }
    if (timeOut > 60000)
    {
        timeOutStr = (timeOut / 60000).toString();
        unit = "mn";
    }
    var timeOutEdit = DuScriptUI.editText( timeG, {
        text: timeOutStr,
        suffix: ' ' + unit,
        localize: false
    });
    timeOutEdit.onChange = function()
    {
        var t = parseInt(timeOutEdit.text);
        if (unit == " s") t = t*1000;
        if (unit == " mn") t = t*60000;
        DuSanity.setTimeOut(test, t);
    };
    // Add custom options
    if (isdef(test.options)) {
        for (var o in test.options) {
            if (!test.options.hasOwnProperty(o)) continue;
            var option = test.options[o];
            var optG = DuScriptUI.group( optionsPopup.content, 'row');
            DuScriptUI.staticText(optG, option.description + ':');
            if (jstype(option.value) == 'number' || jstype(option.value) == 'string' ) {
                var optB = DuScriptUI.editText( optG, {
                    text: DuESF.settings.get("sanity/options/" + test.stringId + "/" + o, option.value).toString(),
                    placeHolder: i18n._("Default")
                });
                optB.onChange = function () {
                    DuESF.settings.set("sanity/options/" + test.stringId + "/" + o, optB.text);
                    DuESF.settings.save();
                    test();
                }
            }
            else if ( jstype(option.value) == 'boolean' )
            {
                var optB = DuScriptUI.simpleCheckBox(  optG );
                optB.onClick = function () {
                    DuESF.settings.set("sanity/options/" + test.stringId + "/" + o, optB.checked);
                    DuESF.settings.save();
                    test();
                }
            }
        }
    }

    optionsPopup.tieTo(optionsButton);

    //First run
    updateUI();
    //add to events
    DuScriptUI.addEvent(updateUI, 1000);
}

// ==================== |------| ====================
// ==================== | init | ====================
// ==================== |------| ====================


DuSanity.initialized = false;

/**
 * Runs all sanity tests
 * @param {boolean} [force=false] Force running all tests even if they've not timed out yet.
 */
DuSanity.run = function( force ) {
    if (!DuSanity.initialized) return;

    force = def(force, false);

    // Runs all tests and get the result
    DuSanity.currentLevel = DuSanity.Level.OK;

    var level = DuSanity.Level.UNKNOWN;
    for (var k in DuSanity.Test) {
        if (!DuSanity.Test.hasOwnProperty(k)) continue;
        var test = DuSanity.Test[k];
        if (DuSanity.isEnabled(test)) {
            var testLevel = test.currentLevel;
            testLevel = DuSanity.runTest(test, false, force);
            if (testLevel > level) level = testLevel;
        }
    }

    DuSanity.currentLevel = level;

    // Update UI
    for(var i = 0; i < DuSanity.UI.items.length; i++) {
        DuSanity.UI.items[i].setLevel( DuSanity.currentLevel );
    }

}

/**
 * This function must be called once when everything in the script is ready and after {@link DuAEF.init}
 */
DuSanity.init = function() {
    // A single global variable to keep track of Ae Uptime
    $.global.DuSan = def($.global.DuSan, {});
    $.global.DuSan.AEStartTime = def($.global.DuSan.AEStartTime, Date.now());

    // Add options (just now to translate the description)
    DuSanity.Test.projectSize.options = {
        sizeLimit: {
            value: 100,
            description: i18n._("Size limit (MB)")
        }
    };

    DuSanity.Test.projectItems.options = {
        itemsLimit: {
            value: 1000,
            description: i18n._("Maximum number of items")
        }
    };

    DuSanity.Test.precomps.options = {
        maxPrecompsAtRoot: {
            value: 0,
            description: i18n._("Maximum number of precompositions in the root folder")
        },
        precompsFolder: {
            value: "Precomps",
            description: i18n._("Default folder for precompositions")
        }
    };

    DuSanity.Test.unusedComps.options = {
        maxUnusedComps: {
            value: 0,
            description: i18n._("Maximum unused comps in the project")
        },
        mainCompsFolder: {
            value: "Project root",
            description: i18n._("Folder for main comps (leave empty for project root)")
        }
    };

    DuSanity.Test.memory.options = {
        memoryLimit: {
            value: 8,
            description: i18n._("Maximum memory (GB)")
        }
    };

    DuSanity.Test.essentialProperties.options = {
        maxEssentialProps: {
            value: 40,
            description: i18n._("Maximum number of essential properties in a comp")
        }
    };

    DuSanity.Test.save.options = {
        timeout: {
            value: 30,
            description: i18n._("Time limit before saving the project (mn)")
        }
    };

    DuSanity.Test.upTime.options = {
        timeout: {
            value: 180,
            description: i18n._("Uptime limit (mn)")
        }
    };

    // Add names
    DuSanity.Test.compNames.testName = i18n._('Composition Names');
    DuSanity.Test.layerNames.testName = i18n._('Layer Names');
    DuSanity.Test.expressionEngine.testName = i18n._('Expression engine');
    DuSanity.Test.projectSize.testName = i18n._('Project size');
    DuSanity.Test.projectItems.testName = i18n._('Project items');
    DuSanity.Test.itemSources.testName = i18n._('Duplicated footages');
    DuSanity.Test.unusedItems.testName = i18n._('Unused footages');
    DuSanity.Test.precomps.testName = i18n._('Precompositions');
    DuSanity.Test.unusedComps.testName = i18n._('Main compositions');
    DuSanity.Test.memory.testName = i18n._('Memory in use');
    DuSanity.Test.essentialProperties.testName = i18n._('Essential properties');
    DuSanity.Test.save.testName = i18n._('Time since last save');
    DuSanity.Test.upTime.testName = i18n._('Up time');
};
DuESF.initMethods.push( DuSanity.init );

DuSanity.enterRunTime = function() {

    DuDebug.log( "DuSanity: entering runtime..." );
    
    if (DuSanity.initialized) return;

    // Enable tests
    for (var k in DuSanity.Test) {
        if (!DuSanity.Test.hasOwnProperty(k)) continue;
        var test = DuSanity.Test[k];
        DuSanity.setEnabled(test, DuSanity.isProjectEnabled(test) && DuSanity.isGloballyEnabled(test));        
    }

    // first run
    DuDebug.log( "DuSanity: First run" );
    DuSanity.run(true);
    // Add event
    DuDebug.log( "DuSanity: Adding event..." );
    DuScriptUI.addEvent(DuSanity.run);

    DuSanity.initialized = true;

    DuDebug.log( "DuSanity: Runtime!" );
};
DuESF.enterRunTimeMethods.push( DuSanity.enterRunTime );
//*/