import { ViewportScroller } from "@angular/common";
import { ReadKeyExpr } from "@angular/compiler";
import {
  Component,
  Input,
  OnInit,
  Output,
  SimpleChange,
  EventEmitter,
  Host,
  HostListener,
  ViewChild,
  ElementRef,
  ChangeDetectorRef,
  OnDestroy,
} from "@angular/core";
import { MatDialog } from "@angular/material/dialog";
import { MatMenuTrigger } from "@angular/material/menu";
import { marker } from "@biesbjerg/ngx-translate-extract-marker";
import { cookieServiceFactory } from "ngx-cookie";
import { DeleteFileDialogComponent } from "src/app/shared/dialogs/delete-file-dialog/delete-file-dialog.component";
import { NewFolderDialogComponent } from "src/app/shared/dialogs/new-folder-dialog/new-folder-dialog.component";
import { getFileFromClipboard } from "src/app/shared/file-reader";
import { AccountService } from "src/app/shared/server-services/account.service";
import { AuthService } from "src/app/shared/server-services/auth.service";
import { NanoService } from "src/app/shared/nano/nano.service";
import { MeRecord } from "src/app/shared/server-services/query-records/account-records";
import { NanoClientRecord } from "src/app/shared/server-services/query-records/nano-records";
import {
  roomConfigRecord,
  RoomRecord,
} from "src/app/shared/server-services/query-records/room-records";
import { WorkspaceSubscriptionRoomAccountPermissionEventRecord } from "src/app/shared/server-services/query-records/workspace-records";
import { getNanoSlotsQuery } from "src/app/shared/server-services/querys";
import { RoomService } from "src/app/shared/server-services/room.service";
import { ServerError } from "src/app/shared/server-services/server-errors";
import {
  SubscriptionServiceEvent,
  SubscriptionServiceEventType,
} from "src/app/shared/server-services/subscription-event";
import { SubscriptionService } from "src/app/shared/server-services/subscription.service";
import { CacheService } from "src/app/shared/services/cache.service";
import { DialogService } from "src/app/shared/services/dialog.service";
import {
  DownloadManagerService,
  FileManagerDirection,
} from "src/app/shared/services/download-manager-service";
import { NanoActionQueueService } from "src/app/shared/services/nano-action-queue.service";
import { RouterHandler } from "src/app/shared/services/router-handler.service";
import { ServerRestApiService } from "src/app/shared/services/server-rest-api.service";
import { DriveFile } from "./drive-file";
import { environment } from "src/environments/environment";
import { base64 } from "rfc4648";
import { AbstractRoomKeyring } from "src/app/shared/crypto/keyring/room_base";
import { ClipboardService } from "src/app/shared/services/clipboard.service";
enum ViewMode {
  TILE = 0,
  LIST = 1,
}
@Component({
  selector: "app-drive-layout",
  templateUrl: "./drive-layout.component.html",
  styleUrls: ["./drive-layout.component.scss"],
})
export class DriveLayoutComponent implements OnInit, OnDestroy {
  @Input("resourceId") resourceId;
  @Input("path") path;
  @Output("navigate") navigateEvent: EventEmitter<string> =
    new EventEmitter<string>();

  @ViewChild("hiddenClipboard", { read: ElementRef })
  hiddenClipboard: ElementRef;

  private routeSubscription;
  private driveViewCacheKey = "DriveViewMode";

  public folderStack = [];
  public isContentLoading = false;

  public showBackArrow = false;

  public currOrderBy = "name";
  public currOrderDirection = 1; // 1 - asc, -1 desc

  public viewMode: ViewMode = ViewMode.LIST;
  public viewModeEnum = ViewMode;

  public files: DriveFile[] = [];
  public selectedFiles: DriveFile[] = [];
  public lastInteracted: DriveFile;

  private elementInARow = 1;
  private ownRoom: boolean = false;

  public loadError = false;
  public pathError = false;
  public pinnedKeyError = false;
  public initLoadDone = false;
  public competingError = false;

  public competingClientNames: string;

  public isReadOnly: boolean = false;
  public canUpload: boolean = false;
  public navigatedFile: DriveFile;

  public showDropArea: boolean = false;

  constructor(
    private nanoService: NanoService,
    private routerHandler: RouterHandler,
    private el: ElementRef,
    public dialog: MatDialog,
    private downloadManagerService: DownloadManagerService,
    private nanoActionQueueService: NanoActionQueueService,
    private accountService: AccountService,
    private roomService: RoomService,
    private dialogService: DialogService,
    private authService: AuthService,
    private subscriptionService: SubscriptionService,
    private serverRestApi: ServerRestApiService,
    private clipboardService: ClipboardService
  ) {
    var cachedViewMode = <ViewMode>(
      (<unknown>localStorage.getItem(this.driveViewCacheKey))
    );
    if (cachedViewMode != undefined) this.viewMode = cachedViewMode;

    this.subscriptionService.subscribe(
      SubscriptionServiceEvent.ROOM_ACCOUNT_PERMISSION_EVENT,
      SubscriptionServiceEventType.ALL,
      this.onRoomAccountPermissionChange.bind(this)
    );
  }

  ngOnDestroy() {
    this.subscriptionService.unsubscribe(
      SubscriptionServiceEvent.ROOM_ACCOUNT_PERMISSION_EVENT,
      SubscriptionServiceEventType.ALL,
      this.onRoomAccountPermissionChange.bind(this)
    );
  }

  private onRoomAccountPermissionChange(
    event: WorkspaceSubscriptionRoomAccountPermissionEventRecord
  ) {
    if (this.resourceId == event.id) {
      this.checkReadOnly();
    }
  }

  @HostListener("window:keydown", ["$event"]) onKeyDown(e) {
    if (this.dialog.openDialogs.length > 0) return;

    if (e.ctrlKey) {
      this.hiddenClipboard.nativeElement.focus();
    }

    if (e.ctrlKey && e.code == "KeyA") {
      this.selectedFiles = this.files.slice(0, this.files.length);
      e.preventDefault();
    } else if (e.shiftKey && this.lastInteracted) {
      let index;

      if (e.code == "ArrowUp") {
        index = Math.max(
          this.files.indexOf(this.lastInteracted) - this.elementInARow,
          0
        );
      } else if (e.code == "ArrowDown") {
        index = Math.min(
          this.files.indexOf(this.lastInteracted) + this.elementInARow,
          this.files.length - 1
        );
      } else if (e.code == "ArrowLeft") {
        index = Math.max(this.files.indexOf(this.lastInteracted) - 1, 0);
      } else if (e.code == "ArrowRight") {
        index = Math.min(
          this.files.indexOf(this.lastInteracted) + 1,
          this.files.length - 1
        );
      }

      if (index != undefined) {
        if (this.selectedFiles.indexOf(this.files[index]) == -1) {
          this.selectedFiles.push(this.files[index]);
        }

        this.lastInteracted = this.files[index];
        e.preventDefault();
      }
    } else if (e.code == "ArrowUp" && this.lastInteracted) {
      this.lastInteracted =
        this.files[
          Math.max(
            0,
            this.files.indexOf(this.lastInteracted) - this.elementInARow
          )
        ];
      e.preventDefault();
    } else if (e.code == "ArrowDown" && this.lastInteracted) {
      this.lastInteracted =
        this.files[
          Math.min(
            this.files.length - 1,
            this.files.indexOf(this.lastInteracted) + this.elementInARow
          )
        ];
      e.preventDefault();
    } else if (e.code == "ArrowLeft" && this.lastInteracted) {
      this.lastInteracted =
        this.files[Math.max(0, this.files.indexOf(this.lastInteracted) - 1)];
      e.preventDefault();
    } else if (e.code == "ArrowRight" && this.lastInteracted) {
      this.lastInteracted =
        this.files[
          Math.min(
            this.files.length - 1,
            this.files.indexOf(this.lastInteracted) + 1
          )
        ];
      e.preventDefault();
    } else if (e.code == "Delete" || e.code == "F8") {
      this.openDeleteDialog();
    } else if (e.code == "F7") {
      this.openCreateFolderDialog();
    }
  }

  ngOnInit() {
    Promise.all([
      this.accountService.getMe(),
      this.roomService.getData(this.resourceId),
    ])
      .then(([me, room]) => {
        this.ownRoom = me.id == room.ownerAccountId;
      })
      .catch((error) => {
        console.error("Error during me or room query", error);
      });

    this.checkReadOnly();
  }

  ngOnChanges(change: SimpleChange) {
    if (
      change["path"] &&
      change["path"].currentValue !== change["path"].previousValue
    ) {
      if (
        change["path"].currentValue &&
        change["path"].currentValue.length > 0
      ) {
        this.folderStack = change["path"].currentValue.split("/");
      } else {
        this.folderStack = [];
      }
    }

    if (change["resourceId"]) {
      this.checkReadOnly();
    }

    this.listDirectory();
  }

  ngAfterViewInit() {
    this.elementInARow = this.calcElementInARow();
  }

  @HostListener("window:resize") onResize() {
    this.elementInARow = this.calcElementInARow();
  }

  private checkReadOnly() {
    if (this.authService.isAnonym()) {
      this.isReadOnly = true;
    } else {
      console.log("get my room perm", this.resourceId);
      this.roomService
        .getMyRoomPermissions(this.resourceId)
        .then((myPerm) => {
          this.isReadOnly =
            !myPerm.account ||
            !(
              myPerm.ownerAccountId === myPerm.account?.id ||
              myPerm.account.driveWrite
            );
          this.canUpload = !this.isReadOnly || myPerm.account?.driveUpload;
        })
        .catch(() => {
          this.isReadOnly = true;
          this.canUpload = false;
        });
    }
  }

  public openCreateFolderDialog() {
    const dialogRef = this.dialog.open(NewFolderDialogComponent, {
      data: { name: "Folder" }, // todo language default
    });

    dialogRef.afterClosed().subscribe((name) => {
      if (name && name.length > 0) {
        this.createFolder(name);
      }
    });
  }

  private createFolder(name) {
    if (this.folderStack.length > 0) {
      name = this.getCurrentFolderPath() + "/" + name;
    }
    this.nanoActionQueueService.makeDir(
      this.resourceId,
      name,
      () => {
        this.listDirectory(() => {
          for (let i = 0; i < this.files.length; i++) {
            /*console.log(
              "s",
              this.files[i].fullName,
              name,
              this.files[i].fullName == name
            );*/
            if (this.files[i].fullName == name) {
              this.lastInteracted = this.files[i];
              setTimeout(() => {
                /*console.log(
                  "file",
                  document.getElementById("f-" + this.files[i].fullName)
                );*/
                if (document.getElementById("f-" + this.files[i].fullName)) {
                  document
                    .getElementById("f-" + this.files[i].fullName)
                    .scrollIntoView({
                      behavior: "smooth",
                      block: "nearest",
                      inline: "nearest",
                    });
                }
              }, 1);
              break;
            }
          }
        });
      },
      (err) => {
        console.error("error", err);
        this.dialogService.openAlertDialog(
          marker("Error"),
          marker("Error happened during the operation")
        );
      }
    );
  }

  public openEditDialog() {
    let editFile = this.lastInteracted;
    this.dialogService
      .openInputDialog(
        marker("Edit file name"),
        marker("File name"),
        editFile.fullName
      )
      .subscribe((result) => {
        if (result && editFile.fullName != result) {
          let path = "";
          if (this.folderStack.length > 0) {
            path = this.getCurrentFolderPath() + "/" + path;
          }

          this.nanoService.moveFile(
            this.resourceId,
            path + editFile.fullName,
            path + result,
            () => {
              this.refresh();
            },
            () => {
              this.dialogService.openAlertDialog(
                marker("Error"),
                marker("Error happened during the operation")
              );
            }
          );
        }
      });
  }

  public copyFileUrl() {
    this.roomService.getMyRoomPermissions(this.resourceId).then((perm) => {
      if (perm.allowAnonymous) {
        this.roomService.getKeyring(this.resourceId).then((r) => {
          this.clipboardService.copy(
              environment.site_base +
                "/room/" +
                this.resourceId +
                "/drive#" +
                this.routerHandler.fragmentToRaw({
                  path: this.path,
                  file: this.lastInteracted.fullName,
                  roomkey: base64.stringify(
                    r["_keys"][AbstractRoomKeyring.PINNED]
                  ),
                })
            )
            .then(() => {
              this.dialogService.showSnackbar(
                marker("File URL copied!") +
                  " (" +
                  this.lastInteracted.fullName +
                  ")"
              );
            });
        });
      } else {
        this.clipboardService.copy(
            environment.site_base +
              "/room/" +
              this.resourceId +
              "/drive#" +
              this.routerHandler.fragmentToRaw({
                path: this.path,
                file: this.lastInteracted.fullName,
              })
          )
          .then(() => {
            this.dialogService.showSnackbar(
              marker("File URL copied!") +
                " (" +
                this.lastInteracted.fullName +
                ")"
            );
          });
      }
    });
  }

  public openDeleteDialog() {
    let forDelete =
      this.selectedFiles.length > 0
        ? this.selectedFiles
        : [this.lastInteracted];

    const dialogRef = this.dialog.open(DeleteFileDialogComponent, {
      data: forDelete,
    });

    dialogRef.afterClosed().subscribe((acceptedForDelete) => {
      if (acceptedForDelete && acceptedForDelete.length > 0) {
        this.deleteFiles(acceptedForDelete);
      }
    });
  }

  public deleteFiles(passedFiles: DriveFile[]) {
    let done = 0;

    passedFiles.forEach((f) => {
      let path = f.fullName;
      if (this.folderStack.length > 0) {
        path = this.getCurrentFolderPath() + "/" + path;
      }

      this.nanoActionQueueService.delete(
        this.resourceId,
        path,
        () => {
          done++;
          if (done == passedFiles.length) {
            this.listDirectory();
          }
        },
        (err) => {
          done++;
          if (done == passedFiles.length) {
            this.listDirectory();
          }
          console.error("error delete", err);
        }
      );
    });
  }

  private calcElementInARow() {
    let container = this.el.nativeElement.querySelector("#file-tile-table");

    if (container) {
      let el = container.querySelector("app-file-block-view:first-of-type");
      if (el) {
        return Math.floor(
          container.getBoundingClientRect().width /
            el.getBoundingClientRect().width
        );
      }
    } else {
      return 1;
    }
  }

  private getCurrentFolderPath() {
    let path = "";
    this.folderStack.forEach((dir) => {
      if (path.length > 0) {
        path += "/";
      }
      path += dir;
    });
    return path;
  }

  public switchViewMode(mode: ViewMode) {
    this.viewMode = mode;
    localStorage.setItem(this.driveViewCacheKey, mode.toString());

    setTimeout(() => {
      // call this async or register the viewMode on the ngOnChanges
      this.elementInARow = this.calcElementInARow();
    }, 10);
  }

  public listDirectory(cb?: Function) {
    this.isContentLoading = true;
    this.nanoActionQueueService.listDir(
      this.resourceId,
      this.getCurrentFolderPath(),
      (data) => {
        this.initLoadDone = true;
        this.loadError = false;
        this.pathError = false;
        this.pinnedKeyError = false;

        this.files = [];

        let navigatedToFileName;
        let fragment = this.routerHandler.getRoute().fragment;
        if (fragment && fragment["file"]) {
          navigatedToFileName = fragment["file"];
        }

        data
          .filter((d) => (d.unresolved ? !d.unresolved : true))
          .forEach((file) => {
            let driveFile = new DriveFile(file);
            this.files.push(driveFile);
            if (
              navigatedToFileName &&
              driveFile.fullName == navigatedToFileName
            ) {
              this.selectedFiles = [driveFile];
              this.lastInteracted = driveFile;
              setTimeout(() => {
                if (document.getElementById("f-" + driveFile.fullName)) {
                  document
                    .getElementById("f-" + driveFile.fullName)
                    .scrollIntoView();
                }
              }, 1);
            }
          });

        this.reorder();
        this.isContentLoading = false;
        this.showBackArrow = this.folderStack.length > 0;
        if (cb) cb();
      },
      (err) => {
        this.pathError = false;
        this.pinnedKeyError = false;
        this.loadError = true;
        this.isContentLoading = false;

        if (err.error == ServerError.NANO_REQUEST_ERROR_HANDLER_BAD_REQUEST) {
          this.pathError = true;
        } else if (err.message == "Nano sessions are competing for target!") {
          //TODO get custom error ID
          this.getCompetingNanos();
        }
        else if (err.message == "MissingKey Pinned key is missing"){
          this.pinnedKeyError = true;
        }

        console.error("can not load drive", err);
      },
      (curr, max) => {
        //console.log('progress '+curr+'/'+max)
      }
    );
  }

  public downloadFile(file: DriveFile) {
    var fileList = this.downloadManagerService.getFileList();

    if (
      fileList
        .filter(
          (file) => !file.done && file.direction == FileManagerDirection.DOWN
        )
        .some(
          (inProgressFile) =>
            inProgressFile.file == file &&
            inProgressFile.resourceId == this.resourceId
        )
    ) {
      this.dialogService.showSnackbar(marker("Download already started!"));
      return;
    }

    let path = file.fullName;
    if (this.folderStack.length > 0) {
      path = this.getCurrentFolderPath() + "/" + file.fullName;
    }

    this.downloadManagerService
      .download(this.resourceId, path, file)
      .then(() => {})
      .catch(() => {
        this.dialogService.openAlertDialog(
          marker("Error"),
          marker("Error happened during the file selection")
        );
      });
  }

  public openSelectFileDialog(event) {
    let files = event?.target?.files;
    if (files) {
      for (var i = 0; i < files.length; i++) {
        this.callUpload(files[i]);
      }
    }

    /*selectLocalFile(true).then((fileList) => {
      for (var i = 0; i < fileList.length; i++) {
        this.callUpload(fileList[i]);
      }
    });*/
  }

  public callUpload(file: File) {
    var fileList = this.downloadManagerService.getFileList();

    let path = "";
    if (this.folderStack.length > 0) {
      path = this.getCurrentFolderPath() + "/";
    }

    if (
      fileList
        .filter(
          (file) => !file.done && file.direction == FileManagerDirection.UP
        )
        .some(
          (inProgressFile) =>
            inProgressFile.path == path + file.name &&
            inProgressFile.resourceId == this.resourceId
        )
    ) {
      this.dialogService.showSnackbar("Upload already started!");
      return;
    }

    path = file.name;
    if (this.folderStack.length > 0) {
      path = this.getCurrentFolderPath() + "/" + path;
    }

    this.downloadManagerService.upload(
      this.resourceId,
      path,
      file,
      () => {
        this.refresh();
      },
      (err) => {
        console.log("upload error", err);
      }
    );
  }

  public singleClick(event, f: DriveFile) {
    if (this.isContentLoading) return;

    if (event.ctrlKey) {
      let index = this.selectedFiles.indexOf(f);
      if (index > -1) {
        this.selectedFiles.splice(index, 1);
      } else {
        this.selectedFiles.push(f);
      }
    } else if (event.shiftKey && this.lastInteracted) {
      let index = this.files.indexOf(f);
      let lastIndex = this.files.indexOf(this.lastInteracted);
      let direction = Math.sign(index - lastIndex);

      for (let i = lastIndex; i != index; i = i + direction) {
        if (this.selectedFiles.indexOf(this.files[i]) == -1) {
          this.selectedFiles.push(this.files[i]);
        }
      }

      if (this.selectedFiles.indexOf(f) == -1) {
        this.selectedFiles.push(f);
      }
    } else {
      this.selectedFiles = [];
    }

    this.lastInteracted = f;
  }

  public doubleClick(file: DriveFile) {
    if (this.isContentLoading) return;

    this.selectedFiles = [];

    if (file.ext == "dir") {
      this.folderStack.push(file.name);
      this.navigateEvent.emit(this.folderStack.join("/"));
    } else {
      this.downloadFile(file);
    }
  }

  /**
   * You have a structured folder stack 0 - root, 1 - first folder level, 2 - go on...
   * If you jump to the 0, you will relocate to the root folder
   * If you jump to the 1, you will relocate to the first subfolder of the root, "root/folder"
   *
   * This is for the navigation bar
   * @param level
   */
  public jumpInFolderStack(level) {
    this.navigateEvent.emit(this.folderStack.slice(0, level).join("/"));
  }

  public backInFolderStack() {
    if (!this.isContentLoading) {
      this.jumpInFolderStack(this.folderStack.length - 1);
    }
  }

  public refresh() {
    this.listDirectory();
  }

  public reorder() {
    this.reorderBy(
      this.currOrderBy,
      this.currOrderDirection,
      ["name", "ext"].includes(this.currOrderBy)
    );
  }

  public setReorder(attr) {
    if (this.currOrderBy == attr) {
      // if click on the same attr, just change the direction
      this.currOrderDirection *= -1;
    } else {
      this.currOrderBy = attr;
      this.currOrderDirection = 1;
    }
    this.reorder();
  }

  /**
   *
   * @param {string} attr order by an attribute
   * @param {number} direction 1 - asc, -1 - desc
   * @param {boolean} convertToLowerCase in case of string, you should do this (true/false)
   */
  public reorderBy(attr, direction = 1, convertToLowerCase = false) {
    this.files.sort((a, b) => {
      if (a.ext == "dir" && b.ext != "dir") return -1;
      if (a.ext != "dir" && b.ext == "dir") return 1;
      if (a.ext == "dir" && b.ext == "dir" && attr == "size") {
        return a.name.toLocaleLowerCase() < b.name.toLocaleLowerCase() ? -1 : 1;
      }

      let aVal;
      let bVal;

      if (convertToLowerCase) {
        aVal = a[attr].toLocaleLowerCase();
        bVal = b[attr].toLocaleLowerCase();
      } else {
        aVal = a[attr];
        bVal = b[attr];
      }

      if (aVal < bVal) return -1 * direction;
      else if (aVal > bVal) return 1 * direction;
      else return 0;
    });
  }

  public moveFile(srcPath, newPath) {
    this.nanoService.moveFile(this.resourceId, srcPath, newPath, () => {
      this.refresh();
    });
  }

  public onDragEnter(event) {
    if (event?.dataTransfer?.items) {
      let items = event.dataTransfer.items;
      for (let i = 0; i < items.length; i++) {
        if (items[i].kind == "file") {
          this.showDropArea = true;
          return;
        }
      }
    }
  }

  public onDragLeave(event) {
    this.showDropArea = false;
  }

  public onDragDrop(event) {
    event.preventDefault();
    event.stopPropagation();
    this.askBeforeUpload(getFileFromClipboard(event.dataTransfer));

    this.showDropArea = false;
  }

  public onPaste(event: ClipboardEvent) {
    this.askBeforeUpload(getFileFromClipboard(event.clipboardData));
  }

  private askBeforeUpload(files: File[]) {
    if (files.length > 0) {
      this.dialogService
        .openConfirmDialog(
          marker("File upload"),
          marker("Are you sure you want to upload these files?")
        )
        .subscribe((response) => {
          if (response) {
            files.forEach((f) => {
              this.callUpload(f);
            });
          }
        });
    } else {
      this.dialogService.showSnackbar(
        marker("This operation is not supported by your current browser")
      );
    }
  }

  private async getCompetingNanos() {
    var clients = <Array<NanoClientRecord>>(
      await this.serverRestApi.query({ query: getNanoSlotsQuery })
    );
    var roomData = await this.roomService.getData(this.resourceId);

    var driveSessionIds: string[] = [];

    for (let sessionId in roomData.nanoSessions) {
      if (roomData.nanoSessions[sessionId] == 1)
        driveSessionIds.push(sessionId);
    }

    var competingClients = clients.filter((c) =>
      driveSessionIds.some((dsId) => dsId == c.sessionId)
    );

    var counter = 0;
    for (let cc of competingClients) {
      this.nanoService.adminGetInfo(
        cc.id,
        (res) => {
          console.log(res);
          counter++;
          cc.detail = res;

          if (counter == competingClients.length) {
            this.competingError = true;
            this.competingClientNames = competingClients
              .map((cc) => cc.detail.name)
              .join(", ");
          }
        },
        (err) => {
          console.error(err);
          counter++;

          if (counter == competingClients.length) {
            this.competingError = true;
          }
        }
      );
    }
  }
}
