import { concatArrayBuffer } from "../crypto/utility";

/**
 * For mutations requests we need a token.
 * 1 user can use max 32 tokens per nano
 * if you start a request with a token, which is under process with an earlier request. It can
 * ruin the earlier call. So lock that token in this case.
 */
export type MutationToken = {
  token: Uint8Array;
  locked: boolean;
  counter: number;
  expired: boolean;
};


export abstract class NanoRequestBase {
  protected request;
  protected payloads = [];

  /**
   * Create the valid request message. Defined by the server
   * @returns
   */
  public getRequest() {
    return this.request;
  }

  /**
   * incoming chunk
   * @param payload
   */
  public push(payload): Promise<void> {
    this.payloads.push(payload)
    return Promise.resolve();
  }

  /**
   * The message coming as parts from the server. The parts's structure are depend on the request type (getDir, getFile, etc)
   * Override this merge function to fit the actual request
   * @param payloads an array, what contains all of the msg part
   * @returns the message from the server
   */
  public abstract mergePayloads();

  public reset(): Promise<void>{
    this.payloads = [];
    return Promise.resolve();
  }

  public setMutationToken(mutationToken: MutationToken){
    this.request['mut_key'] = mutationToken.token
    this.request['mut_seq'] = mutationToken.counter
  }
}

export class MutationTokenRequest extends NanoRequestBase {
  public constructor(roomId) {
    super();

    this.request = {
      type: "MUT-CRT",
      room_id: roomId,
    };
  }

  public mergePayloads() {
    return Promise.resolve(this.payloads[0].token);
  }
}

export class MutationTokenAdminRequest extends NanoRequestBase {
  public constructor(nanoId) {
    super();

    this.request = {
      type: "MUT-CRT",
      nano_id: nanoId,
    };
  }

  public mergePayloads() {
    return Promise.resolve(this.payloads[0].token);
  }
}

export class NanoFileSystemDeleteRequest extends NanoRequestBase {
  public constructor(
    resourceId: string,
    path: string,
    mutationToken?: MutationToken
  ) {
    super();

    this.request = {
      type: "FS-DEL",
      room_id: resourceId,
      path: path
    };

    if (mutationToken){
      this.setMutationToken(mutationToken);
    }
  }

  public mergePayloads() {
    return Promise.resolve(true);
  }
}

export class NanoFilesystemMkdirRequest extends NanoRequestBase {
  public constructor(
    resourceId: string,
    path: string,
    mutationToken?: MutationToken
  ) {
    super();

    this.request = {
      type: "FS-MKD",
      room_id: resourceId,
      path: path
    };

    if (mutationToken){
      this.setMutationToken(mutationToken);
    }
  }

  public mergePayloads() {
    return Promise.resolve(true);
  }
}

export class NanoFilesystemMoveRequestRequest extends NanoRequestBase {
  public constructor(
    resourceId: string,
    srcPath: string,
    newPath: string,
    mutationToken?: MutationToken
  ) {
    super();

    this.request = {
      type: "FS-MOV",
      room_id: resourceId,
      src_path: srcPath,
      new_path: newPath
    };

    if (mutationToken){
      this.setMutationToken(mutationToken);
    }
  }

  public mergePayloads() {
    return Promise.resolve(true);
  }
}

export class NanoFilesystemPutRequest extends NanoRequestBase {
  public constructor(
    resourceId: string,
    path: string,
    size,
    mtime,
    offset,
    data,
    smac,
    overwrite,
    mutationToken?: MutationToken
  ) {
    super();

    this.request = {
      type: "FS-PUT",
      room_id: resourceId,
      path: path,
      size: size,
      offset: offset,
      data: data,
      mtime: Math.round(mtime),
      smac: smac,
      overwrite: overwrite,
    };

    if (mutationToken){
      this.setMutationToken(mutationToken);
    }
  }

  public mergePayloads() {
    return Promise.resolve(true);
  }
}

export abstract class NanoFilesystemGetRequestForFile extends NanoRequestBase {
  public constructor(resourceId: string, path: string) {
    super();
    this.request = {
      type: "FS-GET",
      room_id: resourceId,
      path: path,
    };
  }
}

export class NanoFilesystemGetRequestForFileAsBlob extends NanoFilesystemGetRequestForFile {

  /**
   * fs - filesize is in the init payload
   * fc - file chunk in every part
   * @param payloads
   */
  public mergePayloads() {

    return new Promise((resolve, reject) => {
      try{
        resolve(new Blob(this.payloads, { type: "application/octet-stream" }))
      }
      catch(e){
        reject(e);
      }
      /*console.log("file payload", this.payloads);

      try{
        let arr = [];
        for (let i = 0; i < this.payloads.length; i++) {
          arr.push(this.payloads[i].buffer);
        }
  
        resolve(concatArrayBuffer(arr));
      }catch(e){
        reject(e);
      }*/
      
    });
    
  }

}


export interface FileSystemWritableFileStreamCommonInterface{
  write(payload: any): Promise<void>;
  close();
  truncate(size: number): Promise<void>;
}

export class NanoFilesystemGetRequestForFileAsFileStream extends NanoFilesystemGetRequestForFile{

  private fileSystemWritableFileStream: any;

  public constructor(resourceId: string, path: string, fileSystemWritableFileStream: FileSystemWritableFileStreamCommonInterface = null) {
    super(resourceId, path);
    this.fileSystemWritableFileStream = fileSystemWritableFileStream;
  }

  public push(payload){
    return this.fileSystemWritableFileStream.write(payload);
  }

  public mergePayloads() {
    return this.fileSystemWritableFileStream.close();
  }

  public reset(){
    return this.fileSystemWritableFileStream.truncate(0);
  }
}

export class NanoFilesystemGetRequestForDirectory extends NanoRequestBase {
  public constructor(resourceId: string, path: string) {
    super();
    this.request = {
      type: "FS-GET",
      room_id: resourceId,
      path: path,
    };
  }

  /**
   * There is an "ls" attribute, what contains the file list in an array
   * @param payloads
   */
  public mergePayloads() {
    
    return new Promise((resolve, reject) => {
      let list = [];
      this.payloads.forEach((payload) => {
        list = list.concat(payload.ls);
      });
      
      resolve(list);
    });
  }
}

export class NanoFilesystemPeekRequest extends NanoRequestBase {
  public constructor(resourceId: string, path: string) {
    super();
    this.request = {
      type: "FS-PEEK",
      room_id: resourceId,
      path: path,
    };
  }

  /**
   * There is an "ls" attribute, what contains the file list in an array
   * @param payloads
   */
  public mergePayloads() {
    
    return new Promise((resolve, reject) => {      
      resolve(this.payloads[0].pk);
    });
  }
}

export class NanoSearchRequest extends NanoRequestBase {
  public constructor(resourceId: string, query: string, lang: string) {
    super();

    this.request = {
      type: "SEARCH",
      room_id: resourceId,
      query: query,
      language: lang,
    };
  }

  public mergePayloads() {
    return new Promise((resolve, reject) => {
      let res = [];
      this.payloads.forEach((msg) => {
        res = res.concat(msg.docs);
      });

      resolve(res);
    });
  }
}

export class AdminGetInfoRequest extends NanoRequestBase {
  public constructor(nanoId: number) {
    super();

    this.request = {
      type: "A-INFO",
      id: nanoId,
    };
  }

  public mergePayloads() {
    return new Promise((resolve, reject) => {
      console.log("payloads", this.payloads);

      resolve({
        name: this.payloads[0].name,
        version: this.payloads[0].version,
      });
    });
  }
}

export class AdminGetRemoteConfigRequest extends NanoRequestBase {
  public constructor(nanoId: number, adminPassword: string) {
    super();

    this.request = {
      admin_password: adminPassword,
      type: "A-REM-CFG",
      id: nanoId,
    };
  }

  public mergePayloads() {
    return new Promise((resolve, reject) => {
      console.log("payloads", this.payloads);

      resolve(this.payloads[0]["config"]);
    });
  }
}

export class AdminChangePasswordRequest extends NanoRequestBase {
  public constructor(
    nanoId: number,
    oldPassword: string,
    newPassword: string,
    mutationToken?: MutationToken
  ) {
    super();

    this.request = {
      admin_password: oldPassword,
      type: "A-CH-PWD",
      id: nanoId,
      new_password: newPassword
    };

    if (mutationToken){
      this.setMutationToken(mutationToken);
    }
  }

  public mergePayloads() {
    return new Promise((resolve, reject) => {
      console.log("payloads", this.payloads);

      resolve(true); // there is no payloads for this
    });
  }
}

export class AdminGetListingRequest extends NanoRequestBase {
  public constructor(nanoId: number, path: string, adminPassword: string) {
    super();

    this.request = {
      type: "A-LISTING",
      id: nanoId,
      path,
      admin_password: adminPassword,
    };
  }

  public mergePayloads() {
    return new Promise((resolve, reject) => {
      console.log("payloads", this.payloads);

      resolve(this.payloads[0].ls);
    });
  }
}

export class AdminMkdirRequest extends NanoRequestBase {
  public constructor(
    nanoId: number,
    path: string,
    name: string,

    adminPassword: string,
    mutationToken?: MutationToken
  ) {
    super();

    this.request = {
      type: "A-MKD",
      id: nanoId,
      path,
      name,
      admin_password: adminPassword,
    };

    if (mutationToken){
      this.setMutationToken(mutationToken);
    }
  }

  public mergePayloads() {
    return new Promise((resolve, reject) => {
      console.log("payloads", this.payloads);
      resolve(true);
    });
  }
}

export class AdminCreateDriveRequest extends NanoRequestBase {
  public constructor(
    nanoId: number,
    path: string,
    adminPassword: string,
    mutationToken?: MutationToken
  ) {
    super();

    this.request = {
      type: "A-D-CREATE",
      id: nanoId,
      path,
      admin_password: adminPassword,
    };

    if (mutationToken){
      this.setMutationToken(mutationToken);
    }
  }

  public mergePayloads() {
    return new Promise((resolve, reject) => {
      console.log("payloads", this.payloads);
      resolve(true);
    });
  }
}

export class AdminDeleteDriveRequest extends NanoRequestBase {
  public constructor(
    nanoId: number,
    path: string,
    adminPassword: string,
    mutationToken?: MutationToken
  ) {
    super();

    this.request = {
      type: "A-D-DELETE",
      id: nanoId,
      path,
      admin_password: adminPassword,
    };

    if (mutationToken){
      this.setMutationToken(mutationToken);
    }
  }

  public mergePayloads() {
    return new Promise((resolve, reject) => {
      console.log("payloads", this.payloads);
      resolve(true);
    });
  }
}

export class AdminAttachDriveRequest extends NanoRequestBase {
  public constructor(
    nanoId: number,
    path: string,
    roomId: string,
    roomBlocks: string[],
    adminPassword: string,
    mutationToken?: MutationToken
  ) {
    super();

    this.request = {
      type: "A-D-ATTACH",
      id: nanoId,
      path,
      target_room_id: roomId,
      room_blocks: roomBlocks,
      admin_password: adminPassword,
    };

    if (mutationToken){
      this.setMutationToken(mutationToken);
    }
  }

  public mergePayloads() {
    return new Promise((resolve, reject) => {
      console.log("payloads", this.payloads);

      resolve(true);
    });
  }
}

export class AdminDetachDriveRequest extends NanoRequestBase {
  public constructor(
    nanoId: number,
    path: string,
    roomId: string,
    adminPassword: string,
    mutationToken?: MutationToken
  ) {
    super();

    this.request = {
      type: "A-D-DETACH",
      id: nanoId,
      path,
      target_room_id: roomId,
      admin_password: adminPassword,
    };

    if (mutationToken){
      this.setMutationToken(mutationToken);
    }
  }

  public mergePayloads() {
    return new Promise((resolve, reject) => {
      console.log("payloads", this.payloads);

      resolve(true);
    });
  }
}

export class AdminEditNameRequest extends NanoRequestBase {
  public constructor(
    nanoId: number,
    name: string,
    adminPassword: string,
    mutationToken?: MutationToken
  ) {
    super();

    this.request = {
      type: "A-EDIT-N",
      id: nanoId,
      name,
      admin_password: adminPassword,
    };

    if (mutationToken){
      this.setMutationToken(mutationToken);
    }
  }

  public mergePayloads() {
    return new Promise((resolve, reject) => {
      console.log("payloads", this.payloads);

      resolve(true);
    });
  }
}

export class AdminEditDenyAnonymousRequest extends NanoRequestBase {
  public constructor(
    nanoId: number,
    value: boolean,
    adminPassword: string,
    mutationToken?: MutationToken
  ) {
    super();

    this.request = {
      type: "A-EDIT-DENY-ANON",
      id: nanoId,
      value,
      admin_password: adminPassword,
    };

    if (mutationToken){
      this.setMutationToken(mutationToken);
    }
  }

  public mergePayloads() {
    return new Promise((resolve, reject) => {
      console.log("payloads", this.payloads);

      resolve(true);
    });
  }
}

export class NanoCreateRoomConfigBlockRequest extends NanoRequestBase {
  public constructor(
    resourceId: number,
    memberFlags: Object,
    groupFlags: number,
    mutationToken?: MutationToken
  ) {
    super();

    this.request = {
      type: "ROOM-CFG",
      room_id: resourceId,
      member_flags: memberFlags,
      group_flags: groupFlags,
    };

    if (mutationToken){
      this.setMutationToken(mutationToken);
    }
  }

  public mergePayloads() {
    return new Promise((resolve, reject) => {
      console.log("payloads", this.payloads);

      resolve(this.payloads[0]["block_id"]);
    });
  }
}
