"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 child_process_1 = require("child_process");
const events_1 = require("events");
const logger_1 = require("./logger");
const webServer_1 = require("../servers/webServer");
const telemetry_1 = require("../servers/telemetry");
const osdData_1 = require("../model/osdData");
const dgram_1 = require("dgram");
const opn = require("opn");
/**
 *
 */
class Tello {
    constructor() {
        this.PORT = 8889;
        this.HOST = '192.168.10.1';
        this.PORT2 = 8890;
        this.HOST2 = '0.0.0.0';
        this.localPort = 50602;
        this.isMock = false;
        this.isStreming = false;
        this.hasTelemetry = false;
        this.myEmitter = new events_1.EventEmitter();
        this.osdData = new osdData_1.OSDData();
        this.deviceIP = this.HOST;
        this.telloWebServer = new webServer_1.TelloWebServer();
        this.telloTelemetry = new telemetry_1.TelloTelemetry();
    }
    /**
     *
     * @param {number}  time in ms
     * @return {Promise<Tello>}
     */
    wait(time) {
        return __awaiter(this, void 0, void 0, function* () {
            return new Promise(resolve => {
                setTimeout(() => {
                    resolve(this);
                }, time);
            });
        });
    }
    ;
    /**
     *
     * @return {Promise<Tello>}
     */
    stopStream() {
        return new Promise(resolve => {
            this.sendCmd('streamoff').then(() => {
                if (this.tello_video) {
                    this.tello_video.close();
                    this.tello_video = undefined;
                }
                if (this.h264encoder) {
                    this.h264encoder.kill();
                }
                this.isStreming = false;
                resolve(this);
            });
        });
    }
    /**
     *
     * @return {Promise<Tello>}
     */
    startStream() {
        return new Promise(resolve => {
            this.sendCmd('streamon').then(() => {
                this.tello_video = dgram_1.createSocket('udp4');
                this.h264encoder_spawn = {
                    "command": 'mplayer',
                    "args": ['-gui', '-nolirc', '-fps', '35', '-really-quiet', '-']
                };
                this.h264encoder = child_process_1.spawn(this.h264encoder_spawn.command, this.h264encoder_spawn.args);
                /*  this.h264encoder.on('close', (code) => {
                    Logger.error('[Tello]', `child process exited with code ${code}`);
                  });*/
                this.h264encoder.stderr.on('data', data => {
                    logger_1.Logger.error('[Tello]', 'mplayer error', data.toString());
                });
                /*  this.h264encoder.stdout.on('data', data => {
                      const idx = data.indexOf(this.headers['h264_baseline']);
                      if (idx > -1 && this.h264chunks.length > 0) {
                          this.h264chunks.push(data.slice(0, idx));
                          if (this.hasTelemetry) {
                              try {
                                  TelloTelemetry.sendVideo(Buffer.concat(this.h264chunks).toString('binary'));
                              } catch (e) {
                              }
                          }
                          this.h264chunks = [];
                          this.h264chunks.push(data.slice(idx));
                      } else {
                          this.h264chunks.push(data);
                      }
                  });*/
                this.tello_video.on('message', msg => this.h264encoder.stdin.write(msg));
                this.tello_video.on('listening', () => {
                    logger_1.Logger.info('[Tello]', `tello_video listening ${this.tello_video.address().address}:${this.tello_video.address().port}`);
                    this.isStreming = true;
                    resolve(this);
                });
                this.tello_video.bind(11111, this.HOST2);
            });
        });
    }
    /**
     *
     * @return {Tello}
     */
    mock() {
        this.deviceIP = '127.0.0.1';
        this.isMock = true;
        return this;
    }
    /**
     *
     * @return {Promise<Tello>}
     */
    startTelemetry() {
        return new Promise(resolve => {
            if (!this.isMock) {
                this.telloWebServer.start().then(() => {
                    this.runTelemetry().then(() => resolve(this));
                });
            }
        });
    }
    /**
     *
     * @return {Promise<Tello>}
     */
    runTelemetry() {
        return new Promise(resolve => {
            this.telloTelemetry.start().then(() => {
                this.hasTelemetry = true;
                opn('http://127.0.0.1:3000/telemetry.html').then(() => {
                    logger_1.Logger.info('[Tello]', `Telemetry started`);
                    resolve(this);
                });
            });
        });
    }
    /**
     *
     * @return {Promise<Tello>}
     */
    stopTelemetry() {
        return new Promise(resolve => {
            this.shutDownTelemetry().then(() => {
                this.telloTelemetry.stop().then(() => {
                    this.hasTelemetry = true;
                    logger_1.Logger.info('[Tello]', `Stopping telemetry`);
                    resolve(this);
                });
            });
        });
    }
    /**
     *
     * @return {Promise<Tello>}
     */
    shutDownTelemetry() {
        return new Promise(resolve => {
            if (!this.isMock) {
                this.telloWebServer.stop().then(() => resolve(this));
            }
            else {
                resolve(this);
            }
        });
    }
    /**
     *
     * @param {string} value
     * @return {Promise<{value: string, tello: Tello}>}
     */
    get(value) {
        return Promise.resolve({ value: this.osdData[value], tello: this });
    }
    /**
     *
     */
    listenState() {
        this.UDPServer.on('message', msg => {
            const strMsg = msg.toString().trim();
            const fieldList = strMsg.split(';');
            fieldList.forEach(field => {
                const fields = field.split(':');
                this.osdData[fields[0]] = fields[1];
                if (this.hasTelemetry) {
                    this.telloTelemetry.send(this.osdData);
                }
            });
            /*     Logger.info('fieldList', fieldList)
                 Logger.info('osdData', osdData)*/
        });
        this.UDPServer.on('listening', () => {
            const address = this.UDPServer.address();
            logger_1.Logger.info('[Tello]', 'server listening', address.address, address.port);
        });
        this.UDPServer.bind(this.PORT2, this.HOST2);
    }
    /**
     *
     * @param {string} cmd
     * @return {Promise<Tello>}
     */
    sendMethod(cmd) {
        return new Promise((resolve, reject) => {
            const message = new Buffer(cmd);
            logger_1.Logger.info('[Tello]', 'send:', cmd);
            this.UDPClient.send(message, 0, message.length, this.PORT, this.deviceIP, err => {
                if (err) {
                    reject(err);
                }
                else {
                    resolve(this);
                }
            });
        });
    }
    /**
     *
     * @param {string} cmd
     * @return {Promise<Tello>}
     */
    sendCmd(cmd) {
        return new Promise(((resolve, reject) => {
            this.sendMethod(cmd).then(() => {
                this.myEmitter.on('status', () => {
                    resolve(this);
                });
            }).catch(err => {
                reject(err);
            });
        }));
    }
    /**
     *
     * @return {Promise<Tello>}
     */
    emergencyStop() {
        return __awaiter(this, void 0, void 0, function* () {
            return this.sendCmd('emergency');
        });
    }
    /**
     *
     * @return {Promise<Tello>}
     */
    takeoff() {
        return this.sendCmd('takeoff');
    }
    /**
     *
     * @param {number} x1 in cm
     * @param {number} y1 in cm
     * @param {number} z1 in cm
     * @param {number} x2 in cm
     * @param {number} y2 in cm
     * @param {number} z2 in cm
     * @param {number} speed in cm/s
     * @return {Promise<Tello>}
     */
    curve(x1 = 20, y1 = 20, z1 = 20, x2 = 60, y2 = 40, z2 = 0, speed = 60) {
        return this.sendCmd('curve ' + x1 + ' ' + y1 + ' ' + z1 + ' ' + x2 + ' ' + y2 + ' ' + z2 + ' ' + speed + ' ');
    }
    /**
     *
     * @param {number} distance in cm
     * @return {Promise<Tello>}
     */
    forward(distance = 50) {
        return this.sendCmd('forward ' + distance);
    }
    /**
     *
     * @return {Promise<Tello>}
     */
    start() {
        this.myEmitter.setMaxListeners(20);
        return new Promise(resolve => {
            this.UDPClient = dgram_1.createSocket('udp4');
            this.UDPServer = dgram_1.createSocket('udp4');
            this.UDPClient.bind(this.localPort, '0.0.0.0', () => {
                logger_1.Logger.info('[Tello]', 'connected');
                this.sendCmd('command').then(() => {
                    resolve(this);
                });
            });
            process.on('SIGINT', () => __awaiter(this, void 0, void 0, function* () {
                yield this.stop();
            }));
            this.UDPClient.on('message', msg => {
                if (msg.toString() === 'ok') {
                    logger_1.Logger.info('[Tello]', 'Data received from server : ', msg.toString());
                }
                else {
                    logger_1.Logger.error('[Tello]', 'not ok', msg);
                }
                this.myEmitter.emit('status');
            });
            this.listenState();
        });
    }
    /**
     *
     * @param {number} distance in cm
     * @return {Promise<Tello>}
     */
    right(distance = 50) {
        return this.sendCmd('right ' + distance);
    }
    /**
     *
     * @param {number} height in cm
     * @return {Promise<Tello>}
     */
    down(height = 50) {
        return this.sendCmd('down ' + height);
    }
    /**
     *
     * @param {number} speed in cm/s
     * @return {Promise<Tello>}
     */
    speed(speed = 50) {
        return this.sendCmd('speed ' + speed);
    }
    /**
     * Ends the process
     */
    stop() {
        return __awaiter(this, void 0, void 0, function* () {
            this.UDPClient.close();
            this.UDPServer.close();
            if (this.tello_video) {
                yield this.tello_video.close();
            }
            if (this.h264encoder) {
                yield this.h264encoder.kill();
            }
            if (!this.isMock && this.hasTelemetry) {
                yield this.stopTelemetry();
            }
            logger_1.Logger.info(new Date(), '[Tello]', 'Goodbye !');
            process.exit(0);
        });
    }
    /**
     *
     * @param {number} distance in cm
     * @return {Promise<Tello>}
     */
    left(distance = 50) {
        return this.sendCmd('left ' + distance);
    }
    /**
     *
     * @param {number} angle in degree
     * @return {Promise<Tello>}
     */
    rotateCW(angle = 90) {
        return this.sendCmd('cw ' + angle);
    }
    /**
     *
     * @return {Promise<Tello>}
     */
    land() {
        return this.sendCmd('land');
    }
    /**
     *
     * @param {number} distance in cm
     * @return {Promise<Tello>}
     */
    backward(distance = 50) {
        return this.sendCmd('back ' + distance);
    }
    /**
     *
     * @param {number} angle in degree
     * @return {Promise<Tello>}
     */
    rotateCCW(angle = 90) {
        return this.sendCmd('ccw ' + angle);
    }
    /**
     *
     * @param {number} height in cm
     * @return {Promise<Tello>}
     */
    up(height = 50) {
        return this.sendCmd('up ' + height);
    }
    /**
     *
     * @param {string} orientation values: f, b, l, r
     * @return {Promise<Tello>}
     */
    flip(orientation = 'f') {
        return this.sendCmd('flip ' + orientation);
    }
    /**
     *
     * @param {number} x in cm
     * @param {number} y in cm
     * @param {number} z in cm
     * @param {number} speed in cm/s
     * @return {Promise<Tello>}
     */
    flyTo(x = 50, y = 50, z = 0, speed = 100) {
        return this.sendCmd('go ' + x + ' ' + y + ' ' + z + ' ' + speed + ' ');
    }
}
exports.Tello = Tello;
//# sourceMappingURL=tello.js.map