/**
 * 
 */
package eu.dnetlib.data.collective.manager.ui;

import java.io.IOException;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Random;

import javax.annotation.Resource;

import org.apache.commons.lang.math.NumberUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.zkforge.timeline.Bandinfo;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.DesktopUnavailableException;
import org.zkoss.zk.ui.Executions;
import org.zkoss.zk.ui.event.CheckEvent;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.InputEvent;
import org.zkoss.zk.ui.event.SelectEvent;
import org.zkoss.zk.ui.util.GenericForwardComposer;
import org.zkoss.zul.Button;
import org.zkoss.zul.Checkbox;
import org.zkoss.zul.Label;
import org.zkoss.zul.ListModelList;
import org.zkoss.zul.Listbox;
import org.zkoss.zul.Menuitem;
import org.zkoss.zul.Messagebox;
import org.zkoss.zul.Progressmeter;
import org.zkoss.zul.Tab;
import org.zkoss.zul.Textbox;
import org.zkoss.zul.Window;

import eu.dnetlib.common.interfaces.nh.IBlackboardMessage.Action;
import eu.dnetlib.data.collective.manager.IInstanceService;
import eu.dnetlib.data.collective.manager.nh.JobInfo;
import eu.dnetlib.data.collective.manager.nh.ServiceActivity;
import eu.dnetlib.data.collective.manager.nh.JobInfo.JOBSTATUS;
import eu.dnetlib.data.collective.manager.profile.AbstractInstance;
import eu.dnetlib.data.collective.manager.scheduling.profile.CronScheduling;
import eu.dnetlib.data.collective.manager.scheduling.profile.EventScheduling;

/**
 * The AbstractUiController class is an extended Composer class used to register onXxx events common for manager classes, e.g. for scheduling configuration 
 * @author jochen
 * @param <I> - the instance type
 * @param <S> - the instance service type
 *
 */
public abstract class AbstractUiController<I extends AbstractInstance, S extends IInstanceService> extends GenericForwardComposer{

	/**
	 * GenericForwardComposer implements Serializable interface
	 */
	private static final long serialVersionUID = 6710604221111669678L;
	protected static final String key_backlink = "backlink";
	protected static final String key_id = "id";
	private static final Log log = LogFactory.getLog(AbstractUiController.class);
	
	protected I instance;
	protected S instanceService;
	@Resource(name="uiContent")
	protected UIContentImpl uiContent;
	
	protected String serviceName = "undefined";
	
	protected boolean isChanged = false;

	protected Window mywin;
	@SuppressWarnings("rawtypes")
	protected Map paramMap;
	protected ListModelList cronDayModelList = new ListModelList();
	protected ListModelList cronHourModelList = new ListModelList();
	protected ListModelList intervalFactorModelList = new ListModelList();
	protected ListModelList intervalTypeModelList = new ListModelList();
	
	protected Listbox cronHour;
	protected Listbox cronDay;
	protected Listbox intervalFactor;
	protected Listbox intervalType;
	protected Textbox eventTopic;
	protected Textbox eventXpath;
	
	protected Textbox commentBox;
	
	protected Label jobStatus;
	protected Label lastUpdate; 
	protected Label lastNumOfRecords; 
	protected Label lastErrorMsg; 
	
	protected Label jobStartTime;
	protected Label jobUpdateTime;
	protected Label jobError;
	protected Label jobExpectedRecords;
	protected Label jobCurrentRecords;
	
	protected Label instanceId; 
	
	protected Progressmeter progressmeter;

	Checkbox toggleGlobalScheduling;
	
	Checkbox toggleCronScheduling;
	Checkbox toggleEventScheduling;
	Bandinfo logBandInfoWeek;
	Bandinfo logBandInfoMonth;
	//protected Menuitem startJob; // TODO: uncomment when harv and trans ui dev are in sync, 09062011, JS
	protected Button startJob; // TODO: comment when harv and trans ui dev are in sync, 09062011, JS
	protected Tab tabCurrentJob;
	
	protected String styleSrc;
	
	protected String headerLeftLogoTitle;
	protected String headerLeftLogoUrl;
	protected String headerRightLogoTitle;
	protected String headerRightLogoUrl;
	protected String headerProjectName;
	protected String headerServiceName;
	protected String footerContactUrl;
	protected String footerCopyrightName;
	protected String favIcon;


	/**
	 * @see org.zkoss.zk.ui.util.GenericForwardComposer#doAfterCompose(org.zkoss.zk.ui.Component)
	 */
	@Override
	public void doAfterCompose(Component comp) throws Exception {
		super.doAfterCompose(comp);
		if (!desktop.isServerPushEnabled()){
			desktop.enableServerPush(true);
		}
		if (this.progressmeter != null){
			this.progressmeter.setValue(0);			
		}
	}
	
	/**
	 * 
	 */
	protected void initLogRendering(){
		//logModel = uiContent.getModelProxy(Listmodels.LOG.name(), instance.getResourceId(), desktop);
		//logRenderer = new LogRenderer();
		//logGrid.setModel(logModel);
		//logGrid.setRowRenderer(logRenderer);		
		// TODO logInfoService must be initialized
		//List<LogInfo> logInfos = logInfoService.findAll(instance.getResourceId());
		//uiContent.setObjects(Listmodels.LOG.name(), instance.getResourceId(), logInfos);
		LogInfoBandScrollEventListener eventListener = new LogInfoBandScrollEventListener();
		this.logBandInfoWeek.setModel(new ListModelList());
		this.logBandInfoWeek.setShowBubble(true);
		eventListener.setListModel(logBandInfoWeek.getModel());
		eventListener.setInstanceId(instance.getResourceId());
		eventListener.setLogInfoService(this.instanceService.getLogInfoService());
		this.logBandInfoWeek.addEventListener("onBandScroll", eventListener);
		this.logBandInfoMonth.setSyncWith(this.logBandInfoWeek.getId());
	}
	
	/**
	 * 
	 */
	protected void initSchedulingValues(){
		this.toggleGlobalScheduling.setChecked(this.instanceService.isGlobalSchedulingEnabled());
		
		cronDayModelList.clear();
		cronDayModelList.addAll(UiConstants.getSchedulingCronDays());
		cronDay.setModel(cronDayModelList);
		
		cronHourModelList.clear();
		cronHourModelList.addAll(UiConstants.getSchedulingCronHours());
		cronHour.setModel(cronHourModelList);

		intervalFactorModelList.clear();
		intervalFactorModelList.addAll(UiConstants.getSchedulingIntervalFactors());
		intervalFactor.setModel(intervalFactorModelList);
		
		intervalTypeModelList.clear();
		intervalTypeModelList.addAll(UiConstants.getSchedulingIntervalTypes());
		intervalType.setModel(intervalTypeModelList);

		if (!instance.getScheduling().isCronSchedulingConfigured()){
			cronDay.setDisabled(true);
			cronHour.setDisabled(true);
			intervalFactor.setDisabled(true);
			intervalType.setDisabled(true);
			toggleCronScheduling.setChecked(false);
		}else{
			cronDay.setDisabled(false);
			cronHour.setDisabled(false);
			intervalFactor.setDisabled(false);
			intervalType.setDisabled(false);
			toggleCronScheduling.setChecked(true);

			
			cronDay.setSelectedIndex(UiConstants.indexOfSchedulingCronDay(instance.getScheduling().getCronScheduling().getCronDay()));
			cronHour.setSelectedIndex(UiConstants.indexOfSchedulingCronHour(instance.getScheduling().getCronScheduling().getCronHour()));
			intervalFactor.setSelectedIndex(UiConstants.indexOfSchedulingIntervalFactor(instance.getScheduling().getCronScheduling().getIntervalFactor()));
			
			String it = instance.getScheduling().getCronScheduling().getIntervalType();
			
			if (UiConstants.containsSchedulingIntervalType(it)){
				intervalType.setSelectedIndex(UiConstants.indexOfSchedulingIntervalType(it));			
			}
		}
		
		if (instance.getScheduling().isEventSchedulingConfigured()){
			eventTopic.setDisabled(false);
			eventTopic.setValue(instance.getScheduling().getEventScheduling().getTopic());
			eventXpath.setDisabled(false);
			eventXpath.setValue(instance.getScheduling().getEventScheduling().getXpath());
			toggleEventScheduling.setChecked(true);
		}else{
			eventTopic.setDisabled(true);
			eventTopic.setValue("");
			eventXpath.setDisabled(true);
			eventXpath.setValue("");
			toggleEventScheduling.setChecked(false);
		}

	}

	/**
	 * @param aInstance
	 */
	protected void setJobStatus(I aInstance){
		if (aInstance.isOngoing()){
			jobStatus.setValue(JOBSTATUS.ONGOING.name());
			jobStatus.setStyle(UiConstants.getJobStatusColor(JOBSTATUS.ONGOING));
		}else if (aInstance.getLastUpdateErrorMessage().trim().length() > 0 && !aInstance.getLastUpdateErrorMessage().startsWith("noRecordsMatch")){
			jobStatus.setValue(JOBSTATUS.FAILED.name());
			jobStatus.setStyle(UiConstants.getJobStatusColor(JOBSTATUS.FAILED));
		}else{
			jobStatus.setValue(JOBSTATUS.IDLE.name());
			jobStatus.setStyle(UiConstants.getJobStatusColor(JOBSTATUS.IDLE));			
		}		
	}
	
	/**
	 * gets the profile-id of the instance used in this manager desktop
	 * @return instance-profile-id
	 */
	public String getId(){
		return instance.getResourceId();
	}

	/**
	 * @return
	 */
	public UIContentImpl getUiContent() {
		return uiContent;
	}

	/**
	 * @param uiContent
	 */
	public void setUiContent(UIContentImpl uiContent) {
		this.uiContent = uiContent;
	}
	
	/**
	 * @return
	 */
	public S getInstanceService() {
		return instanceService;
	}

	/**
	 * @param instanceService
	 */
	public void setInstanceService(S instanceService) {
		this.instanceService = instanceService;
	}
	
	/**
	 * sets the resp. hour for the cron job
	 * @param e - a select event
	 */
	public void onSelect$cronHour(SelectEvent e){
		int index = cronHour.getSelectedIndex();
		String selected = (String) cronHourModelList.get(index);
		CronScheduling cs = instance.getScheduling().getCronScheduling();
		cs.setCronHour(UiConstants.getValueOfSchedulingCronHour(selected));
		instance.getScheduling().setCronScheduling(cs);
		isChanged = true;
	}
	
	/**
	 * sets the resp. day for the cron job
	 * @param e - a select event
	 */
	public void onSelect$cronDay(SelectEvent e){
		int index = cronDay.getSelectedIndex();
		String selected = (String) cronDayModelList.get(index);			
		CronScheduling cs = instance.getScheduling().getCronScheduling();
		cs.setCronDay(UiConstants.getValueOfSchedulingCronDay(selected));
		instance.getScheduling().setCronScheduling(cs);
		isChanged = true;
	}
	
	/**
	 * sets the resp. interval type for the cron job, like hourly, daily, weekly
	 * @param e - a select event
	 */
	public void onSelect$intervalType(SelectEvent e){
		int index = intervalType.getSelectedIndex();
		String selectedIntervalType = (String) intervalTypeModelList.get(index);
		CronScheduling cs = instance.getScheduling().getCronScheduling();
		cs.setIntervalType(UiConstants.getValueOfSchedulingIntervalType(selectedIntervalType));
		instance.getScheduling().setCronScheduling(cs);
		isChanged = true;
	}
	
	/**
	 * sets the resp. interval factor for the cron job
	 * @param e - a select event
	 */
	public void onSelect$intervalFactor(SelectEvent e){		
		int index = intervalFactor.getSelectedIndex();
		String selectedIntervalFactor = (String) intervalFactorModelList.get(index);
		CronScheduling cs = instance.getScheduling().getCronScheduling();
		cs.setIntervalFactor(selectedIntervalFactor);
		instance.getScheduling().setCronScheduling(cs);
		isChanged = true;
	}
	
	/**
	 * sets the resp. topic for event based scheduling
	 * @param e - an input event
	 */
	public void onChange$eventTopic(InputEvent e){
		EventScheduling es = instance.getScheduling().getEventScheduling();
		es.setTopic(e.getValue());
		instance.getScheduling().setEventScheduling(es);
		isChanged = true;
	}
	
	/**
	 * sets the resp. xpath expr. for event based scheduling
	 * @param e - an input event
	 */
	public void onChange$eventXpath(InputEvent e){
		EventScheduling es = instance.getScheduling().getEventScheduling();
		es.setXpath(e.getValue());
		instance.getScheduling().setEventScheduling(es);
		isChanged = true;
	}

	/**
	 * enables or disables the scheduling of jobs for all instances globally
	 * @param e - a check event
	 */
	public void onCheck$toggleGlobalScheduling(CheckEvent e){
		this.instanceService.enableGlobalScheduling(e.isChecked());
	}
	
	/**
	 * if the checkbox in the ui is enabled a new cron scheduling is initialized with randomly generated parameters for second, minute and hour
	 * @param e - a check event
	 */
	public void onCheck$toggleCronScheduling(CheckEvent e){
		isChanged = true;
		if (e.isChecked()){
			Random randomGenerator = new Random();
			instance.getScheduling().setCronScheduling(new CronScheduling(randomGenerator.nextInt(60) + " " + randomGenerator.nextInt(60) + " " + randomGenerator.nextInt(24) + " ? * *", "0"));
		}else{
			instance.getScheduling().setCronScheduling(null);
		}
		initSchedulingValues();
	}
	
	/**
	 * if the checkbox in the ui is enabled a new event scheduling is initialized
	 * @param e - a check event
	 */
	public void onCheck$toggleEventScheduling(CheckEvent e){
		isChanged = true;
		if (e.isChecked()){
			instance.getScheduling().setEventScheduling(new EventScheduling("", ""));
		}else{
			instance.getScheduling().setEventScheduling(null);
		}
		initSchedulingValues();
	}
	
	/**
	 * save the text in the comment box if button clicked
	 * @param e - an event
	 */
	public void onClick$saveComment(Event e){
		
		Properties infoProps;
		try {
			infoProps = instanceService.getGenericInfoService().findProperties(getId());
			infoProps.setProperty("comment", commentBox.getValue());
			instanceService.getGenericInfoService().persist(getId(), infoProps);
			// TODO update desktops
		} catch (IOException e1) {
			// TODO display error message
		}
	}
	
	/**
	 * trigger action to empty the data sink if resp. button is clicked
	 * @param e - an event
	 */
	public void onClick$emptyDataSink(Event e){
		try {
			Messagebox.show("Do you really want to empty the Data Sink ?", "Empty Data Sink ?", Messagebox.YES | Messagebox.NO, Messagebox.QUESTION, new EventListener() {
				
				@Override
				public void onEvent(Event arg0) throws Exception {
					if ( ((Integer)arg0.getData()).intValue() == Messagebox.YES){
						instanceService.triggerJob(instance.getResourceId(), Action.EMPTY);
						startJob.setDisabled(true);
						// TODO
						jobStatus.setValue(JOBSTATUS.STARTING.name());
						jobStatus.setStyle(UiConstants.getJobStatusColor(JOBSTATUS.STARTING));
						JobInfo jb = new JobInfo();
						jb.setCurrentNumberOfRecords("0");
						//jb.setElapsedTime("0");
						jb.setEstimatedNumberOfRecords("0");
						jb.setJobStatus(JOBSTATUS.STARTING);
						jb.setActive(true);
						// update the jobtab using the original instance (not the clone)
						try{
							updateJobTab(jb, instanceService.getOriginalInstance(getId()));
						} catch (InstanceNotFoundException e1) {
							throw new IllegalArgumentException(e1);
						}
					}
				}
			});
		} catch (InterruptedException e2) {
			throw new IllegalArgumentException(e2);
		}
	}
	
	/**
	 * if the cancel button is clicked in the ui a new job is fired for canceling 
	 * @param e - an event
	 */
	public void onClick$cancelJob(Event e){
		instanceService.triggerJob(instance.getResourceId(), Action.CANCEL);
		jobStatus.setValue(JOBSTATUS.CANCELLING.name());
		jobStatus.setStyle(UiConstants.getJobStatusColor(JOBSTATUS.CANCELLING));
	}
	
	/**
	 * will fire a new job using the instance-parameters
	 * @param e Event
	 */
	public abstract void onClick$startJob(Event e);
		
	/**
	 * will perform an update of the original instance with this copy
	 * @param e Event
	 */
	public abstract void onClick$updateInstance(Event e);
	
	/**
	 * update the desktops with information from the given service activiy.
	 * @param aServiceActivity
	 * @return true if the resp. desktop is alive, false else
	 */
	public boolean updateUI(ServiceActivity aServiceActivity){
		boolean isAlive = false;
		JobInfo aJobInfo = aServiceActivity.getJobInfo();
		try {
			AbstractInstance origInstance = this.getInstanceService().getOriginalInstance(getId());
			if (!origInstance.isOngoing()){
				instance.setLastUpdateAsDate(origInstance.getLastUpdateAsDate());
				instance.setLastUpdateErrorMessage(origInstance.getLastUpdateErrorMessage());
				instance.setLastUpdateStatus(origInstance.getLastUpdateStatus());
			}
			if (desktop != null){
				if (desktop.isAlive()){
					try{
						Executions.activate(desktop,2000);  // activate desktop, set timeout 2 sec
						// update the jobtab using the original instance (not the clone)
						updateJobTab(aJobInfo, origInstance);						
						isAlive = true;
					}catch(DesktopUnavailableException e){
						log.warn("desktop unavailable for instance: " + getId());
					}finally{
						Executions.deactivate(desktop);						
					}					
				}
			}
		} catch (InterruptedException e) {
			log.warn("updateUi interrupted for instance: " + getId());
		} catch (InstanceNotFoundException e) {
			log.warn("updateUi instance not found: " + getId());
		}
		return isAlive;

	}
	
	/**
	 * update UI elements with information of a current running job
	 * @param aJobInfo
	 * @param aInstance
	 */
	protected void updateJobTab(JobInfo aJobInfo, AbstractInstance aInstance){
		startJob.setDisabled(aJobInfo.isActive());
		
		tabCurrentJob.setDisabled(!aJobInfo.isActive());
		
		jobStartTime.setValue(aJobInfo.getStartDate());
		jobUpdateTime.setValue(aJobInfo.getLastUpdate());
		jobError.setValue(aJobInfo.getError());
		if (!NumberUtils.isNumber(aJobInfo.getCurrentNumberOfRecords())){
			jobCurrentRecords.setValue("0");
		}else{
			jobCurrentRecords.setValue(aJobInfo.getCurrentNumberOfRecords());			
		}
		jobExpectedRecords.setValue(aJobInfo.getEstimatedNumberOfRecords());
		jobStatus.setValue(aJobInfo.getJobStatus().name());
		jobStatus.setStyle(UiConstants.getJobStatusColor(aJobInfo.getJobStatus()));
		lastUpdate.setValue(aInstance.getLastUpdateDate());
		lastErrorMsg.setValue(aInstance.getLastUpdateErrorMessage());
		lastNumOfRecords.setValue(aInstance.getLastUpdateStatus());
		
		if (NumberUtils.isNumber(aJobInfo.getEstimatedNumberOfRecords()) && NumberUtils.isNumber(aJobInfo.getCurrentNumberOfRecords())){
			BigDecimal current = new BigDecimal(aJobInfo.getCurrentNumberOfRecords());
			BigDecimal max = new BigDecimal(aJobInfo.getEstimatedNumberOfRecords());
			if (max.intValue() > 0 && max.intValue() >= current.intValue()){
				progressmeter.setValue(current.divide(max, 2, BigDecimal.ROUND_HALF_UP).multiply(new BigDecimal("100")).intValue());				
			}
		}
	}
	
	/**
	 * consumes information about service activites of the assigned instance and updates ui elements accordingly
	 */
	protected void updateByServiceActivity(){
		List<ServiceActivity> serviceActivities = this.instanceService.getServiceActivityConsumer().getServiceActivities(this.instance.getResourceId());
		for (ServiceActivity serviceActivity: serviceActivities){
			if (serviceActivity.getJobInfo().getJobStatus() == JOBSTATUS.CANCELLING){
				updateJobTab(serviceActivity.getJobInfo(), this.instance);
				break;
			}else if (serviceActivity.getJobInfo().getJobStatus() == JOBSTATUS.ONGOING || serviceActivity.getJobInfo().getJobStatus() == JOBSTATUS.STARTING){
				updateJobTab(serviceActivity.getJobInfo(), this.instance);
			}
		}

	}

	/**
	 * @return the serviceName
	 */
	public String getServiceName() {
		return serviceName;
	}

	/**
	 * @param serviceName the serviceName to set
	 */
	public void setServiceName(String serviceName) {
		this.serviceName = serviceName;
	}

	/**
	 * @return the styleSrc
	 */
	public String getStyleSrc() {
		return styleSrc;
	}

	/**
	 * @param styleSrc the styleSrc to set
	 */
	public void setStyleSrc(String styleSrc) {
		this.styleSrc = styleSrc;
	}

	/**
	 * @return the headerLeftLogoTitle
	 */
	public String getHeaderLeftLogoTitle() {
		return headerLeftLogoTitle;
	}

	/**
	 * @param headerLeftLogoTitle the headerLeftLogoTitle to set
	 */
	public void setHeaderLeftLogoTitle(String headerLeftLogoTitle) {
		this.headerLeftLogoTitle = headerLeftLogoTitle;
	}

	/**
	 * @return the headerLeftLogoUrl
	 */
	public String getHeaderLeftLogoUrl() {
		return headerLeftLogoUrl;
	}

	/**
	 * @param headerLeftLogoUrl the headerLeftLogoUrl to set
	 */
	public void setHeaderLeftLogoUrl(String headerLeftLogoUrl) {
		this.headerLeftLogoUrl = headerLeftLogoUrl;
	}

	/**
	 * @return the headerRightLogoTitle
	 */
	public String getHeaderRightLogoTitle() {
		return headerRightLogoTitle;
	}

	/**
	 * @param headerRightLogoTitle the headerRightLogoTitle to set
	 */
	public void setHeaderRightLogoTitle(String headerRightLogoTitle) {
		this.headerRightLogoTitle = headerRightLogoTitle;
	}

	/**
	 * @return the headerRightLogoUrl
	 */
	public String getHeaderRightLogoUrl() {
		return headerRightLogoUrl;
	}

	/**
	 * @param headerRightLogoUrl the headerRightLogoUrl to set
	 */
	public void setHeaderRightLogoUrl(String headerRightLogoUrl) {
		this.headerRightLogoUrl = headerRightLogoUrl;
	}

	/**
	 * @return the headerProjectName
	 */
	public String getHeaderProjectName() {
		return headerProjectName;
	}

	/**
	 * @param headerProjectName the headerProjectName to set
	 */
	public void setHeaderProjectName(String headerProjectName) {
		this.headerProjectName = headerProjectName;
	}

	/**
	 * @return the headerServiceName
	 */
	public String getHeaderServiceName() {
		return headerServiceName;
	}

	/**
	 * @param headerServiceName the headerServiceName to set
	 */
	public void setHeaderServiceName(String headerServiceName) {
		this.headerServiceName = headerServiceName;
	}

	/**
	 * @return the footerContactUrl
	 */
	public String getFooterContactUrl() {
		return footerContactUrl;
	}

	/**
	 * @param footerContactUrl the footerContactUrl to set
	 */
	public void setFooterContactUrl(String footerContactUrl) {
		this.footerContactUrl = footerContactUrl;
	}

	/**
	 * @return the footerCopyrightName
	 */
	public String getFooterCopyrightName() {
		return footerCopyrightName;
	}

	/**
	 * @param footerCopyrightName the footerCopyrightName to set
	 */
	public void setFooterCopyrightName(String footerCopyrightName) {
		this.footerCopyrightName = footerCopyrightName;
	}
	
	/**
	 * @return the favicon
	 */
	public String getFavIcon() {
		return favIcon;
	}

	/**
	 * @param favicon the favicon to set
	 */
	public void setFavIcon(String favicon) {
		this.favIcon = favicon;
	}

}
