//@template-ignore
import { VerificationError } from "./ts_base";
//@template-ignore
import { RequestObject, ResponseObject, ErrorCodes, Logging } from "./ts_service_base";


export type IMessageCallback = (data: any) => void;

export type ResponseListener = {
   ok: (response:any)=>void;
   err: (error: Error)=>void;
}

export class Service {
   public _name: string = null as any;

   constructor(protected _provider: ServiceProvider, name: string) {
      this._name = name;
      this._provider.services.set(name, this);
   }
}

export class ServiceProvider {
   services = new Map<string, Service>();
   requests = new Map<string, ResponseListener>();

   constructor(private sendPacket: IMessageCallback) {}

   onPacket(msg: RequestObject | ResponseObject) {
      Logging.log("CLIENT: Received message:", msg);
      if("method" in msg) {
         if(msg.id){ 
            Logging.log("CLIENT: Determined type is Request");
            // Request, which are not supported by client, so ignore
            return;
         } else {
            Logging.log("CLIENT: Determined type is Notification");
            //Notification. Send to Notification handler
            const [srvName, fncName] = msg.method.split(".");
            let service = this.services.get(srvName)
            if(!service) {
               Logging.log("CLIENT: Did not find Service wanted by Notification!", srvName);
            } else {
               //TODO: Implement Event thingy (or so :))
            }
         }
      } else {
         Logging.log("CLIENT: Determined type is Response");
         // Response
         let resListener = this.requests.get(msg.id);
         if(!resListener) return; // Ignore wrong responses
         if(msg.error) {
            if(msg.error.data && msg.error.data.$ == "verification_error") {
               resListener.err(new VerificationError(msg.error.data.type, msg.error.data.field, msg.error.data.value))
            } else {
               resListener.err(new Error(msg.error.message));
            }
         } else {
            resListener.ok(msg.result);
         }
      }
         
   }

   sendMessage(msg: RequestObject, res?: ResponseListener) {
      Logging.log("CLIENT: Sending Messgage", msg);
      if(msg.id) {
         this.requests.set(msg.id, res);
      }
      this.sendPacket(msg)
   }
}







declare var require: any;
export const getRandomBytes = (
   typeof self !== "undefined" && (self.crypto || (self as any).msCrypto)
      ? function () {
           // Browsers
           var crypto = self.crypto || (self as any).msCrypto;
           var QUOTA = 65536;
           return function (n: number) {
              var a = new Uint8Array(n);
              for (var i = 0; i < n; i += QUOTA) {
                 crypto.getRandomValues(
                    a.subarray(i, i + Math.min(n - i, QUOTA))
                 );
              }
              return a;
           };
        }
      : function () {
           // Node
           return require("crypto").randomBytes;
        }
)() as (cnt: number) => Uint8Array;

export const getRandomID = (length: number) => {
   return btoa(String.fromCharCode.apply(null, getRandomBytes(length) as any));
};
