import {
  CacheDataPath,
  CacheInterface,
  CacheLogic,
  QueryResponse,
  Request,
  RequestCacheStateType,
} from './cache-logic-interface';

export class Cache implements CacheInterface {
  public cacheData;

  constructor(private cacheLogic: CacheLogic) {
    this.resetCache();
  }

  public resetCache(): void {
    this.cacheData = this.cacheLogic.resetCache(this);
  }

  /**
   * Merge the request result into the cache storage
   */
  public mergeRequestResult(request: Request, response: QueryResponse): void {
    if (request.query.endpoint in this.cacheLogic.requestResponseMerger[request.query.operation]) {
      this.cacheLogic.requestResponseMerger[request.query.operation][request.query.endpoint](
        this,
        request,
        response
      );
    } else {
      this.cacheLogic.requestResponseMerger[request.query.operation].default(
        this,
        request,
        response
      );
    }
  }

  public getRequestCacheState(request: Request): RequestCacheStateType {
    let cacheKey = this.cacheLogic.makeCacheDataPath(request);
    let shouldFetch = this.shouldFetchFromServer(cacheKey, request);
    let data;
    if (!shouldFetch) {
      data = this.getStoredRequestResponse(cacheKey, request);
    }

    return {
      cacheKey,
      shouldFetch,
      data,
    };
  }

  /**
   * Check if we should fetch from the server or the data for the request is already stored in the cache
   */
  private shouldFetchFromServer(cacheKey: CacheDataPath, request: Request): boolean {
    if (request.query.endpoint in this.cacheLogic.conditionForFetch) {
      return this.cacheLogic.conditionForFetch[request.query.endpoint](this, cacheKey, request);
    } else {
      return this.cacheLogic.conditionForFetch.default(this, cacheKey, request);
    }
  }

  /**
   * Get the data from the cache storage.
   * This function manipulates the response by an inside manipulator logic. For the raw cache data
   * use the getRawData
   */
  private getStoredRequestResponse(cacheKey: CacheDataPath, request: Request) {
    if (
      this.cacheLogic.requestResponseManipulator?.[request.query.operation]?.[
        request.query.endpoint
      ]
    ) {
      return this.cacheLogic.requestResponseManipulator[request.query.operation][
        request.query.endpoint
      ](this, cacheKey, request);
    } else {
      return this.cacheLogic.requestResponseManipulator[request.query.operation].default(
        this,
        cacheKey,
        request
      );
    }
  }

  public getCacheData(cacheKey: CacheDataPath): any {
    if (typeof cacheKey == 'string') {
      return this.cacheData[cacheKey];
    } else {
      let obj = this.cacheData;
      cacheKey.forEach((entry) => {
        obj = obj[entry];
      });
      return obj;
    }
  }

  public getCacheDataByRequest(request: Request): any {
    return this.getCacheData(this.getCacheLogic().makeCacheDataPath(request));
  }

  public setCacheData(cacheKey: CacheDataPath, data: any) {
    if (typeof cacheKey == 'string') {
      this.cacheData[cacheKey] = data;
    } else {
      let obj = this.cacheData;
      for (let i = 0; i < cacheKey.length; i++) {
        if (i == cacheKey.length - 1) {
          obj[cacheKey[i]] = data;
        } else {
          if (!obj[cacheKey[i]]) {
            obj[cacheKey[i]] = {};
          }
          obj = obj[cacheKey[i]];
        }
      }
    }
  }

  public setCacheDataByRequest(request: Request, data: any) {
    this.setCacheData(this.getCacheLogic().makeCacheDataPath(request), data);
  }

  public deleteCacheData(cacheKey: CacheDataPath) {
    if (typeof cacheKey == 'string') {
      delete this.cacheData[cacheKey];
    } else {
      let obj = this.cacheData;
      for (let i = 0; i < cacheKey.length; i++) {
        if (i == cacheKey.length - 1) {
          delete obj[cacheKey[i]];
        } else if (obj[cacheKey[i]]) {
          obj = obj[cacheKey[i]];
        } else {
          return;
        }
      }
    }
  }

  deleteCacheDataByRequest(request: Request) {
    this.deleteCacheData(this.getCacheLogic().makeCacheDataPath(request));
  }

  public getCacheLogic(): CacheLogic {
    return this.cacheLogic;
  }
}
