import TuyAPI from 'tuyapi';

export enum CleanSpeed {
    STANDARD = 'Standard',
    BOOST_IQ = 'Boost_IQ',
    MAX = 'Max',
    NO_SUCTION = 'No_suction'
}

export enum ErrorCode {
    NO_ERROR = 'no_error',
    STUCK_5_MIN = 'Stuck_5_min',
    CRASH_BAR_STUCK = 'Crash_bar_stuck',
    SENSOR_DIRTY = 'sensor_dirty',
    NOT_ENOUGH_POWER = 'N_enough_pow',
    WHEEL_STUCK = 'Wheel_stuck',
    S_BRUSH_STUCK = 'S_brush_stuck',
    FAN_STUCK = 'Fan_stuck',
    R_BRUSH_STUCK = 'R_brush_stuck'
}

export enum WorkStatus {
    RUNNING = 'Running', // Cleaning
    STAND_BY = 'standby', // Not in the dock, paused
    SLEEPING = 'Sleeping', // Not in the dock - goes into this state after being paused for a while
    CHARGING = 'Charging', // In the dock, charging
    COMPLETED = 'completed', // In the dock, full charged
    RECHARGE_NEEDED = 'Recharge' // Going home because battery is depleted or home was pressed
}

export enum Direction {
    FORWARD = 'forward',
    BACKWARD = 'backward',
    LEFT = 'left',
    RIGHT = 'right'
}

export enum WorkMode {
    AUTO = 'auto',
    SMALL_ROOM = 'SmallRoom',
    SPOT = 'Spot',
    EDGE = 'Edge',
    NO_SWEEP = 'Nosweep'
}

export interface StatusResponse {
    devId: string,
    dps: {
        "1": boolean,
        "2": boolean,
        "3": string,
        "5": string,
        "15": string,
        "101": boolean,
        "102": string,
        "103": boolean,
        "104": number,
        "106": string,
    }
}

export interface RoboVacConfig {
    deviceId: string
    localKey: string
    ip?: string
    port?: number
}

export class RoboVac {

    api: any;

    PLAY_PAUSE = '2';
    DIRECTION = '3';
    WORK_MODE = '5';
    WORK_STATUS = '15';
    GO_HOME = '101';
    CLEAN_SPEED = '102';
    FIND_ROBOT = '103';
    BATTERY_LEVEL = '104';
    ERROR_CODE = '106';

    connected: boolean = false;
    debugLog: boolean;

    statuses: StatusResponse = null;
    lastStatusUpdate: number = null;
    maxStatusUpdateAge: number = 1000 * (1 * 30); //30 Seconds
    timeoutDuration: number;

    constructor(config: RoboVacConfig, debugLog: boolean = false, timeoutDuration = 2) {
        this.debugLog = debugLog;
        if (!config.deviceId) {
            throw new Error('You must pass through deviceId');
        }
        this.api = new TuyAPI(
            {
                id: config.deviceId,
                key: config.localKey,
                ip: config.ip,
                port: config.port || 6668,
                version: '3.3',
                issueRefreshOnConnect: true
            }
        );
        this.timeoutDuration = timeoutDuration;

        this.api.on('error', (error: any) => {
            if (debugLog) {
                console.error('Robovac Error', JSON.stringify(error, null, 4));
            }
        });

        this.api.on('connected', () => {
            this.connected = true;
            if (debugLog) {
                console.log("Connected!");
            }
        });

        this.api.on('disconnected', () => {
            this.connected = false;
            if (debugLog) {
                console.log('Disconnected!');
            }
        });

        this.api.on('dp-refresh', data => {
            if (debugLog) {
                console.log('DP_REFRESH data from device: ', data);
                console.log('Status Updated!');
            }
        });

        this.api.on('data', (data: StatusResponse) => {
            if (debugLog) {
                console.log('Data from device:', data);
                console.log('Status Updated!');
            }
        });

    }

    async connect() {
        if (!this.connected) {
            if (this.debugLog) {
                console.log('Connecting...');
            }
            await this.api.connect();
        }
    }

    async disconnect() {
        if (this.debugLog) {
            console.log('Disconnecting...');
        }
        await this.api.disconnect();
    }

    async doWork(work: () => Promise<any>): Promise<any> {
        if (!this.api.device.id || !this.api.device.ip) {
            if (this.debugLog) {
                console.log('Looking for device...');
            }
            try {
                await this.api.find({ timeout: this.timeoutDuration });
                if (this.debugLog) {
                    console.log(`Found device ${this.api.device.id} at ${this.api.device.ip}`);
                }
            } catch (err) {
                console.log(err);
            }
        }
        await this.connect();
        return await work();
    }

    async getStatuses(force: boolean = false): Promise<{ devId: string, dps: { [key: string]: string | boolean | number } }> {
        if (force || (new Date()).getTime() - this.lastStatusUpdate > this.maxStatusUpdateAge) {
            return await this.doWork(async () => {
                this.statuses = await this.api.get({ schema: true });
                this.lastStatusUpdate = (new Date()).getTime();
                return this.statuses;
            });
        } else {
            return this.statuses;
        }
    }

    async getCleanSpeed(force: boolean = false): Promise<CleanSpeed> {
        let statuses = await this.getStatuses(force);
        return <CleanSpeed>statuses.dps[this.CLEAN_SPEED];
    }

    async setCleanSpeed(cleanSpeed: CleanSpeed) {
        await this.doWork(async () => {
            await this.set({
                [this.CLEAN_SPEED]: cleanSpeed
            })
        });
    }

    async getPlayPause(force: boolean = false): Promise<boolean> {
        let statuses = await this.getStatuses(force);
        return <boolean>statuses.dps[this.PLAY_PAUSE];
    }

    async setPlayPause(state: boolean) {
        await this.doWork(async () => {
            await this.set({
                [this.PLAY_PAUSE]: state
            })
        });
    }

    async play() {
        await this.setPlayPause(true);
    }

    async pause() {
        await this.setPlayPause(true);
    }

    async getWorkMode(force: boolean = false): Promise<WorkMode> {
        let statuses = await this.getStatuses(force);
        return <WorkMode>statuses.dps[this.WORK_MODE];
    }

    async setWorkMode(workMode: WorkMode) {
        await this.doWork(async () => {
            if (this.debugLog) {
                console.log(`Setting WorkMode to ${workMode}`);
            }
            await this.set({
                [this.WORK_MODE]: workMode
            })
        });
    }

    async startCleaning(force: boolean = false) {
        if (this.debugLog) {
            console.log('Starting Cleaning', JSON.stringify(await this.getStatuses(force), null, 4));
        }
        await this.setWorkMode(WorkMode.AUTO);

        if (this.debugLog) {
            console.log('Cleaning Started!');
        }
    }

    async getWorkStatus(force: boolean = false): Promise<WorkStatus> {
        let statuses = await this.getStatuses(force);
        return <WorkStatus>statuses.dps[this.WORK_STATUS];
    }

    async setWorkStatus(workStatus: WorkStatus) {
        await this.doWork(async () => {
            await this.set({
                [this.WORK_STATUS]: workStatus
            })
        });
    }

    async goHome() {
        await this.doWork(async () => {
            await this.set({
                [this.GO_HOME]: true
            })
        });
    }

    async setFindRobot(state: boolean) {
        return await this.doWork(async () => {
            await this.set({
                [this.FIND_ROBOT]: state
            })
        });
    }

    async getFindRobot(force: boolean = false) {
        let statuses = await this.getStatuses(force);
        return <boolean>statuses.dps[this.FIND_ROBOT];
    }

    async getBatteyLevel(force: boolean = false) {
        let statuses = await this.getStatuses(force);
        return <number>statuses.dps[this.BATTERY_LEVEL];
    }

    async getErrorCode(force: boolean = false): Promise<ErrorCode> {
        let statuses = await this.getStatuses(force);
        return <ErrorCode>statuses.dps[this.ERROR_CODE];
    }

    async set(data: { [key: string]: string | number | boolean }) {
        if (this.debugLog) {
            console.log(`Setting: ${JSON.stringify(data, null, 4)}`);
        }
        return await this.api.set({
            multiple: true,
            data: data
        });
    }

    formatStatus() {
        console.log(`
		-- Status Start --
		 - Play/Pause: ${(this.statuses.dps as any)[this.PLAY_PAUSE]}
		 - Direction: ${(this.statuses.dps as any)[this.DIRECTION]}
		 - Work Mode: ${(this.statuses.dps as any)[this.WORK_MODE]}
		 - Go Home: ${(this.statuses.dps as any)[this.GO_HOME]}
		 - Clean Speed: ${(this.statuses.dps as any)[this.CLEAN_SPEED]}
		 - Find Robot: ${(this.statuses.dps as any)[this.FIND_ROBOT]}
		 - Battery Level: ${(this.statuses.dps as any)[this.BATTERY_LEVEL]}
		 - Error Code: ${(this.statuses.dps as any)[this.ERROR_CODE]}
		-- Status End --
		`);
    }
}
