import * as Cookie from 'js-cookie';
import {getGlobal, setGlobal, Window} from "./window-utils";

export enum StorageDriverId {
    COOKIES,
    LOCAL_STORAGE,
    SESSION_STORAGE,
    WINDOW,
}

interface StorageDriver<U = any> {

    readonly id: StorageDriverId;

    set<T = any>(key: string, value: T, options?: U): boolean;

    get<T = any>(key: string): T;

    remove(key: string, options?: U): boolean;

    clear(): boolean;

    all(): { [key: string]: any };

}

class CookieDriver implements StorageDriver<Cookie.CookieAttributes> {

    readonly id: StorageDriverId = StorageDriverId.COOKIES;

    set<T = any>(key: string, value: T, options?: Cookie.CookieAttributes): boolean {
        Cookie.set(key, value as any, options);
        return true;
    }

    get<T = any>(key: string): T {
        return Cookie.getJSON(key) as unknown as T;
    }

    remove(key: string, options?: Cookie.CookieAttributes): boolean {
        Cookie.remove(key, options)
        return true;
    }

    clear(): boolean {
        console.warn("Cookies does not support clear storage");
        return true;
    }

    all(): { [key: string]: any } {
        console.warn("Cookies does not support fetch all");
        return {};
    }

}

class LocalStorageDriver implements StorageDriver {

    readonly id: StorageDriverId = StorageDriverId.LOCAL_STORAGE;

    set<T = any>(key: string, value: T, options?: never): boolean {
        localStorage.setItem(key, JSON.stringify(value));
        return true;
    }

    get<T = any>(key: string): T {
        return JSON.parse(localStorage.getItem(key)) as T;
    }

    remove(key: string, options?: never): boolean {
        localStorage.removeItem(key);
        return true;
    }

    clear(): boolean {
        localStorage.clear();
        return true;
    }

    all(): { [key: string]: any } {
        let all = {};
        for (let i = 0; i < localStorage.length; i++) {
            const key = localStorage.key(i);
            all[key] = this.get(key);
        }
        return all;
    }

}

class SessionStorageDriver implements StorageDriver {

    readonly id: StorageDriverId = StorageDriverId.SESSION_STORAGE;

    set<T = any>(key: string, value: T, options?: never): boolean {
        sessionStorage.setItem(key, JSON.stringify(value));
        return true;
    }

    get<T = any>(key: string): T {
        return JSON.parse(sessionStorage.getItem(key)) as T;
    }

    remove(key: string, options?: never): boolean {
        sessionStorage.removeItem(key);
        return true;
    }

    clear(): boolean {
        sessionStorage.clear();
        return true;
    }

    all(): { [key: string]: any } {
        let all = {};
        for (let i = 0; i < sessionStorage.length; i++) {
            const key = sessionStorage.key(i);
            all[key] = this.get(key);
        }
        return all;
    }

}

class WindowDriver<U extends Window = Window> implements StorageDriver {

    readonly id: StorageDriverId = StorageDriverId.SESSION_STORAGE;

    set<T = any>(key: string, value: T, options?: never): boolean {
        let currentStorage = JSON.parse(getGlobal<U>('storage')) || {};
        currentStorage[key] = value;
        setGlobal<U>('storage', JSON.stringify(currentStorage));
        return true;
    }

    get<T = any>(key: string): T {
        let currentStorage = JSON.parse(getGlobal<U>('storage')) || {};
        return currentStorage[key] as T;
    }

    remove(key: string, options?: never): boolean {
        let currentStorage = JSON.parse(getGlobal<U>('storage')) || {};
        delete currentStorage[key];
        setGlobal<U>('storage', JSON.stringify(currentStorage));
        return true;
    }

    clear(): boolean {
        setGlobal<U>('storage', {});
        return true;
    }

    all(): { [key: string]: any } {
        return JSON.parse(getGlobal<U>('storage')) || {};
    }

}

const cookieDriver = new CookieDriver();
const localStorageDriver = new LocalStorageDriver();
const sessionStorageDriver = new SessionStorageDriver();
const windowDriver = new WindowDriver();

export default class StorageUtils {

    public static defaultStorageDriverId = StorageDriverId.LOCAL_STORAGE;

    private static getStorageDriver(driverId: StorageDriverId): StorageDriver {
        switch (driverId) {
            case StorageDriverId.COOKIES:
                return cookieDriver;
            case StorageDriverId.LOCAL_STORAGE:
                return localStorageDriver;
            case StorageDriverId.SESSION_STORAGE:
                return sessionStorageDriver
            case StorageDriverId.WINDOW:
                return windowDriver;
            default:
                return StorageUtils.getStorageDriver(StorageUtils.defaultStorageDriverId);
        }
    }

    public static setWithOptions<T = any>(key: string, value: any, options?: any, driverId?: StorageDriverId): boolean {
        return StorageUtils.getStorageDriver(driverId).set(key, value, options as any);
    }

    public static set<T = any>(key: string, value: any, driverId?: StorageDriverId): boolean {
        return StorageUtils.getStorageDriver(driverId).set(key, value);
    }

    public static get<T = any>(key: string, driverId?: StorageDriverId): T {
        return StorageUtils.getStorageDriver(driverId).get(key);
    }

    public static removeWithOptions(key: string, options?: any, driverId?: StorageDriverId): boolean {
        return StorageUtils.getStorageDriver(driverId).remove(key, options);
    }

    public static remove(key: string, driverId?: StorageDriverId): boolean {
        return StorageUtils.getStorageDriver(driverId).remove(key);
    }

    public static clear(driverId?: StorageDriverId): boolean {
        return StorageUtils.getStorageDriver(driverId).clear();
    }

    public static all(driverId?: StorageDriverId): { [key: string]: any } {
        return StorageUtils.getStorageDriver(driverId).all();
    }

}