/*
 * Copyright (C) 2022 StApps
 *  This program is free software: you can redistribute it and/or modify it
 *  under the terms of the GNU General Public License as published by the Free
 *  Software Foundation, version 3.
 *
 *  This program is distributed in the hope that it will be useful, but WITHOUT
 *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 *  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 *  more details.
 *
 *  You should have received a copy of the GNU General Public License along with
 *  this program. If not, see <https://www.gnu.org/licenses/>.
 */
import {Injectable} from '@angular/core';
import {Storage} from '@ionic/storage-angular';

/**
 * Provides interaction with the (ionic) storage on the device (in the browser)
 */
@Injectable()
export class StorageProvider {
  /**
   * @param storage TODO
   */
  constructor(private readonly storage: Storage) {}

  /**
   * Deletes storage entries using keys used to save them
   * @param keys Unique identifiers of the resources for deletion
   */
  async delete(...keys: string[]): Promise<void> {
    keys.map(async key => {
      await this.storage.remove(key);
    });
  }

  /**
   * Deletes all the entries in the storage (empty the storage)
   */
  async deleteAll(): Promise<void> {
    return this.storage.clear();
  }

  /**
   * Gets a value from the storage using the provided key
   * @param key Unique identifier of the wanted resource in storage
   */
  async get<T>(key: string): Promise<T> {
    const entry = await this.storage.get(key);
    if (!entry) {
      throw new Error('Value not found.');
    }

    return entry;
  }

  /* eslint-disable @typescript-eslint/no-explicit-any */
  /**
   * Retrieves all the storage entries
   */
  async getAll(): Promise<Map<string, any>> {
    const map: Map<string, any> = new Map();
    // eslint-disable-next-line unicorn/no-array-for-each
    await this.storage.forEach((value, key) => {
      map.set(key, value);
    });

    return map;
  }

  /**
   * Retrieves multiple entries from the storage using their keys
   * @param keys Unique identifiers of wanted resources from the storage
   */
  async getMultiple(keys: string[]): Promise<Map<string, any>> {
    const gets: Array<Promise<any>> = [];
    const map = new Map();
    const getToMap = async (key: string) => {
      map.set(key, await this.get(key));
    };
    keys.map(key => {
      gets.push(getToMap(key));
    });
    await Promise.all(gets);

    return map;
  }

  /**
   * Provides information if storage has an entry with the given key
   * @param key Unique identifier of the resource in storage
   */
  async has(key: string): Promise<boolean> {
    return (await this.storage.keys()).includes(key);
  }

  /**
   * Initializes the storage (waits until it's ready)
   */
  async init(): Promise<void> {
    await this.storage.create();
  }

  /**
   * Provides information if storage is empty or not
   */
  async isEmpty(): Promise<boolean> {
    return (await this.storage.length()) === 0;
  }

  /**
   * Provides a number of entries in the storage (number of keys)
   */
  async length(): Promise<number> {
    return this.storage.length();
  }

  /**
   * Puts a value of type T into the storage using provided key
   * @param key Unique identifier
   * @param value Resource to store under the key
   * @returns Returns a promise that resolves when the key and value are set
   */
  async put<T>(key: string, value: T) {
    return this.storage.set(key, value);
  }

  /**
   * Saves multiple entries into the storage
   * @param entries Resources to be put into the storage
   */
  async putMultiple(entries: Map<string, any>): Promise<Map<string, any>> {
    const puts: Array<Promise<any>> = [];
    for (const [key, value] of entries.entries()) {
      puts.push(this.put(key, value));
    }
    await Promise.all(puts);

    return entries;
  }

  /**
   * Gets values from the storage using the provided pattern
   * @param pattern Regular expression or text to test existing storage keys with
   */
  async search<T>(pattern: RegExp | string): Promise<Map<string, T>> {
    const map: Map<string, any> = new Map();
    const check = (input: RegExp | string) => {
      if (input instanceof RegExp) {
        return (p: any, k: string): boolean => {
          return p.test(k);
        };
      }

      return (p: any, k: string): boolean => {
        return k.includes(p);
      };
    };
    const checkIt = check(pattern);
    // eslint-disable-next-line unicorn/no-array-for-each
    await this.storage.forEach((value, key) => {
      if (checkIt(pattern, key)) {
        map.set(key, value);
      }
    });

    return map;
  }
}
