import { Storage } from './Storage';
import { SecureStorage } from './SecureStorage';
import { cryptoUtil } from '../util/CryptoUtil';

export const setCvStorageApi = storageApi => {
    ClientStorage.setStorageApi(storageApi);
};

class ClientStorage implements Storage {
    private static _storage:Storage;

    public static setStorageApi(storageApi) {
        if (storageApi) {
            if (storageApi.getAllKeys) {
                // Assume AsyncStorage
                ClientStorage._storage = new AsyncStorageAdapter(storageApi);
            } else {
                // Assume window.localStorage
                ClientStorage._storage = new LocalStorageAdapter(storageApi);
            }
        }
    }

    constructor() {
        // Attempt to set the default storage backend to localStorage
        if (!ClientStorage._storage) {
            if  (typeof(localStorage) !== 'undefined') {
                if (localStorage && localStorage.getItem) {
                    ClientStorage.setStorageApi(localStorage);
                }
            }
        }
    }


    public getSecureInstance(): SecureStorage {
        return new SecureStorageAdapter(this);
    }

    public getItem(key: string): Promise<string> {
        return ClientStorage._storage.getItem(key);
    }

    public getJson(key: string): Promise<any> {
        return ClientStorage._storage.getJson(key);
    }

    public setItem(key: string, value: string): Promise<void> {
        return ClientStorage._storage.setItem(key, value);
    }

    public setJson(key: string, value: any): Promise<void> {
        return ClientStorage._storage.setJson(key, value);
    }

    public removeItem(key: string): Promise<void> {
        return ClientStorage._storage.removeItem(key);
    }

    public clearAll(): Promise<void> {
        return ClientStorage._storage.clearAll();
    }

    public getAllKeys(): Promise<string[]> {
        return ClientStorage._storage.getAllKeys();
    }

    public multiRemove(keys: Array<string>): Promise<void> {
        return ClientStorage._storage.multiRemove(keys);
    }

}

class SecureStorageAdapter implements SecureStorage {

    private _secretKey:string = null;

    constructor(readonly storage:Storage){}
    
    public set secretKey(secretKey:string) {
        this._secretKey = secretKey;
    }

    public destroySecretKey() {
        delete(this._secretKey);
    }

    public getItem(key: string): Promise<string> {
        return this.storage.getItem(key).then(
            encValue => { 
                if(!encValue) return null;
                return cryptoUtil.decrypt(encValue, this.getSecretKey())
            }
        );
    }

    public getJson(key: string): Promise<any> {
        return this.getItem(key).then(JSON.parse);
    }

    public setItem(key: string, value: string): Promise<void> {
        const encValue = value ? cryptoUtil.encrypt(value, this.getSecretKey()) : null;
        return this.storage.setItem(key, encValue);
    }

    public setJson(key: string, value: any): Promise<void> {
        let stringVal = null;
        try {
            stringVal = JSON.stringify(value);
        } catch (err) {
            return Promise.reject(new Error(`Storage::setItem(${key}) failed to stringify JSON value`));
        }
        return this.setItem(key, stringVal);
    }

    public removeItem(key: string): Promise<void> {
        return this.storage.removeItem(key);
    }

    public clearAll(): Promise<void> {
        return this.storage.clearAll();
    }

    public getAllKeys(): Promise<string[]> {
        return this.storage.getAllKeys();
    }

    public multiRemove(keys: Array<string>): Promise<void> {
        return this.storage.multiRemove(keys);
    }

    private getSecretKey():string {
        if(this._secretKey) return this._secretKey;
        throw new Error(`Storage::getSecretKey No secret key set in SecureStorageAdapter`);
    }
    
}

class AsyncStorageAdapter implements Storage {

    constructor(readonly api:any){}

    public getItem(key: string) {
        return this.api.getItem(key);
    }

    public getJson(key: string) {
        return this.api.getItem(key).then(value => {
            try {
                return JSON.parse(value);
            } catch (err) {
                throw new Error(`Storage::getItem('${key}') parse JSON failed for: ${value}`);
            }
        });
    }

    public setItem(key: string, value: string) {
        return this.api.setItem(key, value);
    }

    public setJson(key: string, value: any) {
        let stringVal = null;
        try {
            stringVal = JSON.stringify(value);
        } catch (err) {
            return Promise.reject(new Error(`Storage::setItem(${key}) failed to stringify JSON value`));
        }
        return this.api.setItem(key, stringVal);
    }

    public removeItem(key: string) {
        return this.api.removeItem(key);
    }

    public clearAll() {
        return this.api.clear();
    }

    public getAllKeys() {
        return this.api.getAllKeys();
    }

    public multiRemove(keys: Array<string>) {
        return this.api.multiRemove(keys);
    }
}

class LocalStorageAdapter implements Storage {

    constructor(readonly api){}

    public getItem(key: string):Promise<string> {
        return new Promise((resolve, reject) => {
           try {
               const value:string = this.api.getItem(key);
               resolve(value);
           } catch (e) {
               reject(e);
           }
        });
    }

    public getJson(key: string):Promise<any> {
        return new Promise((resolve, reject) => {
            try {
                resolve(JSON.parse(this.api.getItem(key)));
            } catch (err) {
                reject(Error(`Storage::getItem('${key}') parse JSON failed`));
            }
        });
    }

    public setItem(key: string, value: string):Promise<void> {
        return new Promise((resolve, reject) => {
            try {
                resolve(this.api.setItem(key, value));
            } catch (e) {
                reject(e);
            }
        });
    }

    public setJson(key: string, value: any):Promise<void> {
        return new Promise((resolve, reject) => {
            try {
                const stringVal = JSON.stringify(value);
                resolve(this.api.setItem(key, stringVal));
            } catch (e) {
                reject(e);
            }
        });
    }

    public removeItem(key: string):Promise<void> {
        return new Promise((resolve, reject) => {
            try {
                resolve(this.api.removeItem(key));
            } catch (e) {
                reject(e);
            }
        });
    }

    public clearAll():Promise<void> {
        return new Promise((resolve, reject) => {
            try {
                resolve(this.api.clear());
            } catch (e) {
                reject(e);
            }
        });
    }

    public getAllKeys():Promise<string[]> {
        return new Promise((resolve, reject) => {
            try {
                const keys:Array<string> = [];
                for(let i:number = 0; i < this.api.length; i++) {
                   keys.push(this.api.key(i));
                }
                resolve(keys);
            } catch (e) {
                reject(e);
            }
        });
    }

    public multiRemove(keys: Array<string>):Promise<void> {
        return new Promise((resolve, reject) => {
            try {
                keys.forEach((key) => {
                    this.api.removeItem(key);
                });
                resolve();
            } catch (e) {
                reject(e);
            }
        });
    }
}

export const storage: ClientStorage = new ClientStorage();

