"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 hap_node_client_1 = require("hap-node-client");
const hap_types_1 = require("./hap-types");
const crypto = require("crypto");
const rxjs_1 = require("rxjs");
const operators_1 = require("rxjs/operators");
const door_1 = require("./types/door");
const fan_1 = require("./types/fan");
const garage_door_opener_1 = require("./types/garage-door-opener");
const lightbulb_1 = require("./types/lightbulb");
const lock_mechanism_1 = require("./types/lock-mechanism");
const switch_1 = require("./types/switch");
const window_1 = require("./types/window");
const window_covering_1 = require("./types/window-covering");
const thermostat_1 = require("./types/thermostat");
class Hap {
    constructor(socket, log, pin, config) {
        this.services = [];
        this.types = {
            Door: new door_1.Door(),
            Fan: new fan_1.Fan(),
            GarageDoorOpener: new garage_door_opener_1.GarageDoorOpener(),
            Lightbulb: new lightbulb_1.Lightbulb(),
            LockMechanism: new lock_mechanism_1.LockMechanism(),
            Outlet: new switch_1.Switch('action.devices.types.OUTLET'),
            Switch: new switch_1.Switch('action.devices.types.SWITCH'),
            Thermostat: new thermostat_1.Thermostat(),
            Window: new window_1.Window(),
            WindowCovering: new window_covering_1.WindowCovering(),
        };
        this.evInstances = [];
        this.evServices = [];
        this.reportStateSubject = new rxjs_1.Subject();
        this.pendingStateReport = [];
        this.evTypes = [
            hap_types_1.Characteristic.On,
            hap_types_1.Characteristic.CurrentPosition,
            hap_types_1.Characteristic.TargetPosition,
            hap_types_1.Characteristic.CurrentDoorState,
            hap_types_1.Characteristic.TargetDoorState,
            hap_types_1.Characteristic.Brightness,
            hap_types_1.Characteristic.Hue,
            hap_types_1.Characteristic.Saturation,
            hap_types_1.Characteristic.LockCurrentState,
            hap_types_1.Characteristic.LockTargetState,
            hap_types_1.Characteristic.TargetHeatingCoolingState,
            hap_types_1.Characteristic.TargetTemperature,
            hap_types_1.Characteristic.CurrentTemperature,
            hap_types_1.Characteristic.CurrentRelativeHumidity,
        ];
        this.instanceBlacklist = [];
        this.accessoryFilter = [];
        this.deviceNameMap = [];
        this.config = config;
        this.socket = socket;
        this.log = log;
        this.pin = pin;
        this.accessoryFilter = config.accessoryFilter || [];
        this.instanceBlacklist = config.instanceBlacklist || [];
        this.log.debug('Waiting 15 seconds before starting instance discovery...');
        setTimeout(() => {
            this.discover();
        }, 15000);
        this.reportStateSubject
            .pipe(operators_1.map((i) => {
            if (!this.pendingStateReport.includes(i)) {
                this.pendingStateReport.push(i);
            }
        }), operators_1.debounceTime(1000))
            .subscribe((data) => {
            const pendingStateReport = this.pendingStateReport;
            this.pendingStateReport = [];
            this.processPendingStateReports(pendingStateReport);
        });
    }
    discover() {
        return __awaiter(this, void 0, void 0, function* () {
            this.homebridge = new hap_node_client_1.HAPNodeJSClient({
                debug: this.config.debug,
                pin: this.pin,
                timeout: 10,
            });
            this.homebridge.once('Ready', () => {
                this.ready = true;
                this.log.info(`Finished instance discovery`);
                setTimeout(() => {
                    this.requestSync();
                }, 15000);
            });
            this.homebridge.on('Ready', () => {
                this.start();
            });
            this.homebridge.on('hapEvent', ((event) => {
                this.handleHapEvent(event);
            }));
        });
    }
    start() {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.getAccessories();
            yield this.buildSyncResponse();
            yield this.registerCharacteristicEventHandlers();
        });
    }
    buildSyncResponse() {
        return __awaiter(this, void 0, void 0, function* () {
            const devices = this.services.map((service) => {
                return this.types[service.serviceType].sync(service);
            });
            return devices;
        });
    }
    requestSync() {
        return __awaiter(this, void 0, void 0, function* () {
            this.log.info('Sending Sync Request');
            this.socket.sendJson({
                type: 'request-sync',
            });
        });
    }
    query(devices) {
        return __awaiter(this, void 0, void 0, function* () {
            const response = {};
            for (const device of devices) {
                const service = this.services.find(x => x.uniqueId === device.id);
                if (service) {
                    yield this.getStatus(service);
                    response[device.id] = this.types[service.serviceType].query(service);
                }
                else {
                    response[device.id] = {};
                }
            }
            return response;
        });
    }
    execute(commands) {
        return __awaiter(this, void 0, void 0, function* () {
            const response = [];
            for (const command of commands) {
                for (const device of command.devices) {
                    const service = this.services.find(x => x.uniqueId === device.id);
                    if (service) {
                        const payload = this.types[service.serviceType].execute(service, command);
                        yield new Promise((resolve, reject) => {
                            this.homebridge.HAPcontrol(service.instance.ipAddress, service.instance.port, JSON.stringify(payload), (err) => {
                                if (!err) {
                                    response.push({
                                        ids: [device.id],
                                        status: 'SUCCESS',
                                    });
                                }
                                else {
                                    this.log.error('Failed to control an accessory. Make sure all your Homebridge instances are using the same PIN.');
                                    this.log.error(err.message);
                                    response.push({
                                        ids: [device.id],
                                        status: 'ERROR',
                                    });
                                }
                                return resolve();
                            });
                        });
                    }
                }
            }
            return response;
        });
    }
    getStatus(service) {
        return __awaiter(this, void 0, void 0, function* () {
            const iids = service.characteristics.map(c => c.iid);
            const body = '?id=' + iids.map(iid => `${service.aid}.${iid}`).join(',');
            const characteristics = yield new Promise((resolve, reject) => {
                this.homebridge.HAPstatus(service.instance.ipAddress, service.instance.port, body, (err, status) => {
                    if (err) {
                        return reject(err);
                    }
                    return resolve(status.characteristics);
                });
            });
            for (const c of characteristics) {
                const characteristic = service.characteristics.find(x => x.iid === c.iid);
                characteristic.value = c.value;
            }
        });
    }
    getAccessories() {
        return __awaiter(this, void 0, void 0, function* () {
            return new Promise((resolve, reject) => {
                this.homebridge.HAPaccessories((instances) => __awaiter(this, void 0, void 0, function* () {
                    this.services = [];
                    for (const instance of instances) {
                        if (!this.instanceBlacklist.find(x => x.toLowerCase() === instance.instance.txt.id.toLowerCase())) {
                            yield this.parseAccessories(instance);
                        }
                        else {
                            this.log.debug(`Instance [${instance.instance.txt.id}] on instance blacklist, ignoring.`);
                        }
                    }
                    return resolve(true);
                }));
            });
        });
    }
    parseAccessories(instance) {
        return __awaiter(this, void 0, void 0, function* () {
            instance.accessories.accessories.forEach((accessory) => {
                const accessoryInformationService = accessory.services.find(x => x.type === hap_types_1.Service.AccessoryInformation);
                const accessoryInformation = {};
                if (accessoryInformationService && accessoryInformationService.characteristics) {
                    accessoryInformationService.characteristics.forEach((c) => {
                        if (c.value) {
                            accessoryInformation[c.description] = c.value;
                        }
                    });
                }
                accessory.services
                    .filter(x => x.type !== hap_types_1.Service.AccessoryInformation)
                    .filter(x => hap_types_1.ServicesTypes[x.type])
                    .filter(x => this.types.hasOwnProperty(hap_types_1.ServicesTypes[x.type]))
                    .forEach((service) => {
                    service.accessoryInformation = accessoryInformation;
                    service.aid = accessory.aid;
                    service.serviceType = hap_types_1.ServicesTypes[service.type];
                    service.instance = {
                        ipAddress: instance.ipAddress,
                        port: instance.instance.port,
                        username: instance.instance.txt.id,
                    };
                    service.uniqueId = crypto.createHash('sha256')
                        .update(`${service.instance.username}${service.aid}${service.iid}${service.type}`)
                        .digest('hex');
                    const serviceNameCharacteristic = service.characteristics.find(x => [
                        hap_types_1.Characteristic.Name,
                        hap_types_1.Characteristic.ConfiguredName,
                    ].includes(x.type));
                    service.serviceName = serviceNameCharacteristic ?
                        serviceNameCharacteristic.value : service.accessoryInformation.Name || service.serviceType;
                    const nameMap = this.deviceNameMap.find(x => x.replace === service.serviceName);
                    if (nameMap) {
                        service.serviceName = nameMap.with;
                    }
                    if (!this.accessoryFilter.includes(service.serviceName)) {
                        this.services.push(service);
                    }
                });
            });
        });
    }
    registerCharacteristicEventHandlers() {
        return __awaiter(this, void 0, void 0, function* () {
            for (const service of this.services) {
                const evCharacteristics = service.characteristics.filter(x => x.perms.includes('ev') && this.evTypes.includes(x.type));
                if (evCharacteristics.length) {
                    if (!this.evInstances.find(x => x.username === service.instance.username)) {
                        const newInstance = Object.assign({}, service.instance);
                        newInstance.evCharacteristics = [];
                        this.evInstances.push(newInstance);
                    }
                    const instance = this.evInstances.find(x => x.username === service.instance.username);
                    for (const evCharacteristic of evCharacteristics) {
                        if (!instance.evCharacteristics.find(x => x.aid === service.aid && x.iid === evCharacteristic.iid)) {
                            instance.evCharacteristics.push({ aid: service.aid, iid: evCharacteristic.iid, ev: true });
                        }
                    }
                }
            }
            for (const instance of this.evInstances) {
                const unregistered = instance.evCharacteristics.filter(x => !x.registered);
                if (unregistered.length) {
                    this.homebridge.HAPevent(instance.ipAddress, instance.port, JSON.stringify({
                        characteristics: instance.evCharacteristics.filter(x => !x.registered),
                    }), (err, response) => {
                        if (err) {
                            this.log.error(err);
                        }
                        else {
                            instance.evCharacteristics.forEach((c) => {
                                c.registered = true;
                            });
                            this.log.debug('HAP Event listeners registered succesfully');
                        }
                    });
                }
            }
        });
    }
    handleHapEvent(events) {
        return __awaiter(this, void 0, void 0, function* () {
            for (const event of events) {
                const accessories = this.services.filter(s => s.instance.ipAddress === event.host && s.instance.port === event.port && s.aid === event.aid);
                const service = accessories.find(x => x.characteristics.find(c => c.iid === event.iid));
                const characteristic = service.characteristics.find(c => c.iid === event.iid);
                characteristic.value = event.value;
                this.reportStateSubject.next(service.uniqueId);
            }
        });
    }
    processPendingStateReports(pendingStateReport) {
        return __awaiter(this, void 0, void 0, function* () {
            const states = {};
            for (const uniqueId of pendingStateReport) {
                const service = this.services.find(x => x.uniqueId === uniqueId);
                states[service.uniqueId] = this.types[service.serviceType].query(service);
            }
            return yield this.sendStateReport(states);
        });
    }
    sendFullStateReport() {
        return __awaiter(this, void 0, void 0, function* () {
            const states = {};
            if (!this.services.length) {
                return;
            }
            for (const service of this.services) {
                states[service.uniqueId] = this.types[service.serviceType].query(service);
            }
            return yield this.sendStateReport(states);
        });
    }
    sendStateReport(states, requestId) {
        return __awaiter(this, void 0, void 0, function* () {
            const payload = {
                requestId,
                type: 'report-state',
                body: states,
            };
            this.log.debug('Sending State Report');
            this.log.debug(JSON.stringify(payload, null, 2));
            this.socket.sendJson(payload);
        });
    }
}
exports.Hap = Hap;
//# sourceMappingURL=hap.js.map