import {createLogger} from "@gongt/ts-stl-library/debug/create-logger";
import {LOG_LEVEL} from "@gongt/ts-stl-library/debug/levels";
import {IAData, IReduxActionConstructor, ISingleReducer} from "./action";
import {IReducerInfo} from "./combine-reducers";
import {IState} from "./preload-state";

const silly = createLogger(LOG_LEVEL.SILLY, 'v-store');

export interface IVirtualStore<ValueInterface> {
	getReducers?(): IReducerInfo<ValueInterface>[];
	
	name: string;
	defaultValue?: ValueInterface;
}

export interface IVirtualStoreConstructor<ValueInterface> {
	new(): IVirtualStore<ValueInterface>;
	
	name: string;
}

/** only use in this file */
export interface IVirtualStoreClass<ValueInterface> {
	new(): VirtualStore<ValueInterface>;
	
	name: string;
}

export function reduce<VI, IData extends IAData>
(Constructor: IVirtualStoreClass<VI>,
 Action: IReduxActionConstructor<IData, VI>,
 ...reducer: ISingleReducer<VI, IData>[]) {
	const vs = new Constructor;
	return vs['reduce'](Action, ...reducer);
}

export function reduceAny<VI, IData extends IAData>
(Constructor: IVirtualStoreClass<VI>,
 Action: IReduxActionConstructor<IData, VI>,
 ...reducer: ISingleReducer<VI, IData>[]) {
	const vs = new Constructor;
	return vs['reduceAny'](Action, ...reducer);
}

export function extractorOf<VI>(vs: IVirtualStoreClass<VI>) {
	return function (state: IState): VI {
		if (state[vs.name] === null || state[vs.name] === undefined) {
			displayExtractError(state, vs.name);
		}
		return state[vs.name];
	};
}

export function subExtractorOf<VI, KT = keyof VI>(vs: IVirtualStoreClass<VI>, key: KT) {
	return function (state: IState) {
		if (state[vs.name] === null || state[vs.name] === undefined) {
			displayExtractError(state, vs.name);
		}
		if (!state[vs.name].hasOwnProperty(key)) {
			displayExtractError(state, vs.name + '.' + key);
		}
		return state[vs.name][key];
	};
}

const output = createLogger(LOG_LEVEL.ERROR, 'vs');

function displayExtractError(state: any, varName: string) {
	output('================');
	output(` Cannot get variable '${varName}' of redux store`);
	output(` did you called store.reigster(${varName.replace(/\..*$/, '')})`);
	output(`    and defined a property "defaultValue" in subStore "${varName.replace(/\..*$/, '')}"`);
	output(' STATE: %j', state);
	output('================');
	throw new Error(`Cannot get variable '${varName}' of redux store`);
}

export abstract class VirtualStore<ValueInterface> implements IVirtualStore<ValueInterface> {
	private reducers: IReducerInfo<ValueInterface>[] = [];
	readonly name: string;
	readonly defaultValue: ValueInterface;
	
	static readonly ANY = '*';
	
	constructor() {
		if (this.constructor['instance']) {
			return this.constructor['instance'];
		}
		this.constructor['instance'] = this;
		this.name = <any>this.constructor.name;
		silly('construct vs: ', this.name)
	}
	
	protected reduce<IData extends IAData>(Action: IReduxActionConstructor<IData, ValueInterface>,
	                                       ...reducer: ISingleReducer<ValueInterface, IData>[]): void {
		for (let fn of reducer) {
			this.reducers.push({
				callback: fn,
				actionName: Action.name,
				storeName: this.name,
				global: false,
			});
		}
	}
	
	protected reduceAny<IData extends IAData>(Action: IReduxActionConstructor<IData, any>,
	                                          ...reducer: ISingleReducer<any, IData>[]): void {
		for (let fn of reducer) {
			this.reducers.push({
				callback: fn,
				actionName: Action.name,
				storeName: this.name,
				global: true,
			});
		}
	}
	
	public getReducers(): IReducerInfo<ValueInterface>[] {
		Object.freeze(this.reducers);
		return this.reducers;
	}
}
