import EventEmitter, { IEmitter } from '../Emitter';
import { IApiService } from '../Api';
import Promise from 'bluebird';
import Helpers, { IHelpers } from '../Helpers';

const helpers: IHelpers = new Helpers();

// Main/public page service
let api: IApiService;

export class RangesWrap {
  constructor(ippApi: IApiService) {
    api = ippApi;
  }
}

export interface IPageRangeRights {
  ro: number[];
  no: number[];
  [key: string]: any;
}

export interface IPageRange {
  name: string;
  start: string;
  end: string;
  rights: IPageRangeRights;
  freeze: boolean;
  button: boolean;
  style: boolean;
  notification: boolean;
  access: boolean;
  list: boolean;
}

export interface IPageRangeItem {
  name: string;
  toObject: () => IPageRange;
}

export interface IPagePermissionRange extends IPageRangeItem {
  rowStart: number;
  rowEnd: number;
  colStart: number;
  colEnd: number;
  getCellsReference(): string;
  setCellsReference(ref: string, rows: number, cols: number): void;
  setPermission: (userId: number, permission?: string) => void;
  getPermission: (userId: number) => string;
  togglePermission(userId: number, currentPermission: string | null): string;
}

export class PermissionRange implements IPagePermissionRange {
  // @todo Constants for permissions ro, no etc
  private _permissions: IPageRangeRights = {
    ro: [],
    no: []
  };

  constructor(
    public name: string,
    public rowStart: number = 0,
    public rowEnd: number = 0,
    public colStart: number = 0,
    public colEnd: number = 0,
    permissions?: IPageRangeRights
  ) {
    if (permissions) {
      this._permissions = permissions;
    }
  }

  public getCellsReference(): string {
    return helpers.getCellsReference(
      { row: this.rowStart, col: this.colStart },
      { row: this.rowEnd, col: this.colEnd }
    );
  }

  public setCellsReference(ref: string, rows: number, cols: number): void {
    let cellRange = helpers.cellRange(ref, rows, cols);
    this.rowStart = cellRange.from.row;
    this.colStart = cellRange.from.col;
    this.rowEnd = cellRange.to.row;
    this.colEnd = cellRange.to.col;
  }

  /**
   * Set permission for a user
   * @param userId
   * @param permission
   */
  public setPermission(userId: number, permission?: string | null): void {
    if (permission) {
      permission = permission.toLowerCase();
    }
    if (['ro', 'no'].indexOf(`${permission}`) < 0) {
      permission = null;
    }
    // First remove user rom all ranges
    if (this._permissions.ro.indexOf(userId) >= 0) {
      this._permissions.ro.splice(this._permissions.ro.indexOf(userId), 1);
    }

    if (this._permissions.no.indexOf(userId) >= 0) {
      this._permissions.no.splice(this._permissions.no.indexOf(userId), 1);
    }

    if (permission) {
      this._permissions[permission].push(userId);
    }
  }

  public togglePermission(userId: number, currentPermission: string | null): string {
    if (currentPermission === 'ro') {
      currentPermission = 'no';
    } else if (currentPermission === 'no') {
      currentPermission = null;
    } else {
      currentPermission = 'ro';
    }
    this.setPermission(userId, currentPermission);
    return this.getPermission(userId);
  }

  /**
   * Get permission for a user
   * @param userId
   * @returns {string}
   */
  public getPermission(userId: number): string {
    let permission: string = '';

    if (this._permissions.ro.indexOf(userId) >= 0) {
      permission = 'ro';
    } else if (this._permissions.no.indexOf(userId) >= 0) {
      permission = 'no';
    }

    return permission || 'rw';
  }

  /**
   * Serialize range to final service-accepted object
   * @returns {{name: string, start: string, end: string, rights: IPageRangeRights, freeze: boolean}}
   */
  public toObject(): IPageRange {
    return {
      name: this.name,
      start: `${this.rowStart}:${this.colStart}`,
      end: `${this.rowEnd}:${this.colEnd}`,
      rights: this._permissions,
      freeze: false,
      button: false,
      style: false,
      access: false,
      notification: false,
      list: false
    };
  }
}

export class PermissionRangeWrap {
  constructor() {
    return PermissionRange;
  }
}

export interface IPageRangesCollection extends IEmitter {
  TYPE_PERMISSION_RANGE: string;
  EVENT_UPDATED: string;
  ranges: (IPageRangeItem)[];
  setRanges: (ranges: IPageRangeItem[]) => this;
  addRange: (range: IPageRangeItem) => this;
  removeRange: (rangeName: string) => this;
  save: () => Promise<any>;
  parse: (pageAccessRights: string) => IPageRangeItem[];
}

export class Ranges extends EventEmitter implements IPageRangesCollection {
  /**
   * List of ranges in collection
   * @type {Array}
   * @private
   */
  private _ranges: (IPageRangeItem)[] = [];

  private _folderId: number;
  private _pageId: number;

  public get TYPE_PERMISSION_RANGE(): string {
    return 'permissions';
  }
  public get TYPE_FREEZING_RANGE(): string {
    return 'freezing';
  }

  public get EVENT_UPDATED(): string {
    return 'updated';
  }

  /**
   * Getter for list of ranges
   * @returns {IPageRangeItem[]}
   */
  public get ranges(): (IPageRangeItem)[] {
    return this._ranges;
  }

  constructor(folderId: number, pageId: number, pageAccessRights?: string) {
    super();

    this._folderId = folderId;
    this._pageId = pageId;

    if (pageAccessRights) {
      this.parse(pageAccessRights);
    }
  }

  /**
   * Rewrites current collection of ranges with given ranges
   * @param ranges
   * @returns {ipushpull.Ranges}
   */
  public setRanges(ranges: (IPageRangeItem)[]): this {
    this._ranges = ranges;

    return this;
  }

  /**
   * Adds range to collection. Also prevents duplicate names
   * @param range
   * @returns {ipushpull.Ranges}
   */
  public addRange(range: IPageRangeItem): this {
    // Prevent duplicates
    let nameUnique: boolean = false;
    let newName: string = range.name;
    let count: number = 1;

    while (!nameUnique) {
      nameUnique = true;

      for (let i: number = 0; i < this._ranges.length; i++) {
        if (this._ranges[i].name === newName) {
          nameUnique = false;
          newName = range.name + '_' + count;
          count++;
        }
      }
    }

    range.name = newName;

    this._ranges.push(range);

    return this;
  }

  /**
   * Removes range from collection
   *
   * @param rangeName
   * @returns {ipushpull.Ranges}
   */
  public removeRange(rangeName: string): this {
    let index: number = -1;

    for (let i: number = 0; i < this._ranges.length; i++) {
      if (this._ranges[i].name === rangeName) {
        index = i;
        break;
      }
    }

    if (index >= 0) {
      this._ranges.splice(index, 1);
    }

    return this;
  }

  /**
   * Save ranges to a page
   * @returns {Promise<IRequestResult>}
   */
  // @todo This is way out of scope
  public save(): Promise<any> {
    let p: Promise<any> = new Promise((resolve, reject) => {
      let ranges: IPageRange[] = [];

      // Convert all ranges to objects
      for (let i: number = 0; i < this._ranges.length; i++) {
        ranges.push(this._ranges[i].toObject());
      }

      let requestData: any = {
        domainId: this._folderId,
        pageId: this._pageId,
        data: {
          range_access: ranges.length ? JSON.stringify(ranges) : ''
        }
      };

      api.savePageSettings(requestData).then(data => {
        this.emit(this.EVENT_UPDATED);
        resolve(data);
      }, reject);
    });

    return p;
  }

  /**
   * Parse range data loaded from service into range collection
   * @param pageAccessRights
   * @returns {IPageRangeItem[]}
   */
  public parse(pageAccessRights: string): IPageRangeItem[] {
    let ar: IPageRange[] = JSON.parse(pageAccessRights);

    this._ranges = [];

    for (let i: number = 0; i < ar.length; i++) {
      let range: any = {
        from: {
          row: parseInt(ar[i].start.split(':')[0], 10),
          col: parseInt(ar[i].start.split(':')[1], 10)
        },
        to: {
          row: parseInt(ar[i].end.split(':')[0], 10),
          col: parseInt(ar[i].end.split(':')[1], 10)
        }
      };
      this._ranges.push(
        new PermissionRange(ar[i].name, range.from.row, range.to.row, range.from.col, range.to.col, ar[i].rights)
      );
    }

    return this._ranges;
  }
}
