package eu.dnetlib.functionality.index.solr;

import java.io.IOException;
import java.util.UUID;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SimpleTrigger;
import org.springframework.beans.factory.annotation.Required;

import eu.dnetlib.data.index.IndexServiceException;
import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException;
import eu.dnetlib.enabling.tools.blackboard.AbstractBlackboardNotificationHandler;
import eu.dnetlib.enabling.tools.blackboard.BlackboardJob;
import eu.dnetlib.enabling.tools.blackboard.BlackboardServerHandler;
import eu.dnetlib.enabling.tools.blackboard.NotificationHandler;
import eu.dnetlib.functionality.index.solr.actors.BlackboardActorCallback;
import eu.dnetlib.functionality.index.solr.actors.ResultsetKeepAliveCallback;
import eu.dnetlib.functionality.index.solr.actors.ResultsetKeepAliveJob;

/**
 * SolrIndexNotificationHandler. Responsible for handle incoming notifications to the index service.
 * 
 * @author claudio
 *
 */
public class SolrIndexNotificationHandler 
	extends AbstractBlackboardNotificationHandler<BlackboardServerHandler> 
	implements NotificationHandler {

	/**
	 * logger.
	 */
	private static final Log log = LogFactory.getLog(SolrIndexNotificationHandler.class); //NOPMD

	/**
	 * index service.
	 */
	private SolrIndexServiceImpl indexService;
	
	/**
	 * ResultSet keep alive JobDetail.
	 */
	private transient JobDetail rsKeepaliveJob;
	
	/**
	 * Start deferred jobs, used to keep resultsets alive.
	 */
	private Scheduler jobScheduler;
	
	/**
	 * ResultSet keepAlive trigger's repeat delay.
	 */
	private static final long REPEAT_DELAY = 20000;
	
	@Override
	protected void processJob(final BlackboardJob job) {
		log.info("processing solr index job: " + job.getAction());
		
		final Action action = Action.valueOf(job.getAction());
		try {
			switch (action) {
			
			case CREATE: 
				performCreateIndex(job);
				break;
				
			case FEED:
				performFeedIndex(job);
				break;

			case DELETE:
				performDelete(job);
				break;
				
			case TRANSFORM:
				performTransform(job);
				break;				
				
			case IDENTIFY:
				job.getParameters().put(BBParam.SERVICE_ID, indexService.identify());
				break;
			
			default:
				job.setError("unsupported Action: " + action.name());
				log.warn("unsupported message action: " + action.name());
				throw new IllegalArgumentException("unsupported message action: " + action.name());				
			}
		} catch (Throwable e) {	 // NOPMD
			log.error(job.getAction() + " job set to FAILED ", e);
			getBlackboardHandler().failed(job, e);
		}
	}
	
	/**
	 * Helper method, handle the TRANFORM BB action.
	 * 
	 * @param job
	 * 			the BB job.
	 * @throws ISLookUpException
	 * 				could happen
	 * @throws IOException
	 * 				could happen
	 */
	private void performTransform(final BlackboardJob job) throws ISLookUpException, IOException {
		getBlackboardHandler().ongoing(job);
		log.info("TRANSFORM job set to ONGOING");

		indexService.mergeIndex(job, newBBActorCallback(job));
	}

	/**
	 * Helper method, handle the CRATE BB action.
	 * 
	 * @param job
	 * 			the BB job.
	 * @throws IndexServiceException
	 * 				could happen
	 */
	private void performCreateIndex(final BlackboardJob job) throws IndexServiceException {
		getBlackboardHandler().ongoing(job);
		log.info("CREATE job set to ONGOING");

		final String profileId = indexService.createIndex(job, newBBActorCallback(job));
		job.getParameters().put("id", profileId);
	}
	
	/**
	 * Helper method, handle the DELETE BB action.
	 * 
	 * @param job
	 * 			the BB job.
	 * @throws IndexServiceException
	 * 				could happen
	 */	
	private void performDelete(final BlackboardJob job) throws IndexServiceException {
		getBlackboardHandler().ongoing(job);
		log.info("DELETE job set to ONGOING");

		indexService.deleteIndex(job, newBBActorCallback(job));
	}
	
	/**
	 * Helper method, handle the FEED BB action.
	 * 
	 * @param job
	 * 			the BB job.
	 * @throws IndexServiceException
	 * 				could happen
	 * @throws	SchedulerException
	 * 				could happen
	 */	
	private void performFeedIndex(final BlackboardJob job) throws IndexServiceException, SchedulerException {
		
		getBlackboardHandler().ongoing(job);
		log.info("FEED job set to ONGOING");

		final JobDetail tmp = jobScheduler.getJobDetail(ResultsetKeepAliveJob.JOB_NAME, ResultsetKeepAliveJob.JOB_GROUP);
	
		if (tmp == null) {
			log.fatal("re-registering job detail");
			jobScheduler.addJob(rsKeepaliveJob, true);
		} 			
		final String epr = new String(Base64.decodeBase64(job.getParameters().get(BBParam.RS_EPR).getBytes()));
		final String triggerId = UUID.randomUUID().toString();
		
		log.debug("\n\n scheduling resultSet keepalive trigger: " + triggerId + "\n\n");
		jobScheduler.scheduleJob(getResultsetTrigger(epr, triggerId));
		
		indexService.feedIndex(job, newRSKeepAliveCallback(triggerId), newBBActorCallback(job));
	}

	/////////////////// helpers
	
	/**
	 * Constructor for a blackboard callback to handle the job termination.
	 * 
	 * @param job
	 * 			the BB job.
	 * @return
	 * 			a new eu.dnetlib.functionality.index.solr.actors.BlackboardActorCallback
	 */
	private BlackboardActorCallback newBBActorCallback(final BlackboardJob job) {
		return new BlackboardActorCallback() {
			@Override
			public void setJobDone() {
				log.info(job.getAction() + " job set to DONE");
				getBlackboardHandler().done(job);
			}
			@Override
			public void setJobFailed(final Throwable exception) {
				log.error(job.getAction() + " job set to FAILED ", exception);
				getBlackboardHandler().failed(job, exception);
			}
		};
	}
	
	/**
	 * Constructor for a resultSet keepAlive callbacks.
	 * 
	 * @param triggerId
	 * 			trigger identifier.
	 * @return
	 * 			a new eu.dnetlib.functionality.index.solr.actors.ResultsetKeepAliveCallback
	 */
	private ResultsetKeepAliveCallback newRSKeepAliveCallback(final String triggerId) {
		return new ResultsetKeepAliveCallback() {
			@Override
			public void unschedule() {
				try {
					log.info("\n\n unscheduling resultSet keepalive trigger: " + triggerId + "\n\n");
					jobScheduler.unscheduleJob(triggerId, ResultsetKeepAliveJob.JOB_GROUP);
				} catch (SchedulerException e) {
					log.warn("cannot unschedule RSKeepAlive triggerId: " + triggerId);
					throw new RuntimeException(e); //NOPMD
				}
			}
		};
	}
	
	/**
	 * Constructor for triggers used by the resultSet keepAlive job.
	 * 
	 * @param rsEpr
	 * 			resultSet epr to keep alive.
	 * @param triggerId
	 * 			trigger identifier.
	 * @return
	 * 			a new org.quartz.SimpleTrigger instance.
	 */
	private SimpleTrigger getResultsetTrigger(final String rsEpr, final String triggerId) {
		final SimpleTrigger trigger = new SimpleTrigger(
				triggerId,
				ResultsetKeepAliveJob.JOB_GROUP,
                SimpleTrigger.REPEAT_INDEFINITELY,
                REPEAT_DELAY);
		trigger.getJobDataMap().put(BBParam.RS_EPR, rsEpr);
		trigger.setJobName(ResultsetKeepAliveJob.JOB_NAME);
		trigger.setJobGroup(ResultsetKeepAliveJob.JOB_GROUP);
		return trigger;
	}

	@Required
	public void setIndexService(final SolrIndexServiceImpl indexService) {
		this.indexService = indexService;
	}
	
	public SolrIndexServiceImpl getIndexService() {
		return indexService;
	}

	@Required
	public void setJobScheduler(final Scheduler jobScheduler) {
		this.jobScheduler = jobScheduler;
	}

	public Scheduler getJobScheduler() {
		return jobScheduler;
	}

	@Required
	public void setResultsetKeepaliveJob(final JobDetail rsKeepaliveJob) {
		this.rsKeepaliveJob = rsKeepaliveJob;
	}

	public JobDetail getResultsetKeepaliveJob() {
		return rsKeepaliveJob;
	}

}
