import { Panel } from "./panel";
import { PermissionExplorer, PermissionPanel } from "./permissionPanel";
import { getAllActions, getErrorMessage, readAllActionPermission, setActionPermission, removeActionPermission, globular, getRessources, eventHub, removeRessource } from "./backend";
import { randomUUID, rgbToHsl } from "./utility";
import { Ressource } from "globular-web-client/lib/ressource/ressource_pb";

let emptyPath = "...";
/**
 * Panel to be use to manage ressource access other than files.
 * A ressource is somthing with a path. A path is a string with hierachy 
 * where the symbol '/' is use as level marquer.
 */
export class RessourceManager extends Panel {
  private actionPermissionManager: ActionPermissionManager;
  private pathNavigator: RessourcePathNavigator;
  private ressourcePanel: RessourcesPanel;
  private permissionExplorer: PermissionExplorer;

  constructor() {
    super("ressource_manager");

    // create the ressource action permission manager.
    this.actionPermissionManager = new ActionPermissionManager();
    this.actionPermissionManager.setParent(this.div);

    // Create the ressource path navigator.
    this.pathNavigator = new RessourcePathNavigator();
    this.pathNavigator.setParent(this.div);

    // Create the ressource panel.
    this.ressourcePanel = new RessourcesPanel();
    this.ressourcePanel.setParent(this.div);

    // Create the permission explorer.
    this.permissionExplorer = new PermissionExplorer("ressource_permission_explorer", this.div);

    // Emit when user click on the path
    eventHub.subscribe(
      "set_ressource_path_event",
      (uuid: string) => { },
      (evt: any) => {
        // Set the dir to display.
        // Here I must retreive the directory from the given path.
        let dir = this.ressourcePanel.getRessourceDir(evt.path)

        this.pathNavigator.setPath(evt.path)
        this.permissionExplorer.setRessource(dir);

      },
      true
    );

    eventHub.subscribe(
      "delete_ressource_event",
      (uuid: string) => { },
      (evt: any) => {
        // Set the dir to display.
        this.pathNavigator.setPath("")
        this.permissionExplorer.setRessource(null);

      },
      true
    );

    // Emit when user change the ressource directory.
    eventHub.subscribe(
      "set_ressource_event",
      (uuid: string) => { },
      (evt: any) => {
        // Set the dir to display.
        // Here I must retreive the directory from the given path.
        let dir = this.ressourcePanel.getRessourceDir(evt.ressource.path)
        if (dir != undefined) {
          this.pathNavigator.setPath(evt.ressource.path)
        }
      },
      true
    );
  }

}

/**
 * That panel is use to set ressource action permission. 
 * The action permission can be READ | WRITE | DELETE or any combination of those permission.
 */
class ActionPermissionManager extends Panel {

  // Set editable.
  private editable: boolean;

  // The ressource manager
  constructor() {
    super("ressource_action_permission_manager");
    this.editable = false;
    this.displayPermissions();
  }

  // Here I will react to login information...
  onlogin(data: any) {
    // overide...
    this.editable = true;
    this.displayPermissions();
  }

  onlogout() {
    // overide...
    this.editable = false;
    this.displayPermissions();
  }

  displayPermissions() {
    this.div.removeAllChilds();

    // Display the header.
    let content = this.div.appendElement({ tag: "div", class: "card col s12", style: "padding:10px;" }).down()

    content.appendElement({ tag: "div", class: "row hide-on-small-only" }).down()
      .appendElement({ tag: "div", class: "col m6 l8", style: "border-right: 1px solid lightgray;", innerHtml: "Action(s)" })
      .appendElement({ tag: "div", class: "col m6 l4", innerHtml: "Permission(s)" })

    if (this.editable) {
      // In that case I will append the action selector.
      getAllActions(
        (actions: Array<string>) => {
          // In that case I wil get the list of all operations.
          // Here I will append the actions list.
          let uuid = randomUUID()
          let action_input = content
            .prependElement({ tag: "div", class: "row" })
            .down()
            .appendElement({ tag: "div", class: "input-field col s8" })
            .down()
            .appendElement({
              tag: "input",
              class: "autocomplete",
              placeholder: "New Action",
              id: uuid
            })
            .down();

          let data: any;
          data = {};
          for (var i = 0; i < actions.length; i++) {
            data[actions[i]] = null;
          }

          // The action call on auto complete...
          let onAutocomplete = () => {
            let action = action_input.element.value;
            setActionPermission(action, 0,
              () => {
                M.toast({
                  html: "Action pemission " + action + " has been added!",
                  displayLength: 2000
                });
                this.displayPermissions()
              }, (err: any) => {
                M.toast({ html: getErrorMessage(err.message), displayLength: 2000 });
              });
          }

          // call after the ressource actions are retreived.
          let callback = () => {
            
              M.Autocomplete.init(document.getElementById(uuid), {
                data: data,
                onAutocomplete: onAutocomplete
              });
            
          }

          readAllActionPermission(
            (actionPermission: Array<any>) => {
              // remove existing permission... 
              for (var i = 0; i < actionPermission.length; i++) {
                delete data[actionPermission[i].action]
              }
              callback() // done remove already existing values.
            },
            (err: any) => {
              callback() // simply go to callback.
            })
        },
        (err: any) => {
          M.toast({ html: getErrorMessage(err.message), displayLength: 2000 });
        });
    }

    // Display all permissions.
    readAllActionPermission(
      (actionPermission: Array<any>) => {
        // remove existing permission... 
        for (var i = 0; i < actionPermission.length; i++) {
          let action = actionPermission[i].action;
          let permission = actionPermission[i].permission;
          let div = content.appendElement({ tag: "div", class: "row", style: "padding: 5px 10px 5px 10px; margin: 0px;" }).down();
          div.appendElement({ tag: "div", id: "permission_div_0", class: "input-field col s12 m6 l8", style: "padding: 0px; margin-left: 10px; margin: 0px;", innerHtml: action })
          let permissionDiv = div.appendElement({ tag: "div", id: "permission_div_1", class: "input-field col s12 m6 l4", style: "padding: 0px; margin: 0px; display: flex; margin-bottom: 5px;" }).down()

          // Mouse over to make reading little easier.
          div.getChildById("permission_div_0").element.onmouseenter = div.getChildById("permission_div_1").element.onmouseenter = () => {
            div.getChildById("permission_div_0").element.style.backgroundColor = "#fafafa"
            div.getChildById("permission_div_1").element.style.backgroundColor = "#fafafa"
          }

          div.getChildById("permission_div_0").element.onmouseleave = div.getChildById("permission_div_1").element.onmouseleave = () => {
            div.getChildById("permission_div_0").element.style.backgroundColor = ""
            div.getChildById("permission_div_1").element.style.backgroundColor = ""
          }

          // Set the permission panel.
          new PermissionPanel(randomUUID(), permissionDiv, permission,
            // on change permission
            (permission_number: number) => {
              setActionPermission(action, permission_number, () => {
                this.displayPermissions()
                M.toast({ html: action + " permission was change!", displayLength: 2000 });
              },
                (err: any) => {
                  M.toast({ html: getErrorMessage(err.message), displayLength: 2000 });
                })
            },
            // On delete callback
            () => {
              removeActionPermission(action, () => {
                this.displayPermissions()
                M.toast({ html: action + " permission was deleted!", displayLength: 2000 });
              },
                (err: any) => {
                  M.toast({ html: getErrorMessage(err.message), displayLength: 2000 });
                })
            }, this.editable)
        }
      },
      (err: any) => {
        /** nothing to do here... */
      })
  }
}

/**
 * 
 */
class RessourcePathNavigator extends Panel {
  private content: any;
  private path: string;

  constructor() {
    super(randomUUID());

    // Set the div style.

    // Set the content.
    this.content = this.div.appendElement({ tag: "nav", class: "row card col s12 /*m10 offset-m1*/ indigo darken-4" }).down()
      .appendElement({ tag: "div", class: "nav-wrapper" }).down()
      .appendElement({ tag: "div" }).down()
      .appendElement({ tag: "div", class: "col s12" }).down()

    // Set root path...
    this.setPath("");
  }

  // The the path nivagator path.
  setPath(path: string) {
    this.content.removeAllChilds()

    this.path = "path"
    let values = path.split("/");
    values.unshift(emptyPath)

    let div = this.content.appendElement({ tag: "div", class: "col s12" }).down()

    for (var i = 0; i < values.length; i++) {
      if (values[i].length > 0) {
        let lnk = div
          .appendElement({
            tag: "a",
            innerHtml: values[i],
            class: "breadcrumb"
          })
          .down();

        lnk.element.onmouseenter = function () {
          this.style.cursor = "pointer";
        };
        lnk.element.onmouseleave = function () {
          this.style.cursor = "default";
        };
        let index = i + 1;

        lnk.element.onclick = () => {
          let path_ = "";
          for (var j = 1; j < index; j++) {
            path_ += values[j];
            if (j < index - 1) {
              path_ += "/";
            }
          }
          eventHub.publish("set_ressource_path_event", { path: path_ }, true);
        };
      }
    }
  }

}

/**
 * A recursive tree structure to display and and manage ressource as tree.
 */
class RessourceDir {

  // Keep sub ressource dir.
  private _ressourceDirs: Map<string, RessourceDir>;
  public get ressourceDirs(): Map<string, RessourceDir> {
    return this._ressourceDirs;
  }

  // The list of ressource contain in the dir.
  private _ressources: Map<string, Ressource>;
  public get ressources(): Map<string, Ressource> {
    return this._ressources;
  }

  private _parent: RessourceDir;
  public get parent(): RessourceDir {
    return this._parent;
  }
  public set parent(value: RessourceDir) {
    this._parent = value;
  }

  // Return the name of the Ressource dir
  public get name(): string {
    return this.path.split("/")[this.path.split("/").length - 1]
  }

  // Keep ressource tree.
  constructor(public path: string) {

    // initialyse internal structure.
    this._ressourceDirs = new Map<string, RessourceDir>();
    this._ressources = new Map<string, Ressource>();
    this.parent = null;
  }

  appendRessource(ressource: Ressource) {
    // set the ressource.
    this._ressources.set(ressource.getName(), ressource);
  }

  appendRessourceDir(dir: RessourceDir) {
    dir.parent = this
    this._ressourceDirs.set(dir.path, dir);
  }

}

/**
 * Ressource panel is use to display and manage ressources. A ressource can be anything
 * that has a name propertie and a path.
 */
class RessourcesPanel extends Panel {

  // The list of ressources...
  private ressources: Map<string, RessourceDir>;
  private editable: boolean;

  constructor() {
    super(randomUUID())

    // That will contain all ressource dir by path.
    this.ressources = new Map<string, RessourceDir>();

    // init the ressources and display it after.
    this.initRessources(() => {

      // Set the editable parameter.
      this.displayRessources(null);
    })

    // Display the div.
    eventHub.subscribe(
      "delete_ressource_event",
      (uuid: string) => { },
      (evt: any) => {
        // Set the dir to display.
        this.initRessources(() => {

          // Set the editable parameter.
          this.displayRessources(null);
        })
      },
      true
    );

    // Display the div.
    eventHub.subscribe(
      "set_ressource_event",
      (uuid: string) => { },
      (evt: any) => {
        // Set the dir to display.
        if (this.ressources.has(evt.ressource.path)) {
          this.displayRessources(this.ressources.get(evt.ressource.path));
        }
      },
      true
    );

    // Emit when user click on the path
    eventHub.subscribe(
      "set_ressource_path_event",
      (uuid: string) => { },
      (evt: any) => {
        this.displayRessources(this.ressources.get(evt.path));
      },
      true
    );
  }


  // Here I will react to login information...
  onlogin(data: any) {
    // overide...
    this.editable = true;
    this.div.getChildById("name_title").element.className = "col m6"
  }

  onlogout() {
    // overide...
    this.editable = false;
    this.div.getChildById("name_title").element.className = "col m7"
  }

  /**
   * Init a ressource and create it dir if not already exist.
   * @param ressource 
   */
  initRessource(ressource: Ressource) {
    // I will create ressource dir from path and append it to it parent if it exist...
    let path = ressource.getPath()

    // Remove leading and trailling / symbols.
    if (path.startsWith("/")) {
      path = path.substr(1)
    }
    if (path.endsWith("/")) {
      path = path.substr(0, path.length - 1)
    }

    let paths = path.split("/")
    let path_ = "";
    let parent = "";
    for (var j = 0; j < paths.length; j++) {
      parent = path_
      path_ += "/" + paths[j]
      if (!this.ressources.has(path_)) {
        let dir = new RessourceDir(path_)
        this.ressources.set(path_, dir);
      }

      // append the dir.
      if (this.ressources.has(parent)) {
        this.ressources.get(parent).appendRessourceDir(this.ressources.get(path_))
      }

    }
    // append the ressource
    if (this.ressources.has(ressource.getPath())) {
      this.ressources.get(ressource.getPath()).appendRessource(ressource)
    }
  }

  initRessources(callback: () => void) {
    this.ressources.clear();
    this.div.removeAllChilds();
    getRessources("", "",
      (ressources: Array<Ressource>) => {
        // So here I will inialyse the ressources.
        for (var i = 0; i < ressources.length; i++) {
          this.initRessource(ressources[i])
        }

        callback() // end of ressource initialisation.
      },
      (err: any) => {
        M.toast({ html: getErrorMessage(err.message), displayLength: 2000 });
      });
  }

  getRessourceDir(dir: string) {
    return this.ressources.get(dir)
  }

  /**
   * display a ressources from a given path.
   * @param path 
   */
  displayRessources(dir: RessourceDir) {
    // I will retreive all ressources.
    this.div.removeAllChilds(); // remove all childs...
    let content = this.div.appendElement({ tag: "div", class: "card col s12", style: "padding: 10px;" }).down();

    content.appendElement({ tag: "div", class: "row hide-on-small-only" }).down()
      .appendElement({ tag: "div", id: "name_title", class: "col m7", style: "border-right: 1px solid lightgray;", innerHtml: "Name" })
      .appendElement({ tag: "div", id: "date_title", class: "col m3", style: "border-right: 1px solid lightgray;", innerHtml: "Date modified" })
      .appendElement({ tag: "div", id: "size_title", class: "col m2", innerHtml: "Size" })


    if (this.editable) {
      this.div.getChildById("name_title").element.className = "col m6"
    } else {
      this.div.getChildById("name_title").element.className = "col m7"
    }

    // Now I will retreive all ressources from the server.
    if (dir == undefined) {
      // Here I will display all dir with not parent...
      this.ressources.forEach((dir: RessourceDir) => {
        if (dir.parent == null) {
          let ressourcePanel = new RessourcePanel(content, this.editable)
          ressourcePanel.setRessourceDir(dir)
        }
      })
    } else {
      // Set dir
      dir.ressourceDirs.forEach((dir: RessourceDir) => {
        let ressourcePanel = new RessourcePanel(content, this.editable)
        ressourcePanel.setRessourceDir(dir)
      })

      // Set ressource
      dir.ressources.forEach((r: Ressource) => {
        let ressourcePanel = new RessourcePanel(content, this.editable)
        ressourcePanel.setRessource(r)
      })
    }

  }

}

class RessourcePanel extends Panel {

  private dir: RessourceDir;
  private ressource: Ressource;
  private ico: any;
  private editable: boolean;

  constructor(parent: any, editable: boolean) {
    super(randomUUID())
    this.editable = editable;

    this.div = parent
      .appendElement({
        tag: "div",
        class: "row",
        style: "margin: 0px; padding: 5px;"
      })
      .down();

    eventHub.subscribe(
      "set_ressource_dir_event",
      (uuid: string) => { },
      (evt: any) => {
        // Set the dir to display.
        this.setRessourceDir(evt.dir);
      },
      true
    );
  }

  // Here I will display a ressource dir...
  setRessourceDir(dir: RessourceDir) {
    // clear the div.
    this.div.removeAllChilds()

    this.dir = dir;
    this.ico = this.div
      .appendElement({
        tag: "i",
        class: "Small material-icons col s1",
        innerHtml: "folder"
      })
      .down();

    this.ico.element.onclick = () => {
      eventHub.publish("set_ressource_event", { id: "ressource_permission_explorer", ressource: { path: dir.path, name: dir.name } }, true);
    }

    this.ico.element.onmouseenter = function () {
      this.style.cursor = "pointer"
    }

    this.ico.element.onmouseleave = function () {
      this.style.cursor = "default"
    }

    // The file name link...
    if (this.editable) {
      this.div
        .appendElement({
          tag: "span",
          innerHtml: dir.name,
          class: "col s10"
        })
        .down();

      // I will append a delete button in that particular case.
      let deleteFileBtn = this.div
        .appendElement({
          tag: "i",
          class: "Small material-icons col s1",
          innerHtml: "delete",
          style: "text-align: right;"
        })
        .down();

      deleteFileBtn.element.onmouseenter = function () {
        this.style.cursor = "pointer"
      }

      deleteFileBtn.element.onmouseleave = function () {
        this.style.cursor = "default"
      }

      deleteFileBtn.element.onclick = () => {
        removeRessource(dir.path, "",
          () => {
            eventHub.publish("delete_ressource_event", {}, true);
            M.toast({ html: "ressource for " + dir.path + " have been deleted!", displayLength: 2000 });
          },
          (err: any) => {
            M.toast({ html: getErrorMessage(err.message), displayLength: 2000 });
          });
      }

    } else {
      this.div
        .appendElement({
          tag: "span",
          innerHtml: dir.name,
          class: "col s11"
        })
        .down();
    }
  }

  // Here I will display a ressource.
  setRessource(ressource: Ressource) {
    this.ressource = ressource;
    this.div.removeAllChilds();

    this.ico = this.div
      .appendElement({
        tag: "i",
        class: "Small material-icons col s1",
        innerHtml: "insert_drive_file"
      })
      .down();

    this.ico.element.onclick = () => {
      eventHub.publish("set_ressource_event", { id: "ressource_permission_explorer", ressource: { path: ressource.getPath() + "/" + ressource.getName(), name: ressource.getName() } }, true);
    }

    this.ico.element.onmouseenter = function () {
      this.style.cursor = "pointer"
    }

    this.ico.element.onmouseleave = function () {
      this.style.cursor = "default"
    }

    let nameSpan = this.div
      .appendElement({
        tag: "span",
        innerHtml: ressource.getName(),
        class: "col s11 m6"
      })
      .down();

    let lastModified = new Date(ressource.getModified() * 1000);
    this.div.appendElement({
      tag: "div",
      class: "col s6 m3",
      innerHtml:
        lastModified.toLocaleDateString() +
        " " +
        lastModified.toLocaleTimeString()
    });

    let ressourceSizeDiv = this.div
      .appendElement({
        tag: "div",
        class: "col s6 m2"
      })
      .down();

    if (ressource.getSize() > 1024) {
      if (ressource.getSize() > 1024 * 1024) {
        if (ressource.getSize() > 1024 * 1024 * 1024) {
          let fileSize = ressource.getSize() / (1024 * 1024 * 1024);
          ressourceSizeDiv.element.innerHTML = fileSize.toFixed(0) + " Gb";
        } else {
          let fileSize = ressource.getSize() / (1024 * 1024);
          ressourceSizeDiv.element.innerHTML = fileSize.toFixed(0) + " Mb";
        }
      } else {
        let fileSize = ressource.getSize() / 1024;
        ressourceSizeDiv.element.innerHTML = fileSize.toFixed(0) + " Kb";
      }
    } else {
      ressourceSizeDiv.element.innerHTML = ressource.getSize() + " bytes";
    }

    if (this.editable) {

      // give space to delete button.
      nameSpan.element.className = "col s10 m5"

      // I will append a delete button in that particular case.
      let deleteFileBtn = this.div
        .appendElement({
          tag: "i",
          class: "Small material-icons col s1",
          innerHtml: "delete",
          style: "text-align: right;"
        })
        .down();

      deleteFileBtn.element.onmouseenter = function () {
        this.style.cursor = "pointer"
      }

      deleteFileBtn.element.onmouseleave = function () {
        this.style.cursor = "default"
      }

      deleteFileBtn.element.onclick = () => {
        // removeActionPermission
        removeRessource(ressource.getPath(), ressource.getName(),
          () => {
            eventHub.publish("delete_ressource_event", {}, true);
            M.toast({ html: "ressource for " + ressource.getName() + " have been deleted!", displayLength: 2000 });
          },
          (err: any) => {
            M.toast({ html: getErrorMessage(err.message), displayLength: 2000 });
          });
      }

    }

  }

  onlogin(data: any) {
    // overide...
    this.editable = true;
    if (this.ressource != undefined) {
      this.setRessource(this.ressource)
    } else {
      this.setRessourceDir(this.dir)
    }
  }

  onlogout() {
    // overide...
    this.editable = false;
    if (this.ressource != undefined) {
      this.setRessource(this.ressource)
    } else {
      this.setRessourceDir(this.dir)
    }
  }

}