/*
 * Copyright (C) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

export class IndexedDBHelp {
  private dbName: string = '';
  private dbVersion: number = 1;
  private db: IDBDatabase | undefined;

  public open(dbName: string, dbVersion: number, storeOptions?: Array<StoreOptions>): Promise<IDBDatabase> {
    this.dbName = dbName;
    this.dbVersion = dbVersion;
    return new Promise((resolve, reject) => {
      if (this.db && this.db.name === dbName && this.db.version === dbVersion) {
        resolve(this.db);
        return;
      }
      const idbOpenDBRequest = indexedDB.open(dbName, dbVersion);
      idbOpenDBRequest.onupgradeneeded = (): void => {
        const database: IDBDatabase = idbOpenDBRequest.result;
        this.db = database;
        storeOptions?.forEach((option) => {
          let optionName = option.name;
          if (database.objectStoreNames.contains(optionName) === false) {
            if (option.objectStoreParameters) {
              let objectStore = database.createObjectStore(optionName, option.objectStoreParameters);
              option.dataItems?.forEach((dataItem) => {
                if (dataItem.indexParameters) {
                  objectStore.createIndex(dataItem.name, dataItem.keypath, dataItem.indexParameters);
                } else {
                  objectStore.createIndex(dataItem.name, dataItem.keypath);
                }
              });
            } else {
              let objectStore = database.createObjectStore(optionName);
              option.dataItems?.forEach((dataItem) => {
                if (dataItem.indexParameters) {
                  objectStore.createIndex(dataItem.name, dataItem.name, dataItem.indexParameters);
                } else {
                  objectStore.createIndex(dataItem.name, dataItem.name);
                }
              });
            }
          }
        });
        resolve(database);
      };
      idbOpenDBRequest.onsuccess = (event): void => {
        const database: IDBDatabase = idbOpenDBRequest.result;
        this.db = database;
        resolve(database);
      };
      idbOpenDBRequest.onerror = (event): void => {
        reject(event);
      };
    });
  }

  private async transaction(storeName: string): Promise<IDBTransaction> {
    if (this.db === undefined) {
      this.db = await this.open(this.dbName, this.dbVersion);
    }
    return this.db.transaction([storeName], 'readwrite');
  }

  public async getObjectStore(storeName: string): Promise<IDBObjectStore> {
    let transaction = await this.transaction(storeName);
    return transaction.objectStore(storeName);
  }

  public get(storeName: string, query: IDBValidKey | IDBKeyRange, queryIndex?: string): Promise<unknown> {
    return new Promise((resolve, reject) => {
      this.getObjectStore(storeName).then((objectStore: IDBObjectStore) => {
        // @ts-ignore
        let request: IDBRequest<unknown>;
        if (queryIndex) {
          const index = objectStore.index(queryIndex);
          //@ts-ignore
          request = index.getAll(query);
        } else {
          //@ts-ignore
          request = objectStore.getAll(query);
        }
        request.onsuccess = function (event): void {
          // @ts-ignore
          resolve(event.target.result);
        };
        request.onerror = (event): void => {
          reject(event);
        };
      });
    });
  }
  // @ts-ignore
  public add(storeName: string, value: unknown, key?: IDBValidKey): Promise<unknown> {
    return new Promise((resolve, reject) => {
      this.getObjectStore(storeName).then((objectStore: IDBObjectStore) => {
        const request = objectStore.add(value, key);
        request.onsuccess = function (event): void {
          // @ts-ignore
          resolve(event.target.result);
        };
        request.onerror = (event): void => {
          reject(event);
        };
      });
    });
  }

  public delete(storeName: string, query: IDBValidKey | IDBKeyRange): Promise<unknown> {
    return new Promise((resolve, reject) => {
      this.getObjectStore(storeName).then((objectStore: IDBObjectStore) => {
        const request = objectStore.delete(query);
        request.onsuccess = function (event): void {
          // @ts-ignore
          resolve(event.target.result);
        };
        request.onerror = (event): void => {
          reject(event);
        };
      });
    });
  }
  // @ts-ignore
  public put(storeName: string, value: unknown, key?: IDBValidKey): Promise<unknown> {
    return new Promise((resolve, reject) => {
      this.getObjectStore(storeName).then((objectStore: IDBObjectStore) => {
        const request = objectStore.put(value, key);
        request.onsuccess = function (event): void {
          // @ts-ignore
          resolve(event.target.result);
        };
        request.onerror = (event): void => {
          reject(event);
        };
      });
    });
  }
}

export class StoreOptions {
  name: string = '';
  objectStoreParameters?: IDBObjectStoreParameters;
  dataItems?: Array<{ name: string; keypath: string[] | string; indexParameters?: IDBIndexParameters }>;
}