import { Readable } from 'stream';
import { us_listen_socket_close, TemplatedApp, HttpResponse, HttpRequest } from 'uWebSockets.js';

import formData from './formdata';
import { stob } from './utils';
import { Handler } from './types';
import {join} from "path";

const contTypes = ['application/x-www-form-urlencoded', 'multipart/form-data'];
const noOp = () => true;

const handleBody = (res: HttpResponse, req: HttpRequest) => {
  const contType = req.getHeader('content-type');

  res.bodyStream = function() {
    const stream = new Readable();
    stream._read = noOp; // eslint-disable-line @typescript-eslint/unbound-method

    this.onData((ab: ArrayBuffer, isLast: boolean) => {
      // uint and then slicing is bit faster than slice and then uint
      stream.push(new Uint8Array(ab.slice((ab as any).byteOffset, ab.byteLength))); // eslint-disable-line @typescript-eslint/no-explicit-any
      if (isLast) {
        stream.push(null);
      }
    });

    return stream;
  };

  res.body = () => stob(res.bodyStream());

  if (contType.includes('application/json'))
    res.json = async () => JSON.parse(await res.body());
  if (contTypes.map(t => contType.includes(t)).includes(true))
    res.formData = formData.bind(res, contType);
};

class BaseApp {
  _sockets = new Map();
  ws!: TemplatedApp['ws'];
  get!: TemplatedApp['get'];
  _post!: TemplatedApp['post'];
  _put!: TemplatedApp['put'];
  _patch!: TemplatedApp['patch'];
  _listen!: TemplatedApp['listen'];

  post(pattern: string, handler: Handler) {
    if (typeof handler !== 'function')
      throw Error(`handler should be a function, given ${typeof handler}.`);
    this._post(pattern, (res, req) => {
      handleBody(res, req);
      handler(res, req);
    });
    return this;
  }

  put(pattern: string, handler: Handler) {
    if (typeof handler !== 'function')
      throw Error(`handler should be a function, given ${typeof handler}.`);
    this._put(pattern, (res, req) => {
      handleBody(res, req);

      handler(res, req);
    });
    return this;
  }

  patch(pattern: string, handler: Handler) {
    if (typeof handler !== 'function')
      throw Error(`handler should be a function, given ${typeof handler}.`);
    this._patch(pattern, (res, req) => {
      handleBody(res, req);

      handler(res, req);
    });
    return this;
  }

  listen(h: string | number, p: Function | number = noOp, cb?: Function) {
    if (typeof p === 'number' && typeof h === 'string') {
      this._listen(h, p, socket => {
        this._sockets.set(p, socket);
        if (cb === undefined) {
          throw new Error('cb undefined');
        }
        cb(socket);
      });
    } else if (typeof h === 'number' && typeof p === 'function') {
      this._listen(h, socket => {
        this._sockets.set(h, socket);
        p(socket);
      });
    } else {
      throw Error(
        'Argument types: (host: string, port: number, cb?: Function) | (port: number, cb?: Function)'
      );
    }

    return this;
  }

  close(port: null | number = null) {
    if (port) {
      this._sockets.has(port) && us_listen_socket_close(this._sockets.get(port));
      this._sockets.delete(port);
    } else {
      this._sockets.forEach(app => {
        us_listen_socket_close(app);
      });
      this._sockets.clear();
    }
    return this;
  }
}

export default BaseApp;
