import { Component, Inject, OnDestroy } from '@angular/core';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { TranslateService } from '@ngx-translate/core';
import { DriveFile } from 'src/app/components/resource-page/drive-window/drive-layout/drive-file';
import { NanoService } from '../../nano/nano.service';
import { NanoSlotRecord } from '../../server-services/query-records/nano-records';
import { getNanoSlotsQuery } from '../../server-services/querys';
import { RoomService } from '../../server-services/room.service';
import { ServerError } from '../../server-services/server-errors';
import { NanoActionQueueService } from '../../services/nano-action-queue.service';
import { ServerRestApiService } from '../../services/server-rest-api.service';
import { NanoDialogBaseComponent } from '../nano-base-dialog';
import {
  FileRow,
  FolderDownloadData,
  QueuedCallback,
  QueuedCallbackStatus,
} from './folder-download-records';

@Component({
  selector: 'app-folder-download-confirm-dialog',
  templateUrl: './folder-download-confirm-dialog.component.html',
  styleUrls: ['./folder-download-confirm-dialog.component.scss'],
})
// implements OnInit
export class FolderDownloadConfirmDialogComponent
  extends NanoDialogBaseComponent<FolderDownloadConfirmDialogComponent>
  implements OnDestroy
{
  public resourceId: string;
  public path: string;
  public fileNames: string[];
  // output lol

  public fileCount: number = 0;
  public folderCount: number = 0;

  public startingDepth: number = 0;

  public isContentLoading = true;

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

  public competingClientNames: string;

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

  /**
   * Files at root level in the tree
   */
  public rootFiles: FileRow[] = [];

  public sumSize: number = 0;

  public currentProgress = 0;
  public maxProgress = 1;

  private waitForInprogressCallbacksInterval = null;
  public refreshClicked = false;
  private activeProcessingCallbackNumber = 8;
  private isCallbackQueueProcessing = false;
  private callbackQueueInterval = null;
  private callbackQueue: QueuedCallback[] = [];

  public counter = 0;

  constructor(
    public dialogRef: MatDialogRef<FolderDownloadConfirmDialogComponent>,
    public translate: TranslateService,

    @Inject(MAT_DIALOG_DATA) public folderDownloadData: FolderDownloadData,

    private nanoService: NanoService,
    private serverRestApi: ServerRestApiService,
    private roomService: RoomService,
    private nanoActionQueueService: NanoActionQueueService
  ) {
    super(dialogRef);

    this.resourceId = folderDownloadData.resourceId;
    this.fileNames = folderDownloadData.fileNames;
    this.path = folderDownloadData.path;

    if (this.fileNames.length !== 1) {
      this.startingDepth = 1;
    }
    this.listDirectory(
      this.path,
      () => {
        if (!this.rootFiles.find((f) => f.type === 'folder')) {
          this.isContentLoading = false;
        }
      },
      null,
      this.fileNames
    );

    // processes queued listDirectory promises
    this.processCallbackQueue();
  }

  public changeFileSelection = (selection) => {
    for (let file of this.rootFiles) {
      file.changeFileSelection(selection, file, false);
    }
  };

  ngOnDestroy(): void {
    clearInterval(this.callbackQueueInterval);
    clearInterval(this.waitForInprogressCallbacksInterval);
  }

  private processCallbackQueue = () => {
    this.callbackQueueInterval = setInterval(() => {
      if (!this.isCallbackQueueProcessing) {
        this.isCallbackQueueProcessing = true;

        let runningProcesses = 0;
        for (const oneCallback of this.callbackQueue) {
          if (runningProcesses < this.activeProcessingCallbackNumber) {
            if (oneCallback.status === QueuedCallbackStatus.queued) {
              runningProcesses += 1;

              oneCallback.status = QueuedCallbackStatus.inprogress;

              const now = new Date();
              oneCallback.callback(this.setQueuedCallbackToDone(oneCallback, now));
            }

            if (oneCallback.status === QueuedCallbackStatus.inprogress) {
              runningProcesses += 1;
            }

            if (oneCallback.status === QueuedCallbackStatus.done) {
              this.callbackQueue.splice(this.callbackQueue.indexOf(oneCallback), 1);
            }

            if (oneCallback.status === QueuedCallbackStatus.error) {
              // error handling @todo - retrying or something?
              console.log('ERROR');
            }
          }
        }

        this.isCallbackQueueProcessing = false;
      }
    }, 100);
  };

  private setQueuedCallbackToDone = (qcb, timeStarted) => {
    return () => {
      qcb.status = QueuedCallbackStatus.done;
      // delete this.callbackQueue[qcb];
    };
  };

  // ngOnInit() {}

  public incerementSumSize = (value) => {
    this.sumSize += value;
  };
  public incerementFileCount = (value) => {
    this.fileCount += value;
  };
  public incerementFolderCount = (value) => {
    this.folderCount += value;
  };

  /**
   *
   * @param basePath
   * @param cb
   * @param parentDir FileRow of parent if has
   * @param filteredTo files filtered on given basePath directory
   */
  public listDirectory(
    basePath: string,
    cb?: Function,
    parentDir?: FileRow,
    filteredTo?: String[]
  ) {
    this.isContentLoading = true;
    if (parentDir) {
      parentDir.status = 'loading';
    }
    console.log('try to listdir', basePath, parentDir);
    this.nanoActionQueueService.listDir(
      this.resourceId,
      basePath,
      (data) => {
        console.log('list dir on base: ', basePath, parentDir);
        this.loadError = false;
        this.pathError = false;
        this.pinnedKeyError = false;

        let navigatedToFileName = basePath;

        if (!data.length) {
          if (parentDir) {
            parentDir.checkDone();
          }
          if (
            !parentDir &&
            !this.rootFiles.find(
              (f) => f.type === 'folder' && (f.status === 'loading' || f.status === 'not-started')
            )
          ) {
            console.log('forcing loading stop');
            this.isContentLoading = false;
          }
        }

        data
          .filter((d) => (d.unresolved ? !d.unresolved : true))
          .forEach((file) => {
            if (filteredTo?.length) {
              if (filteredTo.includes(file.name)) {
                console.log('ESCAPE', file, filteredTo);
              } else {
                return null;
              }
            }

            let driveFile = new DriveFile(file);
            let builtBasePath = basePath ? basePath + '/' : '';

            let initData: FileRow;
            if (file.hasOwnProperty('size')) {
              this.incerementSumSize(file.size);
              this.incerementFileCount(1);

              initData = {
                file: driveFile,
                name: driveFile.fullName,
                size: driveFile.size,
                path: builtBasePath + driveFile.fullName,
                selected: parentDir?.selected === 'empty' ? 'empty' : 'selected',
                status: 'done',
                type: 'file',
                parent: parentDir ?? null,
              };
            } else {
              this.incerementFolderCount(1);
              initData = {
                file: driveFile,
                name: file.name,
                size: 0,
                path: builtBasePath + file.name,
                selected: parentDir?.selected === 'empty' ? 'empty' : 'selected',
                status: 'not-started',
                type: 'folder',
                children: [],
                parent: parentDir ?? null,
                incrementSize: (amount: number) => {
                  initData.size += amount;
                  if (parentDir) {
                    parentDir.incrementSize(amount);
                  }
                },
                checkDone: () => {
                  if (initData.status === 'error' || initData.status === 'error-while-loading') {
                    if (parentDir) {
                      parentDir.status = 'error-while-loading';
                      parentDir.checkDone();
                    }
                  } else if (initData.children.every((child) => child.status === 'done')) {
                    initData.status = 'done';
                    if (parentDir) {
                      parentDir.checkDone();
                    } else {
                      // has no parent folder -> this is a root file
                      // check whether all other rootfiles are done, if they are then loading is complete
                      if (this.rootFiles.every((file) => file.status === 'done')) {
                        this.isContentLoading = false;
                      }
                      if (
                        this.rootFiles.find(
                          (file) => file.status === 'error' || file.status === 'error-while-loading'
                        )
                      ) {
                        this.isContentLoading = false;
                        this.loadError = true;
                      }
                    }
                  }
                },
              };
              if (parentDir) {
                parentDir.children.push(initData);
              }
            }
            // is a folder
            if (!parentDir) {
              this.rootFiles.push(initData);
            }
            if (initData.type === 'folder') {
              // 'path/filename' or just simply 'filename' without the '/'
              const subFolderPath = basePath ? basePath + '/' : '';
              this.callbackQueue.push({
                callback: (doneCb) => {
                  this.listDirectory(
                    subFolderPath + file.name,
                    () => {
                      if (
                        initData.status === 'error' ||
                        initData.status === 'error-while-loading'
                      ) {
                        if (parentDir) {
                          parentDir.status = 'error-while-loading';
                          parentDir.checkDone();
                        }
                      } else if (initData.children.every((child) => child.status === 'done')) {
                        initData.status = 'done';
                        if (parentDir) {
                          parentDir.checkDone();
                        }
                      }
                      doneCb();
                    },
                    initData
                  );
                },
                status: QueuedCallbackStatus.queued,
              });
            } else {
              if (parentDir) {
                parentDir.children.push(initData);
                parentDir.incrementSize(initData.size);
                parentDir.checkDone();
              }
            }
          });

        if (cb) cb();
      },
      (err) => {
        console.error('err', basePath, parentDir, err);
        if (parentDir) {
          for (const child of parentDir.children) {
            child.status = 'error-while-loading';
          }
          parentDir.status = 'error-while-loading';
          parentDir.checkDone();
        } else {
          // this.rootFiles.find((file) => file.path === basePath).status = 'error-while-loading';
          for (const file of this.rootFiles) {
            if (file.status === 'loading') {
              file.status = 'error-while-loading';
            }
          }
        }
        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);
      }
    );
  }

  public refresh() {
    if (!this.refreshClicked) {
      this.refreshClicked = true;
      clearInterval(this.callbackQueueInterval);
      // waiting for inprogress callbacks
      this.waitForInprogressCallbacks();
    }
  }

  public waitForInprogressCallbacks = () => {
    this.waitForInprogressCallbacksInterval = setInterval(() => {
      console.log('waitforinprogresscallback tick', new Date(), this.callbackQueue);
      if (
        !this.loadError &&
        this.callbackQueue.find(
          (queuedCallback) => queuedCallback.status === QueuedCallbackStatus.inprogress
        )
      ) {
        // still in progress
      } else {
        this.restartLoading();
      }
    }, 250);
  };

  public restartLoading = () => {
    console.log('restarting then');
    clearInterval(this.waitForInprogressCallbacksInterval);
    this.rootFiles = [];
    this.callbackQueue = [];

    this.fileCount = 0;
    this.folderCount = 0;
    this.sumSize = 0;

    this.refreshClicked = false;
    console.log(this.rootFiles);
    // processes queued listDirectory promises
    console.log('start processcallback again ');
    this.processCallbackQueue();
    console.log('start listDirectory again ');
    this.listDirectory(this.path, () => {}, null, this.fileNames);
  };

  getExcludedFiles = (files: FileRow[]) => {
    const excludedFiles = [];
    for (const file of files) {
      if (file.selected === 'selected') {
        // skip this level
      } else if (file.selected === 'indeterminate') {
        excludedFiles.push(this.getExcludedFiles(file.children));
      } else if (file.selected === 'empty') {
        excludedFiles.push(file.path);
      } // else {}
    }

    // array flattening
    const flattened = excludedFiles.reduce((acc, val) => acc.concat(val), []);

    console.log('flattened', flattened);

    return flattened;
  };

  closeWithOk() {
    const excludedFiles = this.getExcludedFiles(this.rootFiles);

    console.log('excludedFiles', excludedFiles);
    this.dialogRef.close({
      success: true,
      files: this.rootFiles,
      excludedFiles: this.getExcludedFiles(this.rootFiles),
      resourceId: this.resourceId,
    });
  }

  private async getCompetingNanos() {
    var clients = <Array<NanoSlotRecord>>(
      await this.serverRestApi.query({ query: getNanoSlotsQuery })
    );
    var roomData = await this.roomService.getRoom(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)
    );
    console.log('sessions: ', driveSessionIds, competingClients);

    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;
          }
        }
      );
    }
  }
}
