import { ID, OrderableItem } from '../../shared/server-services/query-records/common-records';

export class OrderedList<T extends OrderableItem> {
  private orderedContainer: T[] = [];

  constructor(container?: T[]) {
    if (container) {
      this.orderedContainer = [].concat(container);
      this.reorder();
    }
  }

  /*
    public getData(): T[]{
        return this.container;
    }*/

  public getInorderedDataRef(): T[] {
    return this.orderedContainer;
  }

  public reorder() {
    this.orderedContainer = this.orderedContainer.sort((a: T, b: T) => {
      return a.ordering - b.ordering;
    });
  }

  public forEach(cb: (element: T) => void) {
    this.orderedContainer.forEach((el) => {
      cb(el);
    });
  }

  /**
   * Push the element into the container using its ordering attribute
   * Use for state update by server response
   * @param element
   * @returns
   */
  public pushByOrdering(element: T) {
    if (element.ordering == -1) {
      this.orderedContainer.unshift(element);
      return true;
    }
    for (let i = 0; i < this.orderedContainer.length; i++) {
      if (element.ordering < this.orderedContainer[i].ordering) {
        // we should insert it before the next element
        if (i == 0) {
          this.orderedContainer.unshift(element);
        } else {
          this.orderedContainer.splice(i, 0, element);
        }
        return true;
      }
    }

    // can not fit anywhere, so put it at the end
    this.orderedContainer.push(element);
    return true;
  }

  public remove(id: ID) {
    let index = this.findIndex(id);
    if (index > -1) {
      this.orderedContainer.splice(index, 1);
    }
  }

  public includes(id: ID): boolean {
    return this.findIndex(id) > -1;
  }

  private findIndex(id: ID) {
    return this.orderedContainer.findIndex((el) => {
      return id == el.id;
    });
  }

  /**
   * Calculate the ordering number by the position where do you want to insert the element
   * @param element Element you want to push into the container
   * @param pos Where do you want to put it in the list (0 means start, arraylength-1 means last)
   * @param ignoreId use the moveable object's id here, the algorithm should ignore that during the calculation
   * @returns ordering number
   */
  public calcOrderingByPosition(element: T, pos: number): number {
    if (this.orderedContainer.length == 0) {
      return 1; // default when you have only 1 group
    }

    if (pos >= this.orderedContainer.length) {
      // push after the last element roundup(last)+1
      return Math.ceil(this.orderedContainer[this.orderedContainer.length - 1].ordering + 1);
    }

    // decide where to put (calculate the ordering)
    if (this.orderedContainer[pos].id == element.id) {
      return this.orderedContainer[pos].ordering; // its already on the right position
    }

    if (pos == 0) {
      // push between 0 and the first element ordering
      return this.orderedContainer[0].ordering / 2;
    }

    // push somewhere in the middle (halfway between to elements)

    // check if the element is exist before the position
    for (let i = 0; i < pos; i++) {
      if (this.orderedContainer[i].id == element.id) {
        pos++; // we have to shift the whole process, or we will move 1 level lower
        break;
      }
    }

    // check the new position too
    if (pos >= this.orderedContainer.length) {
      return Math.ceil(this.orderedContainer[this.orderedContainer.length - 1].ordering + 1);
    }

    let posBefore = this.orderedContainer[pos - 1].ordering;
    let posAfter = this.orderedContainer[pos].ordering;

    return (posAfter - posBefore) / 2 + posBefore;
  }

  /**
   * When we reorder an element, we set the ordering property halfway between its new neighbours.
   * It is fast and simply, but after a lot of reorder it can generate bad floats. This function
   * checks if the actual generated position is a too long float number.
   * @param pos float
   * @returns boolean - is it needed to optimize this ordering number or not
   */
  public static isPositionNeedToOptimize(pos: number): boolean {
    return pos <= 0 || pos.toString().length > 10;
  }

  /**
   * when the isPositionNeedToOptimize return with a too long float position, we should regenarate
   * their ordering property with a nice and rounded numbers.
   */
  public getOptimizedOrdering(): { ordering: number; element: T }[] {
    let result: { ordering: number; element: T }[] = [];

    let last = Math.ceil(this.orderedContainer[this.orderedContainer.length - 1].ordering);

    // make round number (start from 100 so we have room to ordering)
    for (let i = this.orderedContainer.length - 1; i >= 0; i--) {
      result.push({ ordering: last + i + 100, element: this.orderedContainer[i] });
    }

    return result;
  }
}
