import { BehaviorSubject, concat, merge, Observable, scheduled, Subscription, timer } from "rxjs";
import { concatMap, delay, filter, map, mergeMap, take, tap, throttleTime } from "rxjs/operators";

export class CachingService {
  public data: BehaviorSubject<any>;
  private dataIndex: {};
  private observable$: Observable<any>;
  private persistence: any;
  private model: any;

  constructor(model = null) {
    this.data = new BehaviorSubject<any>(null);
    this.dataIndex = {};
    this.observable$ = this.data;

    this.model = model;
    if (this.model) {
      try {
        this.dataIndex = JSON.parse(localStorage.getItem(`persistence-${this.model}`));
        this.fire(false);
      } catch (e) {
        this.dataIndex = {};
      }
    }
  }

  private persist(index) {
    localStorage.setItem(`persistence-${this.model}`, JSON.stringify(this.dataIndex));
  }

  private fire(flag = true) {
    const values = Object.values(this.dataIndex);
    this.model && flag && this.persist(this.dataIndex);
    this.data.next(values);
    return values;
  }

  private index(array): void {
    for (let i = 0; i < array.length; i++) {
      this.dataIndex[array[i].id] = {
        ...(this.dataIndex[array[i].id] || {}),
        ...array[i]
      };
    }
  }
  public updateAll(newdata): void {
    this.index(newdata);
    this.fire();
  }

  public updateId(id, data): void {
    this.dataIndex[id] = {
      ...(this.dataIndex[id] || {}),
      ...data
    };
    this.fire();
  }

  public request(observable$): Subscription {
    const request$ = timer(1).pipe(concatMap(() => observable$));
    return request$.subscribe((entity) => {
      this.updateEntity(entity);
    });
  }

  /**
   * returns the cache observable
   */
  public getStream(id = null): Observable<any> {
    return this.observable$.pipe(
      map((values) =>
        values ? values.filter((item) => (id !== null ? item.id == id : true)) : null
      )
    );
  }

  private updateEntity(entity): void {
    if (!entity) return;
    if (Array.isArray(entity)) {
      return this.updateAll(entity);
    } else {
      return entity.id && this.updateId(entity.id, entity);
    }
  }
}
