import { IMySegmentsResponse } from '../dtos/types';
import { MySegmentsData } from '../sync/polling/types';
import { ISegmentsCacheSync } from './types';

/**
 * This class provides a skeletal implementation of the ISegmentsCacheSync interface
 * to minimize the effort required to implement this interface.
 */
export abstract class AbstractMySegmentsCacheSync implements ISegmentsCacheSync {

  protected abstract addSegment(name: string): boolean
  protected abstract removeSegment(name: string): boolean
  protected abstract setChangeNumber(changeNumber?: number): boolean | void

  /**
   * For server-side synchronizer: check if `key` is in `name` segment.
   * For client-side synchronizer: check if `name` segment is in the cache. `key` is undefined.
   */
  abstract isInSegment(name: string, key?: string): boolean

  /**
   * clear the cache.
   */
  clear() {
    this.resetSegments({});
  }


  // No-op. Not used in client-side.
  registerSegments(): boolean { return false; }
  update() { return false; }

  /**
   * For server-side synchronizer: get the list of segments to fetch changes.
   * Also used for the `seC` (segment count) telemetry stat.
   */
  abstract getRegisteredSegments(): string[]

  /**
   * Only used for the `skC`(segment keys count) telemetry stat: 1 for client-side, and total count of keys in server-side.
   */
  // @TODO for client-side it should be the number of clients, but it requires a refactor of MySegments caches to simplify the code.
  abstract getKeysCount(): number

  abstract getChangeNumber(): number

  /**
   * For server-side synchronizer: the method is not used.
   * For client-side synchronizer: it resets or updates the cache.
   */
  resetSegments(segmentsData: MySegmentsData | IMySegmentsResponse): boolean {
    let isDiff = false;
    const { added, removed } = segmentsData as MySegmentsData;

    if (added && removed) {

      added.forEach(segment => {
        isDiff = this.addSegment(segment) || isDiff;
      });

      removed.forEach(segment => {
        isDiff = this.removeSegment(segment) || isDiff;
      });
    } else {

      const names = ((segmentsData as IMySegmentsResponse).k || []).map(s => s.n).sort();
      const storedSegmentKeys = this.getRegisteredSegments().sort();

      // Extreme fast => everything is empty
      if (!names.length && !storedSegmentKeys.length) {
        isDiff = false;
      } else {

        let index = 0;

        while (index < names.length && index < storedSegmentKeys.length && names[index] === storedSegmentKeys[index]) index++;

        // Quick path => no changes
        if (index === names.length && index === storedSegmentKeys.length) {
          isDiff = false;
        } else {

          // Slowest path => add and/or remove segments
          for (let removeIndex = index; removeIndex < storedSegmentKeys.length; removeIndex++) {
            this.removeSegment(storedSegmentKeys[removeIndex]);
          }

          for (let addIndex = index; addIndex < names.length; addIndex++) {
            this.addSegment(names[addIndex]);
          }

          isDiff = true;
        }
      }
    }

    this.setChangeNumber(segmentsData.cn);
    return isDiff;
  }
}
