import {
  RPCClientCodec,
  RPCClientResponseCodec,
  RPCClientNotificationCodec,
} from '../RPCClientCodec';

import {
  RPCError,
} from '../RPCError';

export interface ClientNotification {
  method: string;
  params?: string[];
}

export interface ClientRequest {
  jsonrpc: string;
  method: string;
  params?: string[];
  id?: number;
}

export interface ClientResponse {
  jsonrpc: string;
  result?: any;
  error?: RPCError;
  id?: number;
}

export class JSONRPCClientCodec implements RPCClientCodec {
  public request(method: string, args: any[], id?: number): any {
    const params = convertParamsToStringArray(args);

    const req: ClientRequest = {
      jsonrpc: '2.0',
      method: method,
      params: 0 === params.length ? null : params,
      id: id,
    };
    return JSON.stringify(req);
  }
  public response(res: any): RPCClientResponseCodec {
    const _res: ClientResponse = {
      id: res.id,
      jsonrpc: res.jsonrpc,
      result: res.result,
      error: res.error,
    };
    return new JSONRPCClientResponseCodec(_res);
  }
}

function convertParamsToStringArray(args: any[]): string[] | undefined {
  const values: string[] = [];

  if (undefined === args || null === args || 0 === args.length) {
    return values;
  }

  for (let indexI = 0; indexI < args.length; indexI++) {
    const arg = args[indexI];

    switch (typeof arg) {
      case 'boolean':
      case 'number': // enum
        values.push(String(arg));
        break;
      case 'string':
        values.push(arg);
        break;
      case 'object':  // array, map
        values.push(JSON.stringify(arg));
        break;
      default:
        throw new Error(`Not supported type[${typeof arg}]`);
    }
  }

  return values;
}

export class JSONRPCClientResponseCodec implements RPCClientResponseCodec {
  public constructor(private _res: ClientResponse) {
  }

  public id(): number | undefined {
    return this._res.id;
  }
  public error(): RPCError | undefined {
    return this._res.error;
  }
  public result(): any | undefined {
    return this._res.result;
  }

  public isNotification(): boolean {
    if (undefined !== this.id() || undefined === this.result()) {
      return false;
    }
    return true;
  }

  public notification(): RPCClientNotificationCodec | undefined {
    if (undefined !== this.id() || undefined === this.result()) {
      return undefined;
    }
    const _noti: ClientNotification = {
      method: this._res.result.method,
      params: this._res.result.params,
    };
    return new JSONRPCClientNotificationCodec(_noti);
  }
}

export class JSONRPCClientNotificationCodec implements RPCClientNotificationCodec {
  public constructor(private _noti: ClientNotification) {
  }

  public method(): string {
    return this._noti.method;
  }
  public params(): any[] | undefined {
    return this._noti.params;
  }
}
