// @flow
/* Reducers for Redux
 * all the reducers are here.
 * since most actions are similar (all entities have the same basic actions available) there's alot of templating that happens.
*/
import { combineReducers } from 'redux';

import type { Action } from './actions.js';

import type { BeatObject, BeatSettings, BeatEnvironment } from '@helpers/beat.js';

export type State = {|
	+database: BeatObject[],
	+library: BeatObject[],
	+dataformat: BeatObject[],
	+algorithm: BeatObject[],
	+toolchain: BeatObject[],
	+experiment: BeatObject[],
	+plotter: BeatObject[],
	+plotterparameter: BeatObject[],
	+settings: BeatSettings,
	+environments: BeatEnvironment[],
	+unsaved: boolean,
|};

export type Reducer = (state: State, action: Action) => State;

const initialState: State = {
	database: [],
	library: [],
	dataformat: [],
	algorithm: [],
	toolchain: [],
	experiment: [],
	plotter: [],
	plotterparameter: [],
	settings: {
		prefix: '',
	},
	environments: [],
	unsaved: false,
};

const saveFunc = (state: BeatObject[], objs: BeatObject[]): BeatObject[] => [...objs].sort((a, b) => a.name > b.name ? 1 : -1);
const addFunc = (state: BeatObject[], obj: BeatObject): BeatObject[] => [obj, ...state];
const deleteFunc = (state: BeatObject[], obj: BeatObject): BeatObject[] => state.filter(o => o.name !== obj.name);
const updateFunc = (state: BeatObject[], { oldName, obj }): BeatObject[] => state.map(o => o.name === oldName ? obj : o);

const entityFunc = (state: BeatObject[], actionType: string, payload: any): BeatObject[] => {
	let func;
	switch(true){
		case (/^SAVE_/.test(actionType)):
			func = saveFunc;
			break;
		case (/^ADD_/.test(actionType)):
			func = addFunc;
			break;
		case (/^DELETE_/.test(actionType)):
			func = deleteFunc;
			break;
		case (/^UPDATE_/.test(actionType)):
			func = updateFunc;
			break;
		default:
			throw new Error(`Invalid actionType "${ actionType }"`);
	}

	return func(state, payload);
};

const database = (state: BeatObject[] = [], action: Action) => action.type.endsWith('DATABASE') ? entityFunc(state, action.type, action.payload) : state;
const library = (state: BeatObject[] = [], action: Action) => action.type.endsWith('LIBRARY') ? entityFunc(state, action.type, action.payload) : state;
const dataformat = (state: BeatObject[] = [], action: Action) => action.type.endsWith('DATAFORMAT') ? entityFunc(state, action.type, action.payload) : state;
const algorithm = (state: BeatObject[] = [], action: Action) => action.type.endsWith('ALGORITHM') ? entityFunc(state, action.type, action.payload) : state;
const toolchain = (state: BeatObject[] = [], action: Action) => action.type.endsWith('TOOLCHAIN') ? entityFunc(state, action.type, action.payload) : state;
const experiment = (state: BeatObject[] = [], action: Action) => action.type.endsWith('EXPERIMENT') ? entityFunc(state, action.type, action.payload) : state;
const plotter = (state: BeatObject[] = [], action: Action) => action.type.endsWith('PLOTTER') ? entityFunc(state, action.type, action.payload) : state;
const plotterparameter = (state: BeatObject[] = [], action: Action) => action.type.endsWith('PLOTTERPARAMETER') ? entityFunc(state, action.type, action.payload) : state;
const settings = (state: BeatSettings = {prefix: ''}, action: Action) => {
	if(!action.type.endsWith('SETTINGS'))
		return state;
	const {type, payload} = action;
	switch(type){
		case 'UPDATE_SETTINGS':
			return {
				...state,
				...payload,
			};
		case 'SAVE_SETTINGS':
			return {
				...payload
			};
		default:
			throw new Error(`Invalid actionType "${ type }"`);
	}
};
const environments = (state: BeatEnvironment[] = [], action: Action) => {
	if(!action.type.endsWith('ENVIRONMENTS'))
		return state;
	const {type, payload} = action;
	switch(type){
		case 'SAVE_ENVIRONMENTS':
			return payload;
		default:
			throw new Error(`Invalid actionType "${ type }"`);
	}
};
const unsaved = (state: boolean, action: Action) => {
	if(action.type === 'CLEAR_UNSAVED')
		return false;
	else if(!action.type.startsWith('SAVE_')){
		return true;
	}
	return state;
};

export default combineReducers({
	database,
	library,
	dataformat,
	algorithm,
	toolchain,
	experiment,
	plotter,
	plotterparameter,
	settings,
	environments,
	unsaved,
});
