package org.orienteer.bpm.camunda.handler;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

import org.apache.wicket.util.string.Strings;
import org.camunda.bpm.engine.impl.db.ListQueryParameterObject;
import org.camunda.bpm.engine.impl.persistence.entity.JobEntity;
import org.camunda.bpm.engine.impl.persistence.entity.MessageEntity;
import org.camunda.bpm.engine.impl.persistence.entity.TimerEntity;
import org.camunda.bpm.engine.runtime.JobQuery;
import org.orienteer.bpm.camunda.OPersistenceSession;
import org.orienteer.bpm.camunda.handler.history.UserOperationLogEntryEventEntityHandler;
import org.orienteer.core.OClassDomain;
import org.orienteer.core.util.OSchemaHelper;

import com.github.raymanrt.orientqb.query.Clause;
import com.github.raymanrt.orientqb.query.Operator;
import com.github.raymanrt.orientqb.query.Parameter;
import com.github.raymanrt.orientqb.query.Query;
import com.orientechnologies.orient.core.metadata.schema.OType;
import com.orientechnologies.orient.core.record.impl.ODocument;

/**
 * {@link IEntityHandler} for {@link JobEntity}
 */
public class JobEntityHandler extends AbstractEntityHandler<JobEntity> {

	public static final String OCLASS_NAME = "BPMJob";
	
	public JobEntityHandler() {
		super(OCLASS_NAME);
	}
	
	@Override
	public void applySchema(OSchemaHelper helper) {
		super.applySchema(helper);
		helper.domain(OClassDomain.SYSTEM);
		helper.oProperty("type", OType.STRING, 10)
			  .oProperty("duedate", OType.DATETIME, 20)
			  .oProperty("lockExpirationTime", OType.DATETIME, 30)
			  .oProperty("lockOwner", OType.STRING, 40)
			  .oProperty("exclusive", OType.BOOLEAN, 50)
			  .oProperty("execution", OType.LINK, 60).assignVisualization("listbox")
			  .oProperty("processInstanceId", OType.STRING, 70)
			  .oProperty("processDefinition", OType.LINK, 90).assignVisualization("listbox")
			  .oProperty("processDefinitionKey", OType.STRING, 100)
			  .oProperty("retries", OType.INTEGER, 110)
			  .oProperty("exceptionByteArrayId", OType.STRING, 120)
			  .oProperty("exceptionMessage", OType.STRING, 130)
			  .oProperty("repeat", OType.STRING, 140)
			  .oProperty("jobHandlerType", OType.STRING, 150)
			  .oProperty("JobHandlerConfigurationRaw", OType.STRING, 160)
			  .oProperty("deployment", OType.LINK, 170)
			  .oProperty("suspensionState", OType.INTEGER, 180)
			  .oProperty("jobDefinitionId", OType.STRING, 190)
			  .oProperty("sequenceCounter", OType.LONG, 200)
			  .oProperty("priority", OType.LONG, 210)
			  .oProperty("userOperationLogEntryEvents", OType.LINKLIST, 220).assignVisualization("table");
	}

	@Override
	public void applyRelationships(OSchemaHelper helper) {
		super.applyRelationships(helper);
		helper.setupRelationship(JobEntityHandler.OCLASS_NAME, "deployment", DeploymentEntityHandler.OCLASS_NAME);
		helper.setupRelationship(JobEntityHandler.OCLASS_NAME, "processDefinition", ProcessDefinitionEntityHandler.OCLASS_NAME);
		helper.setupRelationship(JobEntityHandler.OCLASS_NAME, "execution", ExecutionEntityHandler.OCLASS_NAME);
		helper.setupRelationship(OCLASS_NAME, "userOperationLogEntryEvents", UserOperationLogEntryEventEntityHandler.OCLASS_NAME, "job");
	}

	@Override
	public JobEntity mapToEntity(ODocument doc, JobEntity entity, OPersistenceSession session) {
		if(entity==null) {
			String type = doc.field("type");
			if(TimerEntity.TYPE.equals(type)) entity = new TimerEntity();
			else if(MessageEntity.TYPE.equals(type)) entity = new MessageEntity();
		}
		entity =  super.mapToEntity(doc, entity, session);
		String exceptionByteArrayId = doc.field("exceptionByteArrayId");
		//JobEntity doesn't allow to set exceptionByteArrayId: so lets inject it.
		//TODO: Fix this behavior
		if(exceptionByteArrayId!=null) {
			try {
				Field field = JobEntity.class.getDeclaredField("exceptionByteArrayId");
				field.setAccessible(true);
				field.set(entity, exceptionByteArrayId);
			} catch (Exception e) {
				logger.warn("Setting exceptionByteArrayId doesn't work", e);
			}
		}
		return entity;
	}
	
	@Statement
	public List<JobEntity> selectJobByQueryCriteria(OPersistenceSession session, JobQuery query) {
		return  query(session, query);
	}
	
	@Statement
	public List<JobEntity> selectJobsByConfiguration(OPersistenceSession session, ListQueryParameterObject query) {
		Map<String, Object> params = (Map<String, Object>) query.getParameter(); 
	    String config = (String) params.get("handlerConfiguration");
	    String followUpConfig = (String) params.get("handlerConfigurationWithFollowUpJobCreatedProperty");
	    String type = (String) params.get("handlerType");
	    List<String> args = new ArrayList<>();
	    Query q = new Query().from(getSchemaClass());
	    q.where(Clause.clause("jobHandlerType", Operator.EQ, Parameter.PARAMETER));
	    args.add(type);
	    Clause eqConfig = Clause.clause("JobHandlerConfigurationRaw", Operator.EQ, Parameter.PARAMETER);
	    if(Strings.isEmpty(followUpConfig)) {
	    	q.where(eqConfig);
	    	args.add(config);
	    } else {
	    	q.where(Clause.or(eqConfig, eqConfig));
	    	args.add(config); 
	    	args.add(followUpConfig);
	    }
	    return queryList(session, q.toString(), args.toArray());
	}
	
	@Statement
	public List<JobEntity> selectExclusiveJobsToExecute(OPersistenceSession session, ListQueryParameterObject query) {
		Map<String, Object> params = (Map<String, Object>) query.getParameter();
	    Date now=(Date) params.get("now");
	    int maxResults=query.getMaxResults();
	    return queryList(session, "select from "+getSchemaClass()+
	    					" where retries > 0"
	    					+ " and (duedate is null or duedate <= ?)"
	    					+ " and (lockOwner is null or lockExpirationTime < ?)"
	    					+ " and exclusive = true"
	    					+ " and suspensionState = 1"
	    					+ " LIMIT ?", now, now, maxResults);
	}
	
	@Statement
	public List<JobEntity> selectNextJobsToExecute(OPersistenceSession session, ListQueryParameterObject query) {
		Map<String, Object> params = (Map<String, Object>) query.getParameter();
	    Date now=(Date) params.get("now");
	    int maxResults=query.getMaxResults();
	    String orderBy = query.getOrderBy();
	    return queryList(session, "select from "+getSchemaClass()+
	    					" where retries > 0"
	    					+ " and (duedate is null or duedate <= ?)"
	    					+ " and (lockOwner is null or lockExpirationTime < ?)"
	    					+ " and suspensionState = 1"
	    					+ (Strings.isEmpty(orderBy)?" order by "+orderBy:"")
	    					+ " LIMIT ?", now, now, maxResults);
	}
	
	@Statement
	public JobEntity selectJob(OPersistenceSession session, String id) {
		return read(id, session);
	}
	
	@Statement
	public List<JobEntity> selectJobsByExecutionId(OPersistenceSession session, ListQueryParameterObject query) {
		return queryList(session, "select from "+getSchemaClass()+" where execution.id = ?", query.getParameter());
	}
	
}