var expect = require('chai').expect;
var setup  = require(__dirname + '/lib/setup');

var objects = null;
var states  = null;

const stateChangedHandlers = new Set();
function onStateChanged(id, state) {
    stateChangedHandlers.forEach(handler => handler(id, state));
}
/**
 * Adds a state change handler for tests
 * @param {(id: string, state: any) => void} handler The handler callback for the state change
 */
function addStateChangedHandler(handler) {
    stateChangedHandlers.add(handler);
}
/**
 * Removes a state change handler from the list
 * @param {(id: string, state: any) => void} handler The handler callback to be removed
 */
function removeStateChangedHandler(handler) {
    stateChangedHandlers.delete(handler);
}
const objectChangedHandlers = new Set();
function onObjectChanged(id, obj) {
    objectChangedHandlers.forEach(handler => handler(id, obj));
}
/**
 * Adds an object change handler for tests
 * @param {(id: string, obj: any) => void} handler The handler callback for the state change
 */
function addObjectChangedHandler(handler) {
    objectChangedHandlers.add(handler);
}
/**
 * Removes an object change handler from the list
 * @param {(id: string, obj: any) => void} handler The handler callback to be removed
 */
function removeObjectChangedHandler(handler) {
    objectChangedHandlers.delete(handler);
}

function checkConnectionOfAdapter(cb, counter) {
    counter = counter || 0;
    if (counter > 20) {
        cb && cb('Cannot check connection');
        return;
    }

    states.getState('system.adapter.javascript.0.alive', function (err, state) {
        if (err) console.error(err);
        if (state && state.val) {
            cb && cb();
        } else {
            setTimeout(function () {
                checkConnectionOfAdapter(cb, counter + 1);
            }, 1000);
        }
    });
}

function checkValueOfState(id, value, cb, counter) {
    counter = counter || 0;
    if (counter > 20) {
        cb && cb('Cannot check value Of State ' + id);
        return;
    }

    states.getState(id, function (err, state) {
        if (err) console.error(err);
        if (value === null && !state) {
            cb && cb();
        } else
        if (state && (value === undefined || state.val === value)) {
            cb && cb();
        } else {
            setTimeout(function () {
                checkValueOfState(id, value, cb, counter + 1);
            }, 500);
        }
    });
}

describe('Test JS', function() {

    before('Test JS: Start js-controller', function (_done) {
        this.timeout(600000); // because of first install from npm

        setup.setupController(function () {
            var config = setup.getAdapterConfig();
            // enable adapter
            config.common.enabled  = true;
            config.common.loglevel = 'debug';

            config.native.longitude = 43.273709;
            config.native.latitude  = 6.5798918;

            setup.setAdapterConfig(config.common, config.native);

            setup.startController(false, function (id, obj) {
                    if (onObjectChanged) onObjectChanged(id, obj);
                }, function (id, state) {
                    if (onStateChanged) onStateChanged(id, state);
            },
            function (_objects, _states) {
                objects = _objects;
                states  = _states;
                states.subscribe('*');
                var script = {
                    "common": {
                        "name":         "new script global",
                        "engineType":   "Javascript/js",
                        "source":       "function setTestState(val) {\ncreateState('testGlobal', val, function () {\nsetState('testGlobal', val);\n});\n}",
                        "enabled":      true,
                        "engine":       "system.adapter.javascript.0"
                    },
                    "type":             "script",
                    "_id":              "script.js.global.TestGlobalNew.Script",
                    "native": {}
                };
                objects.setObject(script._id, script, function (err) {
                    expect(err).to.be.not.ok;
                    script = {
                        "common": {
                            "name": "Old script global",
                            "engineType": "Javascript/js",
                            "source": "function setTestStateOld(val) {\ncreateState('testGlobalOld', val, function () {\nsetState('testGlobalOld', val);\n});\n}",
                            "enabled": true,
                            "engine": "system.adapter.javascript.0"
                        },
                        "type": "script",
                        "_id": "script.js.global.TestGlobalOld.Script",
                        "native": {}
                    };
                    objects.setObject(script._id, script, function (err) {
                        expect(err).to.be.not.ok;
                        setup.startAdapter(objects, states, function () {
                            _done();
                        });
                    });
                });
            });
        });
    });

    it('Test JS: Check if adapter started', function (done) {
        this.timeout(30000);
        checkConnectionOfAdapter(done);
    });

    it('Test JS: check compareTime between', function (done) {
        this.timeout(10000);
        // add script
        var script = {
            "common": {
                "name":         "check compareTime",
                "engineType":   "Javascript/js",
                "source":       "createState('test10', 0, function () {\n" +
                "   var count = 0;\n" +
/*                "   var date1 = new Date();\n" +
                "   date1.setHours(23);\n" +
                "   date1.setMinutes(30);\n" +
                "   var date2 = new Date();\n" +
                "   date2.setHours(0);\n" +
                "   date2.setMinutes(30);\n" +
                "   count += compareTime('23:00', '01:00', 'between', date1) ? 1 : 0;\n" +
                "   count += compareTime('23:00', '01:00', 'between', date2) ? 1 : 0;\n" +
*/
                "   count += compareTime('23:00', '01:00', 'between', '22:30') ? 0 : 1;\n" +
                "   count += compareTime('23:00', '01:00', 'between', '02:30') ? 0 : 1;\n" +
                "   count += compareTime('10:00', '20:00', 'between', '15:00') ? 1 : 0;\n" +
                "   count += compareTime('10:00', '20:00', 'between', '9:00') ? 0 : 1;\n" +
                "   count += compareTime('10:00', null, '<', '9:00') ? 1 : 0;\n" +
                "   var date1 = new Date();\n" +
                "   date1.setHours(10);\n" +
                "   date1.setMinutes(0);\n" +
                "   count += compareTime(date1, null, '<', '9:00') ? 1 : 0;\n" +
                "   count += compareTime(date1, '20:00', 'between', '15:00') ? 1 : 0;\n" +
                "   count += compareTime('5:00', date1, 'between', '8:00') ? 1 : 0;\n" +
                "   var date2 = new Date(new Date().getTime()+ 24*60*60*1000);\n" +
                "   date2.setHours(2);\n" +
                "   date2.setMinutes(30);\n" +
                "   count += compareTime('23:00', '01:00', 'between', date2) ? 0 : 1;\n" +
                "   setState('test10', count);\n" +
                "});",
                "enabled":      true,
                "engine":       "system.adapter.javascript.0"
            },
            "type":             "script",
            "_id":              "script.js.check_compareTime",
            "native": {}
        };
        const onStateChanged = function (id, state) {
            if (id === 'javascript.0.test10') {
                if (state.val === 9) {
                    removeStateChangedHandler(onStateChanged);
                    states.getState('javascript.0.test10', function (err, state) {
                        expect(err).to.be.not.ok;
                        expect(state.val).to.be.equal(9);
                        done();
                    });
                }
                else {
                    console.log('GOT State.val =' + state.val);
                }
            }
        };
        addStateChangedHandler(onStateChanged);

        objects.setObject(script._id, script, function (err) {
            expect(err).to.be.not.ok;
        });
    });

    it('Test JS: Catch request error', function (done) {
        this.timeout(10000);
        // add script
        var script = {
            "common": {
                "name":         "Catch request error",
                "engineType":   "Javascript/js",
                "source":       "var request = require('request');" +
                                "createState('check_request_error', function () {" +
                                "   request('http://google1456.com').on('error', function (error) { " +
                                "       console.error(error); setState('check_request_error', true, true);" +
                                "   });" +
                                "});",
                "enabled":      true,
                "engine":       "system.adapter.javascript.0"
            },
            "type":             "script",
            "_id":              "script.js.check_request_error",
            "native": {}
        };
        const onStateChanged = function (id, state) {
            if (id === 'javascript.0.check_request_error' && state.val === true) {
                removeStateChangedHandler(onStateChanged);
                done();
            }
        };
        addStateChangedHandler(onStateChanged);

        objects.setObject(script._id, script, function (err) {
            expect(err).to.be.not.ok;
        });
    });

    it('Test JS: check creation of state', function (done) {
        this.timeout(2000);
        // add script
        var script = {
            "common": {
                "name":         "check creation of state",
                "engineType":   "Javascript/js",
                "source":       "createState('test1', 5);",
                "enabled":      true,
                "engine":       "system.adapter.javascript.0"
            },
            "type":             "script",
            "_id":              "script.js.check_creation_of_state",
            "native": {}
        };
        const onStateChanged = function (id, state) {
            if (id === 'javascript.0.test1' && state.val === 5) {
                removeStateChangedHandler(onStateChanged);
                states.getState('javascript.0.test1', function (err, state) {
                    expect(err).to.be.not.ok;
                    expect(state.val).to.be.equal(5);
                    objects.getObject('javascript.0.test1', function (err, obj) {
                        expect(err).to.be.not.ok;
                        expect(obj).to.be.ok;

                        done();
                    });
                });
            }
        };
        addStateChangedHandler(onStateChanged);
        objects.setObject(script._id, script, function (err) {
            expect(err).to.be.not.ok;
        });
    });

    it('Test JS: check creation of state for other instance', function (done) {
        this.timeout(2000);
        // add script
        var script = {
            "common": {
                "name":         "check creation of state",
                "engineType":   "Javascript/js",
                "source":       "createState('javascript.1.test1', 6);",
                "enabled":      true,
                "engine":       "system.adapter.javascript.0"
            },
            "type":             "script",
            "_id":              "script.js.check_creation_of_foreign_state",
            "native": {}
        };
        const onStateChanged = function (id, state) {
            if (id === 'javascript.1.test1' && state.val === 6) {
                removeStateChangedHandler(onStateChanged);
                states.getState('javascript.1.test1', function (err, state) {
                    expect(err).to.be.not.ok;
                    expect(state).to.be.ok;
                    expect(state.val).to.be.equal(6);
                    objects.getObject('javascript.1.test1', function (err, obj) {
                        expect(err).to.be.not.ok;
                        expect(obj).to.be.ok;
                        done();
                    });
                });
            }
        };
        addStateChangedHandler(onStateChanged);
        objects.setObject(script._id, script, function (err) {
            expect(err).to.be.not.ok;
        });
    });

    it('Test JS: check deletion of state', function (done) {
        this.timeout(2000);
        // add script
        var script = {
            "common": {
                "name":         "check deletion of state",
                "engineType":   "Javascript/js",
                "source":       "deleteState('test1');",
                "enabled":      true,
                "engine":       "system.adapter.javascript.0"
            },
            "type":             "script",
            "_id":              "script.js.check_deletion_of_state",
            "native": {}
        };

        objects.getObject('javascript.0.test1', function (err, obj) {
            expect(err).to.be.not.ok;
            expect(obj).to.be.ok;
            states.getState('javascript.0.test1', function (err, state) {
                expect(err).to.be.not.ok;
                expect(state).to.be.ok;
                expect(state.val).to.be.equal(5);

                const onStateChanged = function (id, state) {
                    if (id === 'javascript.0.test1' && state === null) {
                        removeStateChangedHandler(onStateChanged);
                        states.getState('javascript.0.test1', function (err, state) {
                            expect(err).to.be.not.ok;
                            expect(state).to.be.equal(undefined);
                            objects.getObject('javascript.0.test1', function (err, obj) {
                                expect(err).to.be.not.ok;
                                expect(obj).to.be.not.ok;
                                done();
                            });
                        });
                    }
                };
                addStateChangedHandler(onStateChanged);

                objects.setObject(script._id, script, function (err) {
                    expect(err).to.be.not.ok;
                });
            });
        });
    });

    it('Test JS: check deletion of foreign state', function (done) {
        this.timeout(2000);
        // add script
        var script = {
            "common": {
                "name":         "check deletion of state",
                "engineType":   "Javascript/js",
                "source":       "deleteState('javascript.1.test1');",
                "enabled":      true,
                "engine":       "system.adapter.javascript.0"
            },
            "type":             "script",
            "_id":              "script.js.check_deletion_of_foreign_state",
            "native": {}
        };

        objects.getObject('javascript.1.test1', function (err, obj) {
            expect(err).to.be.not.ok;
            expect(obj).to.be.ok;
            states.getState('javascript.1.test1', function (err, state) {
                expect(err).to.be.not.ok;
                expect(state).to.be.ok;
                expect(state.val).to.be.equal(6);

                // we cannot delete foreign object, even if we created it.
                setTimeout(function () {
                    objects.getObject('javascript.1.test1', function (err, obj) {
                        expect(err).to.be.not.ok;
                        expect(obj).to.be.ok;
                        states.getState('javascript.1.test1', function (err, state) {
                            expect(err).to.be.not.ok;
                            expect(state).to.be.ok;
                            expect(state.val).to.be.equal(6);
                            done();
                        });
                    });
                }, 400);

                objects.setObject(script._id, script, function (err) {
                    expect(err).to.be.not.ok;
                });
            });
        });
    });

    it('Test JS: open objects.json file must not work', function (done) {
        this.timeout(20000);
        // add script
        var script = {
            "common": {
                "name":         "open objects",
                "engineType":   "Javascript/js",
                "source":       "var fs=require('fs'); try{fs.readFileSync('" + __dirname + "/../tmp/" + setup.appName + "-data/objects.json');}catch(err){createState('error', err.toString());}",
                "enabled":      true,
                "engine":       "system.adapter.javascript.0"
            },
            "type":             "script",
            "_id":              "script.js.open_objects",
            "native": {}
        };

        const onStateChanged = function (id, state) {
            if (id === 'javascript.0.error' && state.val === 'Error: Permission denied') {
                removeStateChangedHandler(onStateChanged);
                done();
            }
        };
        addStateChangedHandler(onStateChanged);

        objects.setObject(script._id, script, function (err) {
            expect(err).to.be.not.ok;
        });
    });

    it('Test JS: write objects.json file must not work', function (done) {
        this.timeout(2000);
        // add script
        var script = {
            "common": {
                "name":         "open objects",
                "engineType":   "Javascript/js",
                "source":       "var fs=require('fs'); try{fs.writeFileSync('" + __dirname + "/../tmp/" + setup.appName + "-data/objects.json', '');}catch(err){createState('error1', err.toString());}",
                "enabled":      true,
                "engine":       "system.adapter.javascript.0"
            },
            "type":             "script",
            "_id":              "script.js.open_objects",
            "native": {}
        };
        const onStateChanged = function (id, state) {
            if (id === 'javascript.0.error1' && state.val === 'Error: Permission denied') {
                removeStateChangedHandler(onStateChanged);
                done();
            }
        };
        addStateChangedHandler(onStateChanged);
        objects.setObject(script._id, script, function (err) {
            expect(err).to.be.not.ok;
        });
    });

    it('Test JS: write objects.json not in data directory must work', function (done) {
        this.timeout(2000);
        var time = new Date().toString();
        var fs = require('fs');

        if (fs.existsSync(__dirname + "/../tmp/objects.json")) fs.unlinkSync(__dirname + "/../tmp/objects.json");

        // add script
        var script = {
            "common": {
                "name":         "open objects",
                "engineType":   "Javascript/js",
                "source":       "var fs=require('fs'); try{fs.writeFileSync('" + __dirname.replace(/\\/g, "/") + "/../tmp/objects.json', '" + time + "');}catch(err){createState('error3', err.toString());}",
                "enabled":      true,
                "engine":       "system.adapter.javascript.0"
            },
            "type":             "script",
            "_id":              "script.js.open_objects",
            "native": {}
        };

        objects.setObject(script._id, script, function (err) {
            expect(err).to.be.not.ok;
            setTimeout(function () {
                if (!fs.existsSync(__dirname + '/../tmp/objects.json')) {
                    setTimeout(function () {
                        expect(fs.readFileSync(__dirname + "/../tmp/objects.json").toString()).to.be.equal(time);
                        fs.unlinkSync(__dirname + "/../tmp/objects.json");
                        done();
                    }, 500);
                } else {
                    expect(fs.readFileSync(__dirname + "/../tmp/objects.json").toString()).to.be.equal(time);
                    fs.unlinkSync(__dirname + "/../tmp/objects.json");
                    done();
                }
            }, 500);
        });
    });

    it('Test JS: test getAstroDate', function (done) {
        this.timeout(6000);
        var types = [
            "sunrise",
            "sunriseEnd",
            "goldenHourEnd",
            "solarNoon",
            "goldenHour",
            "sunsetStart",
            "sunset",
            "dusk",
            "nauticalDusk",
            "night",
            "nightEnd",
            "nauticalDawn",
            "dawn",
            "nadir"
        ];
        // add script
        var script = {
            "common": {
                "name":         "getAstroDate",
                "engineType":   "Javascript/js",
                "source":       "",
                "enabled":      true,
                "engine":       "system.adapter.javascript.0"
            },
            "type":             "script",
            "_id":              "script.js.getAstroDate",
            "native": {}
        };
        for (var t = 0; t < types.length; t++) {
            script.common.source += "createState('" + types[t] + "', getAstroDate('" + types[t] + "') ? getAstroDate('" + types[t] + "').toString() : '');"
        }

        var typesChanged = {};
        const onStateChanged = function (id, state) {
            if (types.indexOf(id.substring('javascript.0.'.length)) !== -1) {
                typesChanged[id] = true;
                console.log('State change '+ id + ' / ' + Object.keys(typesChanged).length + '-' + types.length + '  = ' + JSON.stringify(state))
                if (Object.keys(typesChanged).length === types.length) {
                    removeStateChangedHandler(onStateChanged);

                    var count = types.length;
                    for (var t = 0; t < types.length; t++) {
                        states.getState('javascript.0.' + types[t], function (err, state) {
                            expect(err).to.be.not.ok;
                            expect(state).to.be.ok;
                            expect(state.val).to.be.ok;
                            if (state) console.log(types[types.length - count] + ': ' + state.val);
                              else console.log(types[types.length - count] + ' ERROR: ' + state);
                            if (!--count) done();
                        });
                    }
                }
            }
        };
        addStateChangedHandler(onStateChanged);

        objects.setObject(script._id, script, function (err) {
            expect(err).to.be.not.ok;
        });
    });

    it('Test JS: test setStateDelayed simple', function (done) {
        this.timeout(5000);
        // add script
        var script = {
            "common": {
                "name":         "setStateDelayed",
                "engineType":   "Javascript/js",
                "source":       "createState('delayed', 4, function () {setStateDelayed('delayed', 5, 1000);});",
                "enabled":      true,
                "engine":       "system.adapter.javascript.0"
            },
            "type":             "script",
            "_id":              "script.js.setStateDelayed",
            "native": {}
        };

        var start = 0;
        const onStateChanged = function (id, state){
            if (id !== 'javascript.0.delayed') return;
            if (state.val === 4) {
                start = state.ts;
            } else if (state.val === 5) {
                expect(start).to.be.not.equal(0);
                expect(state.ts - start).to.be.least(950);
                removeStateChangedHandler(onStateChanged);
                setTimeout(done, 100);
            }
        };
        addStateChangedHandler(onStateChanged);
        objects.setObject(script._id, script);
    });

    it('Test JS: test setStateDelayed nested', function (done) {
        this.timeout(5000);
        // add script
        var script = {
            "common": {
                "name":         "setStateDelayed",
                "engineType":   "Javascript/js",
                "source":       "setStateDelayed('delayed', 6, 500); setStateDelayed('delayed', 7, 1500, false);",
                "enabled":      true,
                "engine":       "system.adapter.javascript.0"
            },
            "type":             "script",
            "_id":              "script.js.setStateDelayed",
            "native": {}
        };

        var start = 0;
        const onStateChanged = function (id, state){
            if (id !== 'javascript.0.delayed') return;
            if (state.val === 6) {
                start = state.ts;
            } else if (state.val === 7) {
                expect(start).to.be.not.equal(0);
                expect(state.ts - start).to.be.least(900);
                removeStateChangedHandler(onStateChanged);
                setTimeout(done, 100);
            }
        };
        addStateChangedHandler(onStateChanged);
        objects.setObject(script._id, script);
    });

    it('Test JS: test setStateDelayed overwritten', function (done) {
        this.timeout(5000);
        // add script
        var script = {
            "common": {
                "name":         "setStateDelayed",
                "engineType":   "Javascript/js",
                "source":       "setStateDelayed('delayed', 8, 500); setStateDelayed('delayed', 9, 1500);",
                "enabled":      true,
                "engine":       "system.adapter.javascript.0"
            },
            "type":             "script",
            "_id":              "script.js.setStateDelayed",
            "native": {}
        };

        objects.setObject(script._id, script, function (err) {
            expect(err).to.be.not.ok;
            checkValueOfState('javascript.0.delayed', 8, function (err) {
                expect(err).to.be.ok;

                states.getState('javascript.0.delayed', function (err, stateStart) {
                    expect(err).to.be.not.ok;
                    expect(stateStart.val).to.be.not.equal(8);

                    checkValueOfState('javascript.0.delayed', 9, function (err) {
                        expect(err).to.be.not.ok;
                        states.getState('javascript.0.delayed', function (err, stateStop) {
                            expect(err).to.be.not.ok;
                            done();
                        });
                    });
                });
            }, 18);
        });
    });

    it('Test JS: test setStateDelayed canceled', function (done) {
        this.timeout(5000);
        // add script
        var script = {
            "common": {
                "name":         "setStateDelayed",
                "engineType":   "Javascript/js",
                "source":       "setStateDelayed('delayed', 10, 500); clearStateDelayed('delayed');",
                "enabled":      true,
                "engine":       "system.adapter.javascript.0"
            },
            "type":             "script",
            "_id":              "script.js.setStateDelayed",
            "native": {}
        };

        objects.setObject(script._id, script, function (err) {
            expect(err).to.be.not.ok;

            checkValueOfState('javascript.0.delayed', 10, function (err) {
                expect(err).to.be.ok;

                states.getState('javascript.0.delayed', function (err, stateStart) {
                    expect(err).to.be.not.ok;
                    expect(stateStart.val).to.be.not.equal(10);
                    done();
                });
            }, 18);
        });
    });

	it('Test JS: test getStateDelayed single', function (done) {
        this.timeout(5000);
        // add script
        var script = {
            "common": {
                "name":         "setStateDelayed",
                "engineType":   "Javascript/js",
                "source":       "createState('delayedResult', '', function () {setStateDelayed('delayed', 10, 1500); setState('delayedResult', JSON.stringify(getStateDelayed('delayed')));});",
                "enabled":      true,
                "engine":       "system.adapter.javascript.0"
            },
            "type":             "script",
            "_id":              "script.js.setStateDelayed",
            "native": {}
        };

        objects.setObject(script._id, script, function (err) {
            expect(err).to.be.not.ok;

			setTimeout(function () {
				states.getState('javascript.0.delayedResult', function (err, delayedResult) {
					expect(err).to.be.not.ok;
					console.log('delayedResult: ' + delayedResult.val);
					var result = JSON.parse(delayedResult.val);
					expect(result[0]).to.be.ok;
					expect(result[0].timerId).to.be.ok;
					expect(result[0].left).to.be.ok;
					expect(result[0].delay).to.be.equal(1500);
					done();
				});
			}, 500);
        });
    });

	it('Test JS: test getStateDelayed all', function (done) {
        this.timeout(5000);
        // add script
        var script = {
            "common": {
                "name":         "setStateDelayed",
                "engineType":   "Javascript/js",
                "source":       "createState('delayedResult', '', function () {setStateDelayed('delayed', 11, 2500); setState('delayedResult', JSON.stringify(getStateDelayed()));});",
                "enabled":      true,
                "engine":       "system.adapter.javascript.0"
            },
            "type":             "script",
            "_id":              "script.js.setStateDelayed",
            "native": {}
        };

        objects.setObject(script._id, script, function (err) {
            expect(err).to.be.not.ok;

			setTimeout(function () {
				states.getState('javascript.0.delayedResult', function (err, delayedResult) {
					console.log('delayedResult!: ' + delayedResult.val);
					expect(err).to.be.not.ok;
					var result = JSON.parse(delayedResult.val);
					expect(result['javascript.0.delayed'][0]).to.be.ok;
					expect(result['javascript.0.delayed'][0].timerId).to.be.ok;
					expect(result['javascript.0.delayed'][0].left).to.be.ok;
					expect(result['javascript.0.delayed'][0].delay).to.be.equal(2500);
					done();
				});
			}, 500);
        });
    });

    it('Test JS: test stopScript', function (done) {
        this.timeout(5000);
        // add script
        var script = {
            "common": {
                "name":         "stopScript",
                "engineType":   "Javascript/js",
                "source":       "stopScript('stopScript');",
                "enabled":      true,
                "engine":       "system.adapter.javascript.0"
            },
            "type":             "script",
            "_id":              "script.js.stopScript",
            "native": {}
        };

        objects.setObject(script._id, script, function (err) {
            expect(err).to.be.not.ok;
            setTimeout(function () {
                objects.getObject(script._id, function (err, obj) {
                    expect(err).to.be.not.ok;
                    expect(obj.common.enabled).to.be.false;
                    done();
                });
            }, 1000);
        });
    });

    it('Test JS: test startScript', function (done) {
        this.timeout(5000);
        // add script
        var script = {
            "common": {
                "name":         "startScript",
                "engineType":   "Javascript/js",
                "source":       "startScript('stopScript');",
                "enabled":      true,
                "engine":       "system.adapter.javascript.0"
            },
            "type":             "script",
            "_id":              "script.js.startScript",
            "native": {}
        };
        var stopScript = {
            "common": {
                "name":         "stopScript",
                "engineType":   "Javascript/js",
                "source":       "console.log('aaa');",
                "enabled":      false,
                "engine":       "system.adapter.javascript.0"
            },
            "type":             "script",
            "_id":              "script.js.stopScript",
            "native": {}
        };

        objects.setObject(stopScript._id, stopScript, function (err) {
            objects.getObject(stopScript._id, function (err, obj) {
                expect(err).to.be.not.ok;
                expect(obj.common.enabled).to.be.false;
                objects.setObject(script._id, script, function (err) {
                    expect(err).to.be.not.ok;
                    setTimeout(function () {
                        objects.getObject(stopScript._id, function (err, obj) {
                            expect(err).to.be.not.ok;
                            expect(obj.common.enabled).to.be.true;
                            done();
                        });
                    }, 1000);
                });
            });
        });
    });

    it('Test JS: test global scripts New', function (done) {
        this.timeout(5000);
        // add script
        var script = {
            "common": {
                "name":         "new script non global",
                "engineType":   "Javascript/js",
                "source":       "setTestState(16);",
                "enabled":      true,
                "engine":       "system.adapter.javascript.0"
            },
            "type":             "script",
            "_id":              "script.js.TestGlobalNew.Script",
            "native": {}
        };
        objects.setObject(script._id, script, function (err) {
            expect(err).to.be.not.ok;
            checkValueOfState('javascript.0.testGlobal', 16, function (err) {
                expect(err).to.be.not.ok;

                states.getState('javascript.0.testGlobal', function (err, state) {
                    expect(err).to.be.not.ok;
                    expect(state).to.be.ok;
                    expect(state.val).to.be.equal(16);
                    done();
                });
            }, 18);
        });
    });

    it('Test JS: test global scripts Old', function (done) {
        this.timeout(5000);
        // add script
         var script = {
            "common": {
                "name":         "Old script non global",
                "engineType":   "Javascript/js",
                "source":       "setTestStateOld(17);",
                "enabled":      true,
                "engine":       "system.adapter.javascript.0"
            },
            "type":             "script",
            "_id":              "script.js.TestGlobalOld.Script",
            "native": {}
        };
        objects.setObject(script._id, script, function (err) {
            expect(err).to.be.not.ok;
            checkValueOfState('javascript.0.testGlobalOld', 17, function (err) {
                expect(err).to.be.not.ok;

                states.getState('javascript.0.testGlobalOld', function (err, state) {
                    expect(err).to.be.not.ok;
                    expect(state).to.be.ok;
                    expect(state.val).to.be.equal(17);
                    done();
                });
            }, 18);
        });
    });

    it('Test JS: test ON default', function (done) {
        this.timeout(5000);
        // add script
        var script = {
            "common": {
                "name":         "test ON default",
                "engineType":   "Javascript/js",
                "source":       "createState('testResponse', false);createState('testVar', 0, function () {on('testVar', function (obj) {setState('testResponse', obj.state.val, true);});});",
                "enabled":      true,
                "engine":       "system.adapter.javascript.0"
            },
            "type":             "script",
            "_id":              "script.js.test_ON_default",
            "native": {}
        };

        const onStateChanged = function (id, state) {
            if (id === 'javascript.0.testVar' && state.val === 0) {
                states.setState('javascript.0.testVar', 6, function (err) {
                    expect(err).to.be.not.ok;
                });
            }
            if (id === 'javascript.0.testResponse' && state.val === 6) {
                removeStateChangedHandler(onStateChanged);
                done();
            }
        };
        addStateChangedHandler(onStateChanged);

        objects.setObject(script._id, script, function (err) {
            expect(err).to.be.not.ok;
        });
    });

    it('Test JS: test ON any', function (done) {
        this.timeout(5000);
        // add script
        var script = {
            "common": {
                "name":         "test ON any",
                "engineType":   "Javascript/js",
                "source":       "createState('testResponse1', false);createState('testVar1', 1, function () {on({id:'testVar1', change:'any'}, function (obj) {setState('testResponse1', obj.state.val, true);});});",
                "enabled":      true,
                "engine":       "system.adapter.javascript.0"
            },
            "type":             "script",
            "_id":              "script.js.test_ON_any",
            "native": {}
        };

        const onStateChanged = function (id, state) {
            if (id === 'javascript.0.testVar1' && state.val === 1) {
                setTimeout(function () {
                    states.setState('javascript.0.testVar1', 1, function (err) {
                        expect(err).to.be.not.ok;
                    });
                }, 1000);
            }
            if (id === 'javascript.0.testResponse1' && state.val === 1) {
                removeStateChangedHandler(onStateChanged);
                done();
            }
        };
        addStateChangedHandler(onStateChanged);

        objects.setObject(script._id, script, function (err) {
            expect(err).to.be.not.ok;
        });
    });

    it('Test JS: test ON misc', function (done) {

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        function scriptFunction (param) {
            var results = '';
            var TEST_VAR = 'javascript.0.device.channel.testVar';
            var TEST_RESULTS = 'javascript.0.testResults';

            var recs = [
                // request Options                                 // on options or iD                       // states to set
                [ { no:  1, cnt: 2, val: true },                   { id: /\.testVar$/, val: true },                            [ true, false, { val: true, ack: true } ] ],
                [ { no:  2, cnt: 2, val: true },                   { id: 0, val: true },                                       [ true, false, { val: true, ack: true } ] ],
                [ { no:  3, cnt: 2, val: false },                  { id: 0, val: false },                                      [ true, false, { val: true, ack: true }, { val: false, ack: true } ] ],
                [ { no:  4, cnt: 1, val: {val: true, ack: true }}, { id: 0, val: true, ack: true },                            [ true, false, { val: true, ack: true }, { val: false, ack: true } ] ],
                [ { no:  5, cnt: 1, val: {val:false, ack: true }}, { id: 0, val: false, ack: true },                           [ true, false, { val: true, ack: true }, { val: false, ack: true } ] ],
                [ { no:  6, cnt: 1, val: true },                   { id: 0, change: 'ne' },                                    [ false, true, true ]],
                [ { no:  7, cnt: 2, val: true },                   { id: 0, change: 'any' },                                   [ true, true ]],
                [ { no:  8, cnt: 1, val: true },                   { id: 0, change: 'gt' },                                    [ false, true, true ]],
                [ { no:  9, cnt: 2, val: true },                   { id: 0, change: 'eq' },                                    [ true, true, true, false ]],
                [ { no: 10, cnt: 1, val: 'World' },                { name: 'Hello', change: 'gt' },                            ['Change', 'World', 'World'] ],
                [ { no: 11, cnt: 0, val: 'World' },                { name: 'hello', change: 'gt' },                            ['Change', 'World', 'World'] ],
                [ { no: 12, cnt: 1, val: 'World' },                { name: /^[h|H]ello/, change: 'any' },                      ['World'] ],

                [ { no: 13, cnt: 1, val: 'B' },                    { id: 0, valGt: 'A' },                                      [ 'B', 'A'] ],
                [ { no: 14, cnt: 2, val: 'B' },                    { id: 0, valGe: 'A' },                                      [ 'B', 'B'] ],
                [ { no: 15, cnt: 1, val: 'B' },                    { id: 0, valGe: 'B' },                                      [ 'B', 'A'] ],
                [ { no: 16, cnt: 1, val: 'A' },                    { id: 0, valLt: 'B' },                                      [ 'A', 'C'] ],
                [ { no: 17, cnt: 1, val: 'A' },                    { id: 0, valLe: 'A' },                                      [ 'A', 'B'] ],
                [ { no: 18, cnt: 1, val: 'B' },                    { id: 0, valNe: 'A' },                                      [ 'B', 'A'] ],
                [ { no: 19, cnt: 1, val: 'onChannel' },            { channelId: 'javascript.0.device.channel' },                      [ 'onChannel'] ],
                [ { no: 20, cnt: 1, val: 'onChannel'},             { channelId: 'javascript.0.device.channel', val: 'onChannel' },    [ 'onChannel', 'xyz'] ],
                [ { no: 21, cnt: 1, val: 'onChannel'},             { channelName: 'Channel' },                                        [ 'onChannel'] ],
                [ { no: 22, cnt: 1, val: 'onChannel'},             { channelName: 'Channel', val: 'onChannel' },                      [ 'onChannel', 'xyz'] ],
                [ { no: 23, cnt: 1, val: 'onDevice'},              { deviceId: 'javascript.0.device' },                               [ 'onDevice'] ],
                [ { no: 24, cnt: 1, val: 'onDevice'},              { deviceId: 'javascript.0.device', val: 'onDevice' },              [ 'onDevice', 'xyz'] ],
                [ { no: 25, cnt: 1, val: 'onDevice'},              { deviceName: 'Device' },                                          [ 'onDevice'] ],
                [ { no: 26, cnt: 1, val: 'onDevice'},              { deviceName: 'Device', val: 'onDevice' },                         [ 'onDevice', 'xyz'] ],

                [ { no: 27, cnt: 1, val: 1, before: false },       { id:0, oldVal: false },                                        [ 1, 1 ] ],
                [ { no: 28, cnt: 1, val: 1, before: 2 },           { id:0, oldValGt: 1 },                                          [ 1, 1 ] ],
                [ { no: 29, cnt: 2, val: 1, before: 2 },           { id:0, oldValGe: 1 },                                          [ 1, 1 ] ],
                [ { no: 30, cnt: 1, before: 2 },                   { id:0, oldValNe: 1 },                                          [ 1, 0 ] ],
                [ { no: 31, cnt: 1, before: 0 },                   { id:0, oldValLt: 1 },                                          [ 1, 0 ] ],
                [ { no: 32, cnt: 2, before: 0 },                   { id:0, oldValLe: 1 },                                          [ 1, 2, 0] ],

                [ { no: 33, cnt: 1, val: 1 },                      { id:0, tsGt: 1 },                                              [ 1 ] ],
                [ { no: 34, cnt: 0 },                              { id:0, tsGt: 0xfffffffffff },                                  [ 1 ] ],
                [ { no: 35, cnt: 1, val: 1 },                      { id:0, tsLt: 0xfffffffffff },                                  [ 1 ] ],
                [ { no: 36, cnt: 0 },                              { id:0, tsLt: 1 },                                              [ 1 ] ],
                [ { no: 37, cnt: 1, val: 1 },                      { id:0, oldTsGt: 1 },                                           [ 1 ] ],
                [ { no: 38, cnt: 0 },                              { id:0, oldTsGt: 0xfffffffffff },                               [ 1 ] ],
                [ { no: 39, cnt: 1, val: 1 },                      { id:0, oldTsLt: 0xfffffffffff },                               [ 1 ] ],
                [ { no: 40, cnt: 0 },                              { id:0, oldTsLt: 1 },                                           [ 1 ] ],
                [ { no: 41, cnt: 1, val: 1 },                      { id:0, lcGt: 1 },                                              [ 1 ] ],
                [ { no: 42, cnt: 1, val: 1 },                      { id:0, lcLt: 0xfffffffffff },                                  [ 1 ] ],
                [ { no: 43, cnt: 0 },                              { id:0, lcLt: 1 },                                              [ 1 ] ],
                [ { no: 44, cnt: 1, val: 1 },                      { id:0, oldLcGt: 1 },                                           [ 1 ] ],
                [ { no: 45, cnt: 0 },                              { id:0, oldLcGt: 0xfffffffffff },                               [ 1 ] ],
                [ { no: 46, cnt: 1, val: 1 },                      { id:0, oldLcLt: 0xfffffffffff },                               [ 1 ] ],
                [ { no: 47, cnt: 0 },                              { id:0, oldLcLt: 1 },                                           [ 1 ] ],

                [ { no: 48, cnt: 1, val: 1 },                      { id:0, from: 'system.adapter.javascript.0' },                  [ 1 ] ],
                [ { no: 49, cnt: 0 },                              { id:0, from: 'system.adapter.javascript.1' },                  [ 1 ] ],
                [ { no: 50, cnt: 1, val: 1 },                      { id:0, oldFrom: 'system.adapter.javascript.0' },               [ 1 ] ],
                [ { no: 51, cnt: 0 },                              { id:0, oldFrom: 'system.adapter.javascript.1' },               [ 1 ] ],

                // not ok with the old patternMatching function
                [ { no: 52, cnt: 1, val: 'onChannel'},          { channelId: /^javascript.0.device.channel$/ },                    [ 'onChannel'] ],
                [ { no: 53, cnt: 1, val: 'onChannel'},          { channelId: /^javascript.0.device.channel$/, val: 'onChannel' },  [ 'onChannel', 'xyz'] ],
                [ { no: 54, cnt: 1, val: 'onChannel'},          { channelName: /^Channel$/ },                                      [ 'onChannel'] ],
                [ { no: 55, cnt: 1, val: 'onChannel'},          { channelName: /^Channel$/, val: 'onChannel' },                    [ 'onChannel', 'xyz'] ],
                [ { no: 56, cnt: 1, val: 'onDevice'},           { deviceId: /^javascript.0.device$/ },                             [ 'onDevice'] ],
                [ { no: 57, cnt: 1, val: 'onDevice'},           { deviceId: /^javascript.0.device$/, val: 'onDevice' },            [ 'onDevice', 'xyz'] ],
                [ { no: 58, cnt: 1, val: 'onDevice'},           { deviceName: /^Device$/ },                                        [ 'onDevice'] ],
                [ { no: 59, cnt: 1, val: 'onDevice'},           { deviceName: /^Device$/, val: 'onDevice' },                       [ 'onDevice', 'xyz'] ]

            ];

            switch (param) {
                case 'recs': return recs;
                case 'TEST_VAR': return TEST_VAR;
            }

            createState(TEST_RESULTS, '', true, { name: 'Testresults', type: 'string' });

            function addResult(name, val) {
                results += name + (val!==undefined ? '=' + val : '') + ';\r\n';
            }

            function handler(result, req, obj) {
                //log ('handler: ' + JSON.stringify (req));
                if (typeof result.val === 'object') {
                    for (var n in result.val) {
                        addResult ('obj.state.' + n + '=' + obj.state[n] + ' val.' + n + '=' + result.val[n]);
                        result.nok = result.nok || (result.val[n] !== obj.state[n]);
                    }
                } else if (result.val !== undefined) {
                    addResult ('obj.state.val=' + obj.state.val + ' val=' + result.val);
                    result.nok = result.nok || (result.val !== obj.state.val);
                }
                result.callCount += 1;
            }

            function createTest(req, obj, ar, callback) {
                results = '';
                if (obj.id === 0) obj.id = TEST_VAR;
                if (req.before === undefined) req.before = false;
                if (req.ack === undefined) req.ack = true;
                if (req.tio === undefined) req.tio = 400; else req.tio *= 1000;
                setState(TEST_VAR, req.before, req.ack, function (_err, _obj) {
                    req.nok = false;
                    req.callCount = 0;
                    if (req.cnt === undefined) req.cnt = 1;
                    var sub = on (obj, handler.bind(1, req, obj));
                    if (!ar) return doIt();
                    var no = 0;
                    (function doIt() {
                        if (no >= ar.length) {
                            setTimeout(function () {
                                unsubscribe(sub);
                                results = ((req.callCount === req.cnt && req.nok === false) ? 'OK;' : 'NOK;') + 'no=' + req.no + ';' + results + 'callCount=' + req.callCount + ';cnt=' + req.cnt;
                                setState(TEST_RESULTS, results, true, callback);
                            }, req.tio);
                            return;
                        }
                        var o = ar[no++];
                        if (typeof o !== 'object') {
                            o = { val: o };
                        }
                        setState(TEST_VAR, o.val, o.ack, doIt)
                    })();
                });
            }

            function runTests (id) {
                createState(id, '', true, { name: 'Hello' }, function (err, obj) {
                    var cnt = 0;
                    (function doIt() {
                        if (cnt >= recs.length) return;
                        var rec = recs[cnt++];
                        createTest(rec[0], rec[1], rec[2], doIt);
                    })();
                });
            }
            runTests(TEST_VAR);
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        var script = {
            "common": {
                "name":         "test ON any",
                "engineType":   "Javascript/js",
                "source":       scriptFunction.toString() + '\r\nscriptFunction();\r\n',
                "enabled":      true,
                "engine":       "system.adapter.javascript.0"
            },
            "type":             "script",
            "_id":              "script.js.test_ON",
            "native": {}
        };


        var recs = scriptFunction('recs');
        var TEST_VAR = scriptFunction('TEST_VAR');
        this.timeout(5000 + 500*recs.length);

        function createObjects(callback) {
            var channel = TEST_VAR.replace(/\.[^.]+$/, '');
            var device = channel.replace(/\.[^.]+$/, '');
            objects.setObject(device, { common: { name: 'Device', type: 'device'}}, function (err, obj) {
                expect(err).to.be.not.ok
                objects.setObject(channel, { common: { name: 'Channel', type: 'channel'}}, callback)
            })
        }

        createObjects(function (err, _obj) {
            expect(err).to.be.not.ok
            // objects.getObject('system.adapter.javascript.0', function(err, obj) {
            //     obj.native.enableSetObject = true;
            //     objects.setObject('system.adapter.javascript.0', function(err, obj) {
            var cnt = 0;

            const onStateChanged = function (id, state) {
                if (id === 'javascript.0.testResults' && state.val) {
                    cnt += 1;
                    var ar = /^(OK;no=[\d]+)/.exec(state.val) || ['', state.val];
                    expect(ar).to.be.ok;
                    expect(ar[1]).to.be.equal('OK;no=' + cnt);

                    if (cnt >= recs.length) {
                        removeStateChangedHandler(onStateChanged);
                        done ();
                    }
                }
            };
            addStateChangedHandler(onStateChanged);

            objects.setObject(script._id, script, function (err) {
                expect(err).to.be.not.ok;
            });
        })


    });

    it('Test JS: test schedule for seconds', function (done) {
        this.timeout(4000);
        var d = new Date();

        console.log('Must wait 2 seconds[' + ((d.getSeconds() + 2) % 60) + ' * * * * *]' + d.toISOString());
        // add script
        var script = {
            "common": {
                "name":         "test ON any",
                "engineType":   "Javascript/js",
                "source":       "createState('testScheduleResponse', false);schedule('" + ((d.getSeconds() + 2) % 60) + " * * * * *', function (obj) {setState('testScheduleResponse', true, true);});",
                "enabled":      true,
                "engine":       "system.adapter.javascript.0"
            },
            "type":             "script",
            "_id":              "script.js.test_ON_any",
            "native": {}
        };

        const onStateChanged = function (id, state) {
            if (id === 'javascript.0.testScheduleResponse' && state.val === true) {
                removeStateChangedHandler(onStateChanged);
                done();
            }
        };
        addStateChangedHandler(onStateChanged);

        objects.setObject(script._id, script, function (err) {
            expect(err).to.be.not.ok;
        });
    });

    it('Test JS: test schedule for minutes', function (done) {
        var d = new Date();
        console.log('Must wait ' + (60 - d.getSeconds()) + ' seconds[' + ((d.getMinutes() + 1) % 60) + ' * * * *] ' + d.toISOString());
        this.timeout((64 - d.getSeconds()) * 1000);

        // add script
        var script = {
            "common": {
                "name":         "test ON any",
                "engineType":   "Javascript/js",
                "source":       "createState('testScheduleResponse1', false);schedule('" + ((d.getMinutes() + 1) % 60) + " * * * *', function (obj) {setState('testScheduleResponse1', true, true);});",
                "enabled":      true,
                "engine":       "system.adapter.javascript.0"
            },
            "type":             "script",
            "_id":              "script.js.test_ON_any",
            "native": {}
        };

        const onStateChanged = function (id, state) {
            if (id === 'javascript.0.testScheduleResponse1' && state.val === true) {
                removeStateChangedHandler(onStateChanged);
                done();
            }
        };
        addStateChangedHandler(onStateChanged);

        objects.setObject(script._id, script, function (err) {
            expect(err).to.be.not.ok;
        });
    });

    it('Test JS: test write file to "javascript"', function (done) {
        this.timeout(5000);
        // add script
        var script = {
            "common": {
                "name":         "test ON any",
                "engineType":   "Javascript/js",
                "source":       "createState('testScheduleResponse2', false, function () {writeFile('/test.txt', 'test', function () {setState('testScheduleResponse2', true, true);});});",
                "enabled":      true,
                "engine":       "system.adapter.javascript.0"
            },
            "type":             "script",
            "_id":              "script.js.test_write",
            "native": {}
        };

        const onStateChanged = function (id, state) {
            if (id === 'javascript.0.testScheduleResponse2' && state.val === true) {
                removeStateChangedHandler(onStateChanged);
                done();
            }
        };
        addStateChangedHandler(onStateChanged);

        objects.setObject(script._id, script, function (err) {
            expect(err).to.be.not.ok;
        });
    });

    it('Test JS: test read file from "javascript"', function (done) {
        this.timeout(5000);
        // add script
        var script = {
            "common": {
                "name":         "test ON any",
                "engineType":   "Javascript/js",
                "source":       "readFile('/test.txt', function (err, data) {setState('testScheduleResponse2', data, true);});",
                "enabled":      true,
                "engine":       "system.adapter.javascript.0"
            },
            "type":             "script",
            "_id":              "script.js.test_read",
            "native": {}
        };

        const onStateChanged = function (id, state) {
            if (id === 'javascript.0.testScheduleResponse2' && state.val === 'test') {
                removeStateChangedHandler(onStateChanged);
                done();
            }
        };
        addStateChangedHandler(onStateChanged);

        objects.setObject(script._id, script, function (err) {
            expect(err).to.be.not.ok;
        });
    });

    it('Test JS: test write file  to "vis.0"', function (done) {
        this.timeout(5000);
        // add script
        var script = {
            "common": {
                "name":         "test ON any",
                "engineType":   "Javascript/js",
                "source":       "createState('testScheduleResponse2', false, function () {writeFile('vis.0', '/test1.txt', 'test', function () {setState('testScheduleResponse2', true, true);});});",
                "enabled":      true,
                "engine":       "system.adapter.javascript.0"
            },
            "type":             "script",
            "_id":              "script.js.test_write1",
            "native": {}
        };

        const onStateChanged = function (id, state) {
            if (id === 'javascript.0.testScheduleResponse2' && state.val === true) {
                removeStateChangedHandler(onStateChanged);
                done();
            }
        };
        addStateChangedHandler(onStateChanged);

        objects.setObject(script._id, script, function (err) {
            expect(err).to.be.not.ok;
        });
    });

    it('Test JS: test read file from "vis.0"', function (done) {
        this.timeout(5000);
        // add script
        var script = {
            "common": {
                "name":         "test ON any",
                "engineType":   "Javascript/js",
                "source":       "readFile('vis.0', '/test1.txt', function (err, data) {setState('testScheduleResponse2', data, true);});",
                "enabled":      true,
                "engine":       "system.adapter.javascript.0"
            },
            "type":             "script",
            "_id":              "script.js.test_read1",
            "native": {}
        };

        const onStateChanged = function (id, state) {
            if (id === 'javascript.0.testScheduleResponse2' && state.val === 'test') {
                removeStateChangedHandler(onStateChanged);
                done();
            }
        };
        addStateChangedHandler(onStateChanged);

        objects.setObject(script._id, script, function (err) {
            expect(err).to.be.not.ok;
        });
    });

    after('Test JS: Stop js-controller', function (done) {
        this.timeout(6000);

        setup.stopController(function (normalTerminated) {
            console.log('Adapter normal terminated: ' + normalTerminated);
            done();
        });
    });
});
