import http = require('http');

import { ImportSystem } from './import-system.class';
import { Handler } from './handler.class';
import { extendServerResponse as extendRes } from './extends/res.func';
import { extendServerRequest as extendReq } from './extends/req.func';
import { logger } from './logger.func';
import { wait, generateId } from "./lib";
import { concat, isObject } from 'lodash';
import { $Middleware } from "./models.lib";
import { middleware } from './middleware.func';
import { Router } from './router/router.class';

export class Service extends ImportSystem {

  constructor(params) {
    super();

    this.readonly([
      'address',
      'app',
      'init',
      'includeRouters',
      'startServer',
      'id',
      'name',
      'baseDir',
      'protocol',
      'format',
      'x-powered-by'
    ]);

    this.name = params.name;
    this.port = params.port;
    this.filepath = params.path;
    let path = params.path.split('/');

    path.pop();

    this.baseDir = path.join('/');
    this.router = new Router(this);

    global['AZTEC']['SERVICES'][this.name] = this;

    this.init();
  }

  address(): string {
    return `${this.protocol}://${this.domain}:${this.port}`;
  }

  plugin(func: $Middleware) {
      this.pluginQueue.push(func);
  }

  app(app): void {
    wait('for routes in app', () => {
      if (isObject(app)) {
        this.apps.push(app);
      } else {
        Handler.error(`Undefined application ${app}`);
      }
    });
  }

  private run(): void {
    if (this.server) this.server.close();

    Handler.env = this.env;

    this.router.mergeRoutes();
    this.startServer();
  }

  private init() {
    wait('for options', () => {
      this.appRoutes();
      if (require(this.filepath)) this.run();
    });
  }

  private appRoutes(): void {
    wait('for apps', () => {
      this.apps.forEach(app => {
        this.router.set('routers', concat(this.router.get('routers'), app.router.routers));
      });
    });
  }

  private startServer(): void {
    this.server = http.createServer((req, res) => {
      this.format = '';
      req.on('data', chunk => req['body'] = JSON.parse(chunk.toString()));
      req = extendReq(req); res = extendRes(this, res);
      req.setEncoding('utf-8');

      wait('for data', () => {
        if (this.pluginQueue.length) middleware(req, res, this.pluginQueue);

        if (this.v2 && req.url.indexOf(`/${this['api-prefix']}1`) > -1) this.format = 'json';
        if (this.v2 && req.url.indexOf(`/${this['api-prefix']}2`) > -1) this.format = 'xml';
        if (!this.v2 || !this.format) this.format = this['default-format'];

        switch (this.format) {
          case 'json': res.setHeader('Content-Type', 'application/json'); break;
          case 'xml': res.setHeader('Content-Type', 'text/xml'); break;
        }

        if (this['x-powered-by']) res.setHeader('X-Powered-By', this['x-powered-by']);

        this.routerController.parseRequest(this, req, res);
        if (this.env === 'development') logger(req, res);
      });
    });

    this.server.on('error', e => Handler.error(e));
    this.server.listen(this.port, this.serverStartingMessage.bind(this));
  }

  private serverStartingMessage(): void {
    Handler.info(`Server listening on port ${this.port}`);
  }

  private id: string = generateId();
  private name: string;
  private filepath: string;
  private baseDir: string;
  private server: any;

  protected ['x-powered-by']: string = 'Aztec';
  protected ['public-path']: string;
  protected ['default-format']: string = 'json';

  protected v2: boolean = false;
  protected format: string;
  protected env: string = 'development';

  protected protocol: string = 'http';
  protected domain: string = 'localhost';
  protected port: number = 5050;

  protected pluginQueue: $Middleware[] = [];
  protected apps: any[] = [];

}
