import Module from '../../core/Module.js';

import defaultReaders from './defaults/readers.js';
import defaultWriters from './defaults/writers.js';

export default class Persistence extends Module{

	static moduleName = "persistence";

	static moduleInitOrder = -10;

	//load defaults
	static readers = defaultReaders;
	static writers = defaultWriters;

	constructor(table){
		super(table);

		this.mode = "";
		this.id = "";
		// this.persistProps = ["field", "width", "visible"];
		this.defWatcherBlock = false;
		this.config = {};
		this.readFunc = false;
		this.writeFunc = false;

		this.registerTableOption("persistence", false);
		this.registerTableOption("persistenceID", ""); //key for persistent storage
		this.registerTableOption("persistenceMode", true); //mode for storing persistence information
		this.registerTableOption("persistenceReaderFunc", false); //function for handling persistence data reading
		this.registerTableOption("persistenceWriterFunc", false); //function for handling persistence data writing
	}

	// Test for whether localStorage is available for use.
	localStorageTest() {
		var  testKey =  "_tabulator_test";

		try {
			window.localStorage.setItem( testKey, testKey);
			window.localStorage.removeItem( testKey );
			return true;
		} catch(e) {
			return false;
		}
	}

	//setup parameters
	initialize(){
		if(this.table.options.persistence){
			//determine persistent layout storage type
			var mode = this.table.options.persistenceMode,
			id = this.table.options.persistenceID,
			retrievedData;

			this.mode = mode !== true ?  mode : (this.localStorageTest() ? "local" : "cookie");

			if(this.table.options.persistenceReaderFunc){
				if(typeof this.table.options.persistenceReaderFunc === "function"){
					this.readFunc = this.table.options.persistenceReaderFunc;
				}else{
					if(Persistence.readers[this.table.options.persistenceReaderFunc]){
						this.readFunc = Persistence.readers[this.table.options.persistenceReaderFunc];
					}else{
						console.warn("Persistence Read Error - invalid reader set", this.table.options.persistenceReaderFunc);
					}
				}
			}else{
				if(Persistence.readers[this.mode]){
					this.readFunc = Persistence.readers[this.mode];
				}else{
					console.warn("Persistence Read Error - invalid reader set", this.mode);
				}
			}

			if(this.table.options.persistenceWriterFunc){
				if(typeof this.table.options.persistenceWriterFunc === "function"){
					this.writeFunc = this.table.options.persistenceWriterFunc;
				}else{
					if(Persistence.writers[this.table.options.persistenceWriterFunc]){
						this.writeFunc = Persistence.writers[this.table.options.persistenceWriterFunc];
					}else{
						console.warn("Persistence Write Error - invalid reader set", this.table.options.persistenceWriterFunc);
					}
				}
			}else{
				if(Persistence.writers[this.mode]){
					this.writeFunc = Persistence.writers[this.mode];
				}else{
					console.warn("Persistence Write Error - invalid writer set", this.mode);
				}
			}

			//set storage tag
			this.id = "tabulator-" + (id || (this.table.element.getAttribute("id") || ""));

			this.config = {
				sort:this.table.options.persistence === true || this.table.options.persistence.sort,
				filter:this.table.options.persistence === true || this.table.options.persistence.filter,
				headerFilter:this.table.options.persistence === true || this.table.options.persistence.headerFilter,
				group:this.table.options.persistence === true || this.table.options.persistence.group,
				page:this.table.options.persistence === true || this.table.options.persistence.page,
				columns:this.table.options.persistence === true ? ["title", "width", "visible"] : this.table.options.persistence.columns,
			};

			//load pagination data if needed
			if(this.config.page){
				retrievedData = this.retrieveData("page");

				if(retrievedData){
					if(typeof retrievedData.paginationSize !== "undefined" && (this.config.page === true || this.config.page.size)){
						this.table.options.paginationSize = retrievedData.paginationSize;
					}

					if(typeof retrievedData.paginationInitialPage !== "undefined" && (this.config.page === true || this.config.page.page)){
						this.table.options.paginationInitialPage = retrievedData.paginationInitialPage;
					}
				}
			}

			//load group data if needed
			if(this.config.group){
				retrievedData = this.retrieveData("group");

				if(retrievedData){
					if(typeof retrievedData.groupBy !== "undefined" && (this.config.group === true || this.config.group.groupBy)){
						this.table.options.groupBy = retrievedData.groupBy;
					}
					if(typeof retrievedData.groupStartOpen !== "undefined" && (this.config.group === true || this.config.group.groupStartOpen)){
						this.table.options.groupStartOpen = retrievedData.groupStartOpen;
					}
					if(typeof retrievedData.groupHeader !== "undefined" && (this.config.group === true || this.config.group.groupHeader)){
						this.table.options.groupHeader = retrievedData.groupHeader;
					}
				}
			}

			if(this.config.columns){
				this.table.options.columns = this.load("columns", this.table.options.columns);
				this.subscribe("column-init", this.initializeColumn.bind(this));
				this.subscribe("column-show", this.save.bind(this, "columns"));
				this.subscribe("column-hide", this.save.bind(this, "columns"));
				this.subscribe("column-moved", this.save.bind(this, "columns"));
			}

			this.subscribe("table-built", this.tableBuilt.bind(this), 0);

			this.subscribe("table-redraw", this.tableRedraw.bind(this));

			this.subscribe("filter-changed", this.eventSave.bind(this, "filter"));
			this.subscribe("filter-changed", this.eventSave.bind(this, "headerFilter"));
			this.subscribe("sort-changed", this.eventSave.bind(this, "sort"));
			this.subscribe("group-changed", this.eventSave.bind(this, "group"));
			this.subscribe("page-changed", this.eventSave.bind(this, "page"));
			this.subscribe("column-resized", this.eventSave.bind(this, "columns"));
			this.subscribe("column-width", this.eventSave.bind(this, "columns"));
			this.subscribe("layout-refreshed", this.eventSave.bind(this, "columns"));
		}

		this.registerTableFunction("getColumnLayout", this.getColumnLayout.bind(this));
		this.registerTableFunction("setColumnLayout", this.setColumnLayout.bind(this));
	}

	eventSave(type){
		if(this.config[type]){
			this.save(type);
		}
	}

	tableBuilt(){
		var sorters, filters, headerFilters;

		if(this.config.sort){
			sorters = this.load("sort");

			if(!sorters === false){
				this.table.options.initialSort = sorters;
			}
		}

		if(this.config.filter){
			filters = this.load("filter");

			if(!filters === false){
				this.table.options.initialFilter = filters;
			}
		}
		if(this.config.headerFilter){
			headerFilters = this.load("headerFilter");

			if(!headerFilters === false){
				this.table.options.initialHeaderFilter = headerFilters;
			}
		}
		
	}

	tableRedraw(force){
		if(force && this.config.columns){
			this.save("columns");
		}
	}

	///////////////////////////////////
	///////// Table Functions /////////
	///////////////////////////////////

	getColumnLayout(){
		return this.parseColumns(this.table.columnManager.getColumns());
	}

	setColumnLayout(layout){
		this.table.columnManager.setColumns(this.mergeDefinition(this.table.options.columns, layout, true));
		return true;
	}

	///////////////////////////////////
	///////// Internal Logic //////////
	///////////////////////////////////

	initializeColumn(column){
		var def, keys;

		if(this.config.columns){
			this.defWatcherBlock = true;

			def = column.getDefinition();

			keys = this.config.columns === true ? Object.keys(def) : this.config.columns;

			keys.forEach((key)=>{
				var props = Object.getOwnPropertyDescriptor(def, key);
				var value = def[key];

				if(props){
					Object.defineProperty(def, key, {
						set: (newValue) => {
							value = newValue;

							if(!this.defWatcherBlock){
								this.save("columns");
							}

							if(props.set){
								props.set(newValue);
							}
						},
						get:() => {
							if(props.get){
								props.get();
							}
							return value;
						}
					});
				}
			});

			this.defWatcherBlock = false;
		}
	}

	//load saved definitions
	load(type, current){
		var data = this.retrieveData(type);

		if(current){
			data = data ? this.mergeDefinition(current, data) : current;
		}

		return data;
	}

	//retrieve data from memory
	retrieveData(type){
		return this.readFunc ? this.readFunc(this.id, type) : false;
	}

	//merge old and new column definitions
	mergeDefinition(oldCols, newCols, mergeAllNew){
		var output = [];

		newCols = newCols || [];

		newCols.forEach((column, to) => {
			var from = this._findColumn(oldCols, column),
			keys;

			if(from){
				if(mergeAllNew){
					keys = Object.keys(column);
				}else if(this.config.columns === true || this.config.columns == undefined){
					keys =  Object.keys(from);
					keys.push("width");
				}else{
					keys = this.config.columns;
				}

				keys.forEach((key)=>{
					if(key !== "columns" && typeof column[key] !== "undefined"){
						from[key] = column[key];
					}
				});

				if(from.columns){
					from.columns = this.mergeDefinition(from.columns, column.columns);
				}

				output.push(from);
			}
		});

		oldCols.forEach((column, i) => {
			var from = this._findColumn(newCols, column);

			if (!from) {
				if(output.length>i){
					output.splice(i, 0, column);
				}else{
					output.push(column);
				}
			}
		});

		return output;
	}

	//find matching columns
	_findColumn(columns, subject){
		var type = subject.columns ? "group" : (subject.field ? "field" : "object");

		return columns.find(function(col){
			switch(type){
				case "group":
					return col.title === subject.title && col.columns.length === subject.columns.length;

				case "field":
					return col.field === subject.field;

				case "object":
					return col === subject;
			}
		});
	}

	//save data
	save(type){
		var data = {};

		switch(type){
			case "columns":
				data = this.parseColumns(this.table.columnManager.getColumns());
				break;

			case "filter":
				data = this.table.modules.filter.getFilters();
				break;

			case "headerFilter":
				data = this.table.modules.filter.getHeaderFilters();
				break;

			case "sort":
				data = this.validateSorters(this.table.modules.sort.getSort());
				break;

			case "group":
				data = this.getGroupConfig();
				break;

			case "page":
				data = this.getPageConfig();
				break;
		}

		if(this.writeFunc){
			this.writeFunc(this.id, type, data);
		}

	}

	//ensure sorters contain no function data
	validateSorters(data){
		data.forEach(function(item){
			item.column = item.field;
			delete item.field;
		});

		return data;
	}

	getGroupConfig(){
		var data = {};

		if(this.config.group){
			if(this.config.group === true || this.config.group.groupBy){
				data.groupBy = this.table.options.groupBy;
			}

			if(this.config.group === true || this.config.group.groupStartOpen){
				data.groupStartOpen = this.table.options.groupStartOpen;
			}

			if(this.config.group === true || this.config.group.groupHeader){
				data.groupHeader = this.table.options.groupHeader;
			}
		}

		return data;
	}

	getPageConfig(){
		var data = {};

		if(this.config.page){
			if(this.config.page === true || this.config.page.size){
				data.paginationSize = this.table.modules.page.getPageSize();
			}

			if(this.config.page === true || this.config.page.page){
				data.paginationInitialPage = this.table.modules.page.getPage();
			}
		}

		return data;
	}


	//parse columns for data to store
	parseColumns(columns){
		var definitions = [],
		excludedKeys = ["headerContextMenu", "headerMenu", "contextMenu", "clickMenu"];

		columns.forEach((column) => {
			var defStore = {},
			colDef = column.getDefinition(),
			keys;

			if(column.isGroup){
				defStore.title = colDef.title;
				defStore.columns = this.parseColumns(column.getColumns());
			}else{
				defStore.field = column.getField();

				if(this.config.columns === true || this.config.columns == undefined){
					keys =  Object.keys(colDef);
					keys.push("width");
					keys.push("visible");
				}else{
					keys = this.config.columns;
				}

				keys.forEach((key)=>{
					switch(key){
						case "width":
							defStore.width = column.getWidth();
							break;
						case "visible":
							defStore.visible = column.visible;
							break;

						default:
							if(typeof colDef[key] !== "function" && excludedKeys.indexOf(key) === -1){
								defStore[key] = colDef[key];
							}
					}
				});
			}

			definitions.push(defStore);
		});

		return definitions;
	}
}