package info.bukova.isspst.ui;

import info.bukova.isspst.StringUtils;
import info.bukova.isspst.data.DataModel;
import info.bukova.isspst.data.Season;
import info.bukova.isspst.data.SeasonsAware;
import info.bukova.isspst.filters.Filter;
import info.bukova.isspst.services.HqlDataFilter;
import info.bukova.isspst.services.IsspstException;
import info.bukova.isspst.services.Service;
import info.bukova.isspst.services.settings.SeasonService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.security.access.AccessDeniedException;
import org.zkoss.bind.BindUtils;
import org.zkoss.bind.annotation.BindingParam;
import org.zkoss.bind.annotation.Command;
import org.zkoss.bind.annotation.GlobalCommand;
import org.zkoss.bind.annotation.Init;
import org.zkoss.bind.annotation.NotifyChange;
import org.zkoss.zk.ui.Executions;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.select.annotation.WireVariable;
import org.zkoss.zul.Messagebox;
import org.zkoss.zul.Window;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ListViewModel<T extends DataModel> extends DocumentViewModel
{

	private boolean confirmDelete = false;
	private boolean filter = false;
	private Window editWin;
	private T dataBean;
	private T filterTemplate;
	private T editBean;
	private List<T> dataList;
	private List<T> fullList;
	private List<T> tmpList;
	private int selIndex = -1;
	private String sortCol;
	private boolean sortDesc = true;
	private boolean newRec = false;
	private boolean fullFill = false;

	@WireVariable
	private SeasonService seasonService;
	private List<Season> seasons;
	private Season selSeason;
	@WireVariable
	private HqlDataFilter hqlFilter;

	protected Service<T> service;
	protected Class<T> dataClass;
	protected String formZul;
	protected Filter<T> dataFilter;
	
	@Init
	public void initListViewModel()
	{
		super.initDocumentViewModel();
		seasons = seasonService.getAllSeasons();

		Map<String, String[]> map = Executions.getCurrent().getParameterMap();
		if (map.get("select") == null) {
			selSeason = seasonService.getActive();
		}
		setHqlFilter();
	}

	public List<T> getDataList() {
		if (dataList == null) {
			dataList = new ArrayList<T>();
			loadFromDbSync();
		}

		return dataList;
	}

	@NotifyChange({"dataBean","ableToDelete"})
	public void setDataBean(T data) {
		loadLazyData(data);
		this.dataBean = data;
	}
	
	protected void loadLazyData(T data) {
		service.loadLazyData("grid", data);
	}

	public void setDataFilter(Filter<T> dataFilter) {
		this.dataFilter = dataFilter;
	}

	public T getFilterTemplate() {
		if (filterTemplate == null) {
			try {
				filterTemplate = dataClass.newInstance();
			} catch (InstantiationException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}
		}

		return filterTemplate;
	}

	public T getDataBean() {
		return dataBean;
	}

	public boolean getConfirmDelete() {
		return confirmDelete;
	}

	public boolean getFilter() {
		return this.filter;
	}

	public boolean getFullFill() {
		return fullFill;
	}

	protected void newRecMode() {
		newRec = true;
	}

	protected void setEditBean(T edit) {
		this.editBean = edit;
	}

	@Command
	@NotifyChange({ "filter", "dataList", "dataBean" })
	public void filter() {
		filter = !filter;

		if (!filter) {
			dataList = fullList;
			dataBean = null;
			selIndex = -1;
		} else {
			doFilter();
			dataBean = null;
		}
	}

	@Command
	@NotifyChange("dataList")
	public void doFilter() {
		if (dataFilter == null) {
			return;
		}

		List<T> result = service.filterList(fullList, dataFilter);
		selIndex = -1;
		dataList = result;
	}

	@Command
	public void addNew() {
		try {
			newRecMode();
			editBean = service.create();
			if (editBean == null) {
				editBean = dataClass.newInstance();
			}
			showForm();
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (AccessDeniedException e) {
			Messagebox.show(StringUtils.localize("ErrorRights"), StringUtils.localize("Error"), Messagebox.OK, Messagebox.ERROR);
		}
	}
	
	@Command
	public void edit() {
		int index = dataList.indexOf(dataBean);
		newRec = false;
		if (index != -1) { 
			selIndex = index;
		}
		editBean = service.getById(dataBean.getId());
		loadLazyDataForEdit(editBean);
		showForm();
	}
	
	protected void loadLazyDataForEdit(T data) {
		service.loadLazyData("form", data);
	}
	
	@Command
	@NotifyChange("confirmDelete")
	public void delObject() {
		confirmDelete = true;
	}

	@Command
	public void delete() {
		Messagebox.show(StringUtils.localize("DbReallyDelete"), StringUtils.localize("DbDeleteRecord"), Messagebox.YES
				| Messagebox.NO, Messagebox.QUESTION,
				new EventListener<Event>() {

					@Override
					public void onEvent(Event evt) throws Exception {
						if (((Integer) evt.getData()).intValue() == Messagebox.YES) {
							try {
								service.delete(dataBean);
								dataList.remove(dataBean);
								dataBean = null;
								BindUtils.postNotifyChange(null, null,
										ListViewModel.this, "dataList");
								BindUtils.postNotifyChange(null, null,
										ListViewModel.this, "dataBean");
								BindUtils.postGlobalCommand(null, null, "reloadRelated", null);
							} catch (IsspstException e) {
								if (e.getReason() != null) {
									Messagebox.show(
											StringUtils.localize(e.getReason()),
											StringUtils.localize("Error"),
											Messagebox.OK, Messagebox.ERROR);
								} else {
									Messagebox.show(
											StringUtils.localize("DbSaveError"),
											StringUtils.localize("Error"),
											Messagebox.OK, Messagebox.ERROR);
								}
							} catch (DataIntegrityViolationException e) {
								Messagebox.show(StringUtils.localize("DbCannotDelete"), StringUtils.localize("Error"), Messagebox.OK, Messagebox.ERROR);
							} catch (AccessDeniedException e) {
								Messagebox.show(StringUtils.localize("ErrorRights"), StringUtils.localize("Error"), Messagebox.OK, Messagebox.ERROR);
							}
						}
					}
				});

		confirmDelete = false;
	}

	@NotifyChange({ "dataList", "dataBean" })
	private void onDelete() {
		try {
			service.delete(dataBean);
			dataList.remove(dataBean);
			dataBean = null;
		} catch (DataIntegrityViolationException e) {
			Messagebox.show(StringUtils.localize("DbCannotDelete"), StringUtils.localize("Error"), Messagebox.OK,
					Messagebox.ERROR);
		}
	}

	@GlobalCommand
	@NotifyChange({ "dataList", "dataBean", "ableToDelete" })
	public void refresh() {
		if (editBean != null && !editBean.isValid()) {
			return;
		}
		if (editBean != null && editBean.getCreated() == null) {
			return;
		}
		if (!filter && newRec) {
			dataList.add(editBean);
			if (dataList != fullList) {
				fullList.add(editBean);
			}
		} else if (newRec) {
			fullList.add(editBean);
		}
		dataBean = editBean;
		if (!newRec && editBean != null) {
			if (selIndex < dataList.size())
			{
				dataList.set(selIndex, editBean);
			}
		}
		
		BindUtils.postGlobalCommand(null, null, "reloadRelated", null);
		
	}

	@GlobalCommand
	@NotifyChange({ "dataList", "dataBean", "fullFill" })
	public void reload() {
		dataBean = null;
		dataList.clear();
		setHqlFilter();
		loadFromDbSync();
	}

	@Command
	@NotifyChange({"selIndex", "dataBean", "ableToDelete"})
	public void afterRender() {
		if (editBean != null && !editBean.isValid()) {
			return;
		}

		if (selIndex > dataList.size() - 1) {
			selIndex = -1;
		}

		if (newRec) {
			selIndex = dataList.size() - 1;
			newRec = false;
		}
		
		Map<String, String[]> map = Executions.getCurrent().getParameterMap();
		int id = -1;
		
		try {
			if (map.get("select") != null) {
				id = Integer.parseInt(map.get("select")[0]);
			}
		} catch (NumberFormatException e) {
			Logger logger = LoggerFactory.getLogger(this.getClass());
			logger.warn("ID is not number: " + map.get("select")[0]);
		}
		
		if (id > 0) {
			selSeason = null;
			setHqlFilter();
			dataList = getListFromService();
			for (int i = 0; i < dataList.size(); i++) {
				if (dataList.get(i).getId() == id) {
					selIndex = i;
					beforeSelectViaUrl();
					break;
				}
			}
		}
		
		if (selIndex > -1) {
			this.setDataBean(dataList.get(selIndex));
			afterSelect();
		}
	}
	
	protected void beforeSelectViaUrl() {

	}
	
	protected void afterSelect() {
		
	}

	@Command
	@NotifyChange("dataBean")
	public void onSort(@BindingParam("column") String column) {
		if (sortCol == null || this.sortCol.equals(column))
			this.sortDesc = !this.sortDesc;

		this.sortCol = column;
		selIndex = -1;
		dataBean = null;
	}

	public int getSelIndex() {
		return this.selIndex;
	}
	
	public void setSelIndex(int selIndex) {
		this.selIndex = selIndex;
	}

	@Command
	@NotifyChange({ "dataList", "fullFill" })
	public void fullFill() {
		if (fullFill && dataList.isEmpty()) {
			dataList.addAll(tmpList);
			fullList = dataList;
		}
	}

	@Command
	public void onPrint() {
		Map<String, Object> params = new HashMap<String, Object>();
		params.put("reports", service.getReports());
		params.put("data", dataList);
		params.put("singleObject", dataBean);
		params.put("service", service);
		Window win = (Window) Executions.createComponents("/app/reporting/reportDialog.zul", null, params);
		win.doModal();
	}
	
	private void loadFromDbSync() {
		dataList.addAll(getListFromService());
		fullList = dataList;
	}
	
	protected List<T> getListFromService() {
		return service.getAll();
	}

	protected void showForm() {
		Map<String, Object> arg = new HashMap<String, Object>();
		arg.put("selected", editBean);
		arg.put("service", service);
		editWin = (Window) Executions.createComponents(formZul, null, arg);
		editWin.doModal();
	}

	protected List<T> getFullList() {
		return fullList;
	}

	protected void setFullFill(boolean fullFill) {
		this.fullFill = fullFill;
	}
	
	public boolean isRecordSelected()
	{
		return (dataBean != null);
	}

	public boolean isAbleToDelete()
	{
		if (isSeasonFilter()
				&&(!seasonService.getActive().equals(selSeason))) {
			return false;
		}
		return this.isRecordSelected();
	}
	
	public boolean isAbleToAdd()
	{
		if (isSeasonFilter()
				&&(!seasonService.getActive().equals(selSeason))) {
			return false;
		}
		return true;
	}

	public List<Season> getSeasons() {
		return seasons;
	}

	public Season getSelSeason() {
		return selSeason;
	}

	public void setSelSeason(Season selSeason) {
		this.selSeason = selSeason;
	}

	private void setHqlFilter() {
		if (selSeason == null) {
			hqlFilter.reset();
		} else {
			hqlFilter.setWhere("SEASON_ID = :season");
			hqlFilter.setParam("season", selSeason.getId());
			hqlFilter.setDataClass(SeasonsAware.class);
		}
	}

	@Command
	@NotifyChange({"dataList", "ableToAdd", "selIndex", "dataBean", "ableToDelete"})
	public void filterSeason() {
		setHqlFilter();
		dataList.clear();
		loadFromDbSync();
		dataBean = null;
		selIndex = -1;
	}

	public boolean isSeasonFilter() {
		for (Class<?> clazz : service.getEntityClasses()) {
			if (SeasonsAware.class.isAssignableFrom(clazz)) {
				return true;
			}
		}

		return false;
	}
}
