/**
 * Virtual Machines API
 *
 * @author Pierre HUBERT
 */

import { APIClient } from "./ApiClient";

export type VMState =
  | "NoState"
  | "Running"
  | "Blocked"
  | "Paused"
  | "Shutdown"
  | "Shutoff"
  | "Crashed"
  | "PowerManagementSuspended"
  | "Other";

export type DiskAllocType = "Sparse" | "Fixed";

export interface VMDisk {
  size: number;
  name: string;
  alloc_type: DiskAllocType;
  delete: boolean;

  // application attribute
  new?: boolean;
  deleteType?: "keepfile" | "deletefile";
}

export interface VMNetInterfaceFilterParams {
  name: string;
  value: string;
}

export interface VMNetInterfaceFilter {
  name: string;
  parameters: VMNetInterfaceFilterParams[];
}

export type VMNetInterface = (VMNetUserspaceSLIRPStack | VMNetDefinedNetwork) &
  VMNetInterfaceBase;

export interface VMNetInterfaceBase {
  mac: string;
  nwfilterref?: VMNetInterfaceFilter;
}

export interface VMNetUserspaceSLIRPStack {
  type: "UserspaceSLIRPStack";
}

export interface VMNetDefinedNetwork {
  type: "DefinedNetwork";
  network: string;
}

interface VMInfoInterface {
  name: string;
  uuid?: string;
  genid?: string;
  title?: string;
  description?: string;
  boot_type: "UEFI" | "UEFISecureBoot";
  architecture: "i686" | "x86_64";
  memory: number;
  number_vcpu: number;
  vnc_access: boolean;
  iso_files: string[];
  disks: VMDisk[];
  networks: VMNetInterface[];
  tpm_module: boolean;
}

export class VMInfo implements VMInfoInterface {
  name: string;
  uuid?: string;
  genid?: string;
  title?: string;
  description?: string;
  boot_type: "UEFI" | "UEFISecureBoot";
  architecture: "i686" | "x86_64";
  number_vcpu: number;
  memory: number;
  vnc_access: boolean;
  iso_files: string[];
  disks: VMDisk[];
  networks: VMNetInterface[];
  tpm_module: boolean;

  constructor(int: VMInfoInterface) {
    this.name = int.name;
    this.uuid = int.uuid;
    this.genid = int.genid;
    this.title = int.title;
    this.description = int.description;
    this.boot_type = int.boot_type;
    this.architecture = int.architecture;
    this.number_vcpu = int.number_vcpu;
    this.memory = int.memory;
    this.vnc_access = int.vnc_access;
    this.iso_files = int.iso_files;
    this.disks = int.disks;
    this.networks = int.networks;
    this.tpm_module = int.tpm_module;
  }

  static NewEmpty(): VMInfo {
    return new VMInfo({
      name: "",
      boot_type: "UEFI",
      architecture: "x86_64",
      memory: 1024,
      number_vcpu: 1,
      vnc_access: true,
      iso_files: [],
      disks: [],
      networks: [],
      tpm_module: true,
    });
  }

  get ViewURL(): string {
    return `/vm/${this.uuid}`;
  }

  get EditURL(): string {
    return `/vm/${this.uuid}/edit`;
  }

  get VNCURL(): string {
    return `/vm/${this.uuid}/vnc`;
  }

  get XMLURL(): string {
    return `/vm/${this.uuid}/xml`;
  }
}

export class VMApi {
  /**
   * Create a new virtual machine
   */
  static async Create(v: VMInfo): Promise<{ uuid: string }> {
    return (
      await APIClient.exec({
        uri: `/vm/create`,
        method: "POST",
        jsonData: v,
      })
    ).data;
  }

  /**
   * Get the list of defined virtual machines
   */
  static async GetList(): Promise<VMInfo[]> {
    return (
      await APIClient.exec({
        uri: "/vm/list",
        method: "GET",
      })
    ).data.map((i: VMInfoInterface) => new VMInfo(i));
  }

  /**
   * Get the information about a single VM
   */
  static async GetSingle(uuid: string): Promise<VMInfo> {
    const data = (
      await APIClient.exec({
        uri: `/vm/${uuid}`,
        method: "GET",
      })
    ).data;
    return new VMInfo(data);
  }

  /**
   * Get the source XML configuration of a domain for debugging purposes
   */
  static async GetSingleXML(uuid: string): Promise<string> {
    return (
      await APIClient.exec({
        uri: `/vm/${uuid}/src`,
        method: "GET",
      })
    ).data;
  }

  /**
   * Update the information about a single VM
   */
  static async UpdateSingle(vm: VMInfo): Promise<VMInfo> {
    // Process disks list, looking for removal
    vm.disks = vm.disks.filter((d) => d.deleteType !== "keepfile");
    vm.disks.forEach((d) => {
      if (d.deleteType === "deletefile") d.delete = true;
    });

    const data = (
      await APIClient.exec({
        uri: `/vm/${vm.uuid!}`,
        method: "PUT",
        jsonData: vm,
      })
    ).data;
    return new VMInfo(data);
  }

  /**
   * Check if autostart is enabled on a VM
   */
  static async IsAutostart(vm: VMInfo): Promise<boolean> {
    return (
      await APIClient.exec({
        uri: `/vm/${vm.uuid}/autostart`,
        method: "GET",
      })
    ).data.autostart;
  }

  /**
   * Set autostart status of a VM
   */
  static async SetAutostart(vm: VMInfo, enabled: boolean): Promise<void> {
    await APIClient.exec({
      uri: `/vm/${vm.uuid}/autostart`,
      method: "PUT",
      jsonData: { autostart: enabled },
    });
  }

  /**
   * Get the state of a VM
   */
  static async GetState(vm: VMInfo): Promise<VMState> {
    return (
      await APIClient.exec({
        uri: `/vm/${vm.uuid}/state`,
        method: "GET",
      })
    ).data.state;
  }

  /**
   * Get a screenshot of a VM
   */
  static async Screenshot(vm: VMInfo): Promise<Blob> {
    return (
      await APIClient.exec({
        uri: `/vm/${vm.uuid}/screenshot`,
        method: "GET",
      })
    ).data;
  }

  /**
   * Start the VM
   */
  static async StartVM(vm: VMInfo): Promise<void> {
    return (
      await APIClient.exec({
        uri: `/vm/${vm.uuid}/start`,
        method: "GET",
      })
    ).data.state;
  }

  /**
   * Shutdown the VM
   */
  static async ShutdownVM(vm: VMInfo): Promise<void> {
    return (
      await APIClient.exec({
        uri: `/vm/${vm.uuid}/shutdown`,
        method: "GET",
      })
    ).data.state;
  }

  /**
   * Restt the VM
   */
  static async ResetVM(vm: VMInfo): Promise<void> {
    return (
      await APIClient.exec({
        uri: `/vm/${vm.uuid}/reset`,
        method: "GET",
      })
    ).data.state;
  }

  /**
   * Kill the VM
   */
  static async KillVM(vm: VMInfo): Promise<void> {
    return (
      await APIClient.exec({
        uri: `/vm/${vm.uuid}/kill`,
        method: "GET",
      })
    ).data.state;
  }

  /**
   * Suspend the VM
   */
  static async SuspendVM(vm: VMInfo): Promise<void> {
    return (
      await APIClient.exec({
        uri: `/vm/${vm.uuid}/suspend`,
        method: "GET",
      })
    ).data.state;
  }

  /**
   * Resume the VM
   */
  static async ResumeVM(vm: VMInfo): Promise<void> {
    return (
      await APIClient.exec({
        uri: `/vm/${vm.uuid}/resume`,
        method: "GET",
      })
    ).data.state;
  }

  /**
   * Delete a virtual machine
   */
  static async Delete(vm: VMInfo, keep_files: boolean): Promise<void> {
    await APIClient.exec({
      uri: `/vm/${vm.uuid}`,
      method: "DELETE",
      jsonData: { keep_files },
    });
  }

  /**
   * Get a oneshot VNC connect URL
   */
  static async OneShotVNCURL(vm: VMInfo): Promise<string> {
    const token = (
      await APIClient.exec({
        uri: `/vm/${vm.uuid}/vnc_token`,
        method: "GET",
      })
    ).data.token;

    let baseWSURL = APIClient.backendURL();
    if (!baseWSURL.includes("://"))
      baseWSURL =
        window.location.protocol + "//" + window.location.host + baseWSURL;

    return (
      baseWSURL.replace("http", "ws") +
      "/vnc?token=" +
      encodeURIComponent(token)
    );
  }
}