"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
const rxjs_1 = require("rxjs");
const operators_1 = require("rxjs/operators");
const adapter_1 = require("@ha4us/adapter");
const core_1 = require("@ha4us/core");
const ha4us_script_1 = require("./ha4us-script");
const us_scheduler_1 = require("us-scheduler");
const ADAPTER_OPTIONS = {
    // h,m,u,p,l,n
    name: 'scripts',
    path: __dirname + '/..',
    args: {
        dir: {
            alias: 'dir',
            demandOption: false,
            describe: 'directory of script files',
            type: 'string',
        },
        lat: {
            alias: 'lat',
            default: 54.46,
            demandOption: false,
            describe: 'Latitude for sun calculation',
            type: 'number',
        },
        long: {
            alias: 'long',
            default: 9.95,
            demandOption: false,
            describe: 'Longitutde for sun calculation',
            type: 'number',
        },
        watch: {
            alias: 'w',
            demandOption: false,
            default: true,
            describe: 'watch for script file changes',
            type: 'boolean',
        },
    },
    imports: [
        '$log',
        '$args',
        '$states',
        '$yaml',
        '$objects',
        // '$event',
        '$media',
    ],
};
function Adapter($log, $args, $states, $yaml, $objects, $media
//  $event: EventService
) {
    const scripts = new Map();
    const scriptEvent$ = new rxjs_1.Subject();
    let sub;
    function createScriptObjects(script) {
        $log.debug(`creating objects for ${script.name}`);
        return $objects
            .create({
            state: {
                role: 'Value/Script/State',
                type: core_1.Ha4usObjectType.String,
                can: { trigger: true },
            },
            log: {
                role: 'Value/Script/Log',
                type: core_1.Ha4usObjectType.Object,
                can: { trigger: true },
            },
        }, { root: core_1.MqttUtil.join('$', script.topic) })
            .toPromise()
            .then(res => {
            $log.debug(`Topics ${res.objects.map(obj => obj.topic).join(',')} installed`);
            return script;
        });
    }
    function installScript(aScript) {
        return __awaiter(this, void 0, void 0, function* () {
            if (scripts.has(aScript.name)) {
                throw new core_1.Ha4usError(409, `script "${aScript.name} "already exists`);
            }
            $log.debug(`creating script ${aScript.name}`);
            scripts.set(aScript.name, aScript);
            scriptEvent$.next(aScript);
            aScript.log$
                .pipe(operators_1.mergeMap(event => $states.status(core_1.MqttUtil.join(aScript.name, 'log'), event, true)))
                .subscribe(msg => {
                $log.debug(`Message send`, msg.topic, msg.val);
            }, e => $log.error('error in script event listener', e), () => $log.info('Script event listener completed'));
            aScript.status$
                .pipe(operators_1.mergeMap(status => $states.status(core_1.MqttUtil.join(aScript.name, 'state'), status, true)))
                .subscribe(msg => {
                $log.debug(`Status update`, msg.topic, msg.val);
            }, e => $log.error('error in script event listener', e), () => $log.info('Script event listener completed'));
            return aScript
                .init()
                .then(script => createScriptObjects(aScript))
                .then(script => script.compile())
                .then(script => {
                if (script && script.autostart) {
                    return script.start();
                }
                else {
                    return script;
                }
            })
                .catch(e => {
                $log.error(`${aScript.name} errored`, e);
                return undefined;
            });
        });
    }
    function $onInit() {
        return __awaiter(this, void 0, void 0, function* () {
            yield $objects.connect();
            yield $media.connect();
            const res = yield $objects
                .create([
                {
                    role: core_1.Ha4usRole.ScriptAdapter,
                },
                {
                    sun: [
                        {
                            role: 'Device/Sun',
                        },
                        {
                            position: {
                                role: 'Value/SunPosition',
                                type: core_1.Ha4usObjectType.Object,
                                can: { trigger: true },
                            },
                            time: {
                                role: 'Value/SunTime',
                                type: core_1.Ha4usObjectType.String,
                                can: { trigger: true },
                            },
                        },
                    ],
                },
            ], { mode: 'update', root: '$' })
                .toPromise();
            $log.info(`${res.updated} Objects updated. ${res.inserted} created`);
            // first loading script from database
            //
            $objects
                .observe(core_1.MqttUtil.join($args.name, '+'))
                .pipe(operators_1.filter(script => script.role === 'Script'), operators_1.mergeMap(scriptObject => installScript(new ha4us_script_1.Ha4usScript(scriptObject, {
                $args,
                $log,
                $yaml,
                $states,
                $objects,
                $media,
            }))), operators_1.filter(script => !!script))
                .subscribe(script => {
                $log.info('Loaded script from database', script.name);
            });
            // listen for object changes (insert, update, delete)
            sub = $states
                .observe('/$object/+')
                .pipe(operators_1.filter(event => event.val.object.role === core_1.Ha4usRole.Script), operators_1.switchMap(event => {
                const action = event.val.action;
                const name = event.val.object.topic;
                const script = scripts.get(event.val.object.topic);
                return rxjs_1.of([action, name, script]).pipe(operators_1.mergeMap(data => {
                    $log.debug('script update arrived', event.val.action);
                    switch (event.val.action) {
                        case 'update':
                            if (!script) {
                                throw new core_1.Ha4usError(409, `script ${event.val.object.topic} does not exist`);
                            }
                            script.source = event.val.object.native.source;
                            script.autostart =
                                event.val.object.native.autostart ||
                                    typeof event.val.object.native.autostart === 'undefined';
                            $log.debug('Setting new source with autostart', event.val.object.native.autostart);
                            return script.compile().then(() => script.restart());
                        case 'insert':
                            if (script) {
                                throw new core_1.Ha4usError(404, `script ${event.val.object.topic} already exists`);
                            }
                            return installScript(new ha4us_script_1.Ha4usScript(event.val.object, {
                                $args,
                                $log,
                                $yaml,
                                $states,
                                $objects,
                                $media,
                            }));
                        case 'delete':
                            if (script) {
                                return script.stop().then(stoppedScript => {
                                    scripts.delete(stoppedScript.name);
                                    return stoppedScript;
                                });
                            }
                            else {
                                throw new core_1.Ha4usError(404, `script ${event.val.object.topic} already exists`);
                            }
                        default:
                            throw new core_1.Ha4usError(405, `method ${event.val.action} not known`);
                    }
                }), operators_1.catchError(e => {
                    $log.error(`action errored: ${e.message}`);
                    return rxjs_1.NEVER;
                }));
            }))
                .subscribe(script => {
                $log.info(`action for script ${script.name} success`);
            });
            sub.add($states
                .observe('/$set/+/state')
                .pipe(operators_1.switchMap(msg => {
                const [scriptName] = msg.match.params;
                $log.debug(`Setting state of ${scriptName} to ${msg.val}`);
                const storedScript = scripts.get(core_1.MqttUtil.join($args.name, scriptName));
                return rxjs_1.of(storedScript).pipe(operators_1.mergeMap(aScript => {
                    if (aScript) {
                        if (msg.val === true) {
                            return aScript.compile().then(() => aScript.start());
                        }
                        else {
                            return aScript.stop();
                        }
                    }
                    else {
                        throw new core_1.Ha4usError(404, `script ${scriptName} does not exists`);
                    }
                }), operators_1.catchError(e => {
                    $log.error(`Error setting state of ${scriptName} to ${msg.val} because ${e.message}`);
                    return rxjs_1.NEVER;
                }));
            }))
                .subscribe((script) => {
                $log.info(`script ${script.name} is ${script.status}`);
            }, e => {
                $log.error(`BUMMER`, e);
            }, () => {
                $log.error('Script STATE Listener completed');
            }));
            if (!$args.scriptsDir) {
                throw new Error('No script directory given');
            }
            const st = new us_scheduler_1.SunTimes({
                latitude: $args.lat,
                longitude: $args.long,
            });
            sub.add(rxjs_1.timer(0, 300000)
                .pipe(operators_1.map(() => st.sun))
                .subscribe(position => {
                $log.debug(`Sunposition calculated altitude ${position.altitude}°, azimuth: ${position.azimuth}° `);
                $states.status('$sun/position', position, true);
            }));
            const scheduler = new us_scheduler_1.Scheduler({
                latitude: $args.lat,
                longitude: $args.long,
                skipPast: false,
            });
            sub.add(scheduler.schedule().subscribe(event => {
                $log.debug('SunTime', event.label, event.target.toISO());
                $states.status('$sun/time', event.label, true);
            }));
            $states.status('$sun/all', st.sortedTimes, true);
            $states.connected = 2;
            return true;
        });
    }
    function $onDestroy() {
        return __awaiter(this, void 0, void 0, function* () {
            $log.info('Destroying scripts', scripts.keys());
            yield rxjs_1.from(scripts.values())
                .pipe(operators_1.mergeMap(script => script.stop()), operators_1.map(() => sub.unsubscribe()), operators_1.toArray(), operators_1.delay(2000) // waiting for 2 seconds to let all state emissions finish
            )
                .toPromise();
        });
    }
    return {
        $onInit,
        $onDestroy,
    };
}
adapter_1.ha4us(ADAPTER_OPTIONS, Adapter).catch(e => {
    console.error('Error occurred', e);
    process.exit(1);
});
//# sourceMappingURL=index.js.map