/**
 * Copyright 2017 Institute of Computing Technology, Chinese Academy of Sciences.
 * Licensed under the terms of the Apache 2.0 license.
 * Please see LICENSE file in the project root for terms
 */
package eml.studio.server.rpc;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;

import eml.studio.client.rpc.JobService;
import eml.studio.shared.model.BdaJob;

import org.apache.oozie.client.OozieClientException;

import eml.studio.server.db.SecureDao;
import eml.studio.server.graph.OozieGraphXMLParser;
import eml.studio.server.oozie.instance.OozieInstance;
import eml.studio.server.util.GenerateSequenceUtil;
import eml.studio.server.util.HDFSIO;
import eml.studio.server.util.OozieUtil;
import eml.studio.server.util.TimeUtils;
import eml.studio.shared.graph.OozieGraph;
import eml.studio.shared.graph.OozieProgramNode;
import eml.studio.shared.oozie.OozieAction;
import eml.studio.shared.oozie.OozieJob;

import com.google.gwt.user.server.rpc.RemoteServiceServlet;

/**
 * Specific methods in Oozie Jobs' related RemoteServiceServlet
 */
public class JobServiceImpl extends RemoteServiceServlet implements JobService {

	private static final long serialVersionUID = 1L;

	private static Logger logger = Logger.getLogger(JobServiceImpl.class
			.getName());

	/**
	 * Get job's graphXML
	 * @param jobId target job id
	 * @return job graphXML string
	 */
	@Override
	public String getJobGraphXML(String jobId) {
		OozieJob job = new OozieJob();
		job.setId(jobId);
		try {
			OozieJob res = SecureDao.getObject(job);
			if (res != null) {
				return res.getGraphxml();
			}
			return null;
		} catch (Exception ex) {
			ex.printStackTrace();
		}
		return null;
	}

	/**
	 * Get BdaJob(all info) form mysql
	 * @param jobId target job id
	 * @return Bdajob object
	 */
	@Override
	public BdaJob getJob(String jobId) {

		try {
			BdaJob job = new BdaJob();
			job.setJobId(jobId);
			job = SecureDao.getObject(job);

			OozieJob entity = new OozieJob();
			entity.setId(job.getCurOozJobId());
			entity = SecureDao.getObject(entity);
			job.setCurOozJob(entity);

			logger.info("************* Parse Graph *****************");
			OozieGraph graph = OozieGraphXMLParser.parse(entity.getGraphxml());
			job.setOozieGraph(graph);
			logger.info(graph.toXML());
			logger.info("************* Parse Graph *****************");
			return job;
		} catch (Exception ex) {
			logger.warning(ex.getMessage());
		}
		return null;
	}

	/**
	 * Get a job status
	 * @param jobId target job id
	 * @return status string
	 */
	@Override
	public String getJobStatus(String jobId) {
		try {
			OozieJob job = OozieUtil.getJob(jobId);
			return job.getStatus();
		} catch (Exception ex) {
			ex.printStackTrace();
		}
		return null;
	}
	/**
	 * Kill a job
	 * @param jobId target job id
	 */
	@Override
	public void killJob(String jobId) {

		try {
			OozieUtil.kill(jobId);
		} catch (OozieClientException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	/**
	 * Suspend a job
	 * @param jobId target job id
	 */
	@Override
	public void suspendJob(String jobId) {
		try {
			OozieUtil.suspend(jobId);
		} catch (OozieClientException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	/**
	 * Resume a job
	 * @param jobId target job id
	 */
	@Override
	public void resumeJob(String jobId) {
		try {
			OozieUtil.resume(jobId);
		} catch (OozieClientException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	/**
	 * Start a job
	 * @param jobId target job id
	 */
	@Override
	public void startJob(String jobId) {
		try {
			OozieUtil.start(jobId);
		} catch (OozieClientException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Rerun a job
	 * @param jobId target job id
	 */
	@Override
	public void reRun(String jobId) {
		try {
			OozieUtil.reRun(jobId);
		} catch (OozieClientException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	/**
	 * Return the stdout of a action node
	 *
	 * @param jobId target job id
	 * @param actionId target action in this job
	 * @return stdout string
	 */
	@Override
	public String getStdOut(String jobId, String actionId) {

		String url;
		try {
			url = OozieUtil.getUrl(jobId) + actionId;
			String url_out = url + "/stdout";
			return HDFSIO.cat(url_out);

		} catch (OozieClientException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * Return the stderr of a action node
	 *
	 * @param jobId target job id
	 * @param actionId target action in this job
	 * @return error string
	 */
	@Override
	public String getStdErr(String jobId, String actionId) {
		try {
			String url = OozieUtil.getUrl(jobId) + actionId;
			String url_err = url + "/stderr";
			return HDFSIO.cat(url_err);
		} catch (Exception ex) {
			ex.printStackTrace();
		}
		return null;
	}

	/**
	 * List recent jobs belong to an user
	 * @param email target user
	 * @return list of jobs
	 */
	@Override
	public List<BdaJob> listRecentUserJobs(String email) {
		BdaJob entity = new BdaJob();
		entity.setAccount(email);

		try {
			List<BdaJob> list = SecureDao.listAll(entity,
					"order by last_submit_time desc limit 100");

			for (BdaJob temp : list) {
				syncDatabase(temp);
			}
			logger.info("recent user job size: " + list.size());
			return list;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * List recent exampleJobs
	 * @return the list of recent example jobs
	 */
	@Override
	public List<BdaJob> listRecentExampleJobs() {
		BdaJob entity = new BdaJob();
		entity.setIsExample(true);
		try {
			List<BdaJob> list = SecureDao.listAll(entity, "order by job_name");
			for (BdaJob temp : list) {
				syncDatabase(temp);
			}
			logger.info("recent example job size: " + list.size());
			return list;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;

	}

	/**
	 * Add a job to Example job list
	 * @param jobId target jobid
	 */
	@Override
	public void setExampleJobs(String jobId) {
		BdaJob example = new BdaJob();
		example.setJobId(jobId);
		example.setIsExample(true);
		String[] setFields = {"is_example"};
		String[] condFields = {"job_id"};
		try {
			SecureDao.update(example, setFields, condFields);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * List recent jobs
	 * @return recent jobs list
	 */
	@Override
	public List<BdaJob> listRecentJobs() {

		try {
			List<BdaJob> jobs = SecureDao.listAll(new BdaJob(),
					"order by last_submit_time desc limit 100");

			for (BdaJob temp : jobs) {
				syncDatabase(temp);
			}
			logger.info("recent job size: " + jobs.size());
			return jobs;
		} catch (Exception ex) {
			ex.printStackTrace();
		}
		return null;
	}

	/**
	 * Synchronize the status of the BdaJob in the EML and Oozie databases
	 * If the task status is non-SUCCEEDED, KILLED, FAILED,
	 * the state is obtained from the oozie database and the bda database
	 * will be updated and the job object information also will be updated
	 * 
	 * @param job
	 *            The incoming job object will be synchronized with
	 *            the oozie database after the method is executed successfully
	 */
	private void syncDatabase(BdaJob job) {

		try {
			OozieJob ooziejob = new OozieJob();
			ooziejob.setId(job.getCurOozJobId());
			ooziejob = SecureDao.getObject(ooziejob);

			job.setCurOozJob(ooziejob);

			if (ooziejob != null && ooziejob.getEndTime() == null) {
				OozieJob temp = OozieUtil.getJob(ooziejob.getId());
				ooziejob.setStatus(temp.getStatus());
				ooziejob.setEndTime(temp.getEndTime());
				ooziejob.setCreatedTime(temp.getCreatedTime());
				ooziejob.setActions(temp.getActions());
				String[] setFields = {"status", "endtime", "createtime"};
				String[] condFields = {"id"};
				SecureDao.update(ooziejob, setFields, condFields);

				// syc Action
				for (OozieAction action : temp.getActions()) {
					action.setJobId(ooziejob.getId());
					SecureDao.update(action, new String[]{"jobid",
							"ooziejobid", "name"});
				}

				for (OozieAction action : ooziejob.getActions()) {
					action.setAppPath(ooziejob.getAppPath());
				}
			}
		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}

	/**
	 * Synchronize the status of the OozieJob in the EML and Oozie databases
	 * If the task status is non-SUCCEEDED, KILLED, FAILED,
	 * the state is obtained from the oozie database and the bda database
	 * will be updated and the job object information also will be updated
	 * 
	 * @param job
	 *            The incoming job object will be synchronized with
	 *            the oozie database after the method is executed successfully
	 */
	private void syncDatabase(OozieJob oozieJob) {

		try {
			// If not for the end state
			if (oozieJob != null && oozieJob.getEndTime() == null) {
				// Get status from oozie database
				OozieJob temp = OozieUtil.getJob(oozieJob.getId());
				// Synchronize object information
				oozieJob.setStatus(temp.getStatus());
				oozieJob.setEndTime(temp.getEndTime());
				oozieJob.setCreatedTime(temp.getCreatedTime());
				oozieJob.setActions(temp.getActions());
				String[] setFields = {"status", "endtime", "createtime"};
				String[] condFields = {"id"};
				// Synchronize database information
				SecureDao.update(oozieJob, setFields, condFields);

				// Synchronize action information
				for (OozieAction action : temp.getActions()) {
					action.setJobId(oozieJob.getId());
					SecureDao.update(action, new String[]{"jobid",
							"ooziejobid", "name"});
				}

				for (OozieAction action : oozieJob.getActions()) {
					action.setAppPath(oozieJob.getAppPath());
				}
			}
		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}

	/**
	 * Update the status of a oozie job in database
	 *
	 * @param jobId jobid
	 */
	@Override
	public void updateJobStatus(String jobId) {
		try {
			OozieJob job = OozieUtil.getJob(jobId);
			String[] setFields = {"status", "endtime", "createtime"};
			String[] condFields = {"id"};
			SecureDao.update(job, setFields, condFields);
		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}

	/**
	 * Delete bdajob and related ooziejob and actions via job id
	 * @param bdaJobId
	 */
	@Override
	public void deleteJob(String bdaJobId) {
		BdaJob bdaJob = new BdaJob();
		bdaJob.setJobId(bdaJobId);
		try {
			SecureDao.delete(bdaJob);
			OozieJob query = new OozieJob();
			query.setJobid(bdaJobId);
			List<OozieJob> oozJobs = SecureDao.listAll(query);
			for (OozieJob oozJob : oozJobs) {
				deleteOozieJob(oozJob.getId());
			}

			// delete related OozieAction records
			OozieAction actionQuery = new OozieAction();
			actionQuery.setBdaJobId(bdaJobId);
			SecureDao.delete(actionQuery);

		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}

	/**
	 * Submit a bda job to Oozie
	 * @param bdaJobName
	 * @param bdaJobId
	 * @param graph
	 * @param account
	 * @param desc
	 * @return new EMLjob
	 */
	@Override
	public BdaJob submit(String bdaJobName, String bdaJobId, OozieGraph graph,
			String account, String desc) {
		try {
			BdaJob job = null;
			if (bdaJobId == null) {
				job = this.createBdaJob(bdaJobName, bdaJobId, graph, account,
						desc);
				bdaJobId = job.getJobId();
			} else {
				job = this.updateBdaJob(bdaJobName, bdaJobId, graph, account,
						desc);
			}

			OozieInstance instant = new OozieInstance(job);
			job = instant.exec();
			return job;
		} catch (Exception ex) {
			ex.printStackTrace();
		}
		return null;
	}

	/**
	 * Inquire OozieJob info via bdajobid,include related OozieActions
	 * 
	 * @param bdaJobId target job id
	 * @return newest oozieJob
	 */
	@Override
	public BdaJob getBdaJob(String bdaJobId) {
		try {
			BdaJob job = new BdaJob();
			job.setJobId(bdaJobId);
			job = SecureDao.getObject(job);
			syncDatabase(job);

			logger.info("query actions from bda database");
			OozieAction queryAction = new OozieAction();
			queryAction.setBdaJobId(bdaJobId);
			List<OozieAction> actions = SecureDao.listAll(queryAction);
			job.setActions(actions);

			return job;

		} catch (Exception ex) {
			ex.printStackTrace();
		}
		return null;
	}

	/**
	 * Synchronize a EML job with Oozie Job
	 *
	 * @param bdaJobId
	 * @return ooziejob object
	 */
	@Override
	public OozieJob synCurOozJob(String bdaJobId) {
		try {
			BdaJob job = new BdaJob();
			job.setJobId(bdaJobId);
			job = SecureDao.getObject(job);
			syncDatabase(job);

			return job.getCurOozJob();

		} catch (Exception ex) {
			ex.printStackTrace();
		}
		return null;
	}

	@Override
	public OozieAction getOozieAction(String oozieJobId, String actionName) {
		// TODO Auto-generated method stub
		try {
			OozieAction queryAction = new OozieAction();
			queryAction.setJobId(oozieJobId);
			queryAction.setName(actionName);
			List<OozieAction> actions = SecureDao.listAll(queryAction);
			return actions.get(0);
		} catch (Exception ex) {
			ex.printStackTrace();
		}
		return null;
	}

	@Override
	public OozieJob synCurOozJobByOozId(String oozieJobId) {
		try {
			OozieJob job = new OozieJob();
			job.setId(oozieJobId);
			job = SecureDao.getObject(job);
			// Synchronize with oozie database
			syncDatabase(job);

			return job;

		} catch (Exception ex) {
			ex.printStackTrace();
		}
		return null;
	}

	/**
	 * Create a EML job in database
	 * @param bdaJobName
	 * @param bdaJobId
	 * @param graph
	 * @param account
	 * @param desc
	 * @return
	 * @throws Exception
	 */
	private BdaJob createBdaJob(String bdaJobName, String bdaJobId,
			OozieGraph graph, String account, String desc) throws Exception {

		if (bdaJobName == null)
			bdaJobName = "Not Specified";
		if (bdaJobId == null)
			bdaJobId = GenerateSequenceUtil.generateSequenceNo() + "-bda";

		// Create the corresponding bda job in the database
		BdaJob job = new BdaJob();
		job.setJobName(bdaJobName);
		job.setJobId(bdaJobId);
		job.setGraphxml(graph.toXML());
		job.setAccount(account);
		job.setDesc(desc);
		job.setLastSubmitTime(TimeUtils.getTime());

		//Modify the programs in the graph xml of the bda job to latest
		OozieGraph tmpGraph = OozieGraphXMLParser.parse(graph.toXML());
		LinkedList<OozieProgramNode> proNodes =  tmpGraph.getProgramNodes();
		for(int i=0 ; i < proNodes.size() ; i++)
		{
			OozieProgramNode proNode = proNodes.get(i);
			proNode.setOozJobId("latest");
			proNode.setWorkPath("${appPath}/" + proNode.getId() + "/");
			proNodes.set(i, proNode	);
		}
		job.setGraphxml(tmpGraph.toXML());
		SecureDao.insert(job);

		job.setOozieGraph(graph);
		return job;
	}

	/**
	 * Update a EML job in the database
	 * @param bdaJobName
	 * @param bdaJobId
	 * @param graph
	 * @param account
	 * @param desc
	 * @return Bda job object
	 * @throws Exception
	 */
	private BdaJob updateBdaJob(String bdaJobName, String bdaJobId,
			OozieGraph graph, String account, String desc) throws Exception {

		if (bdaJobName == null)
			bdaJobName = "Not Specified";
		BdaJob job = new BdaJob();
		job.setJobName(bdaJobName);
		job.setJobId(bdaJobId);
		job.setGraphxml(graph.toXML());
		job.setAccount(account);
		job.setDesc(desc);
		job.setLastSubmitTime(TimeUtils.getTime());

		String[] cond = {"job_id"};
		String[] sets = {"job_name", "graphxml", "account", "description",
		"last_submit_time"};
		
		//Modify the programs in the graph xml of the bda job to latest
	    OozieGraph tmpGraph = OozieGraphXMLParser.parse(graph.toXML());
	    LinkedList<OozieProgramNode> proNodes =  tmpGraph.getProgramNodes();
	    for(int i=0 ; i < proNodes.size() ; i++)
	    {
	      OozieProgramNode proNode = proNodes.get(i);
	      proNode.setOozJobId("latest");
	      proNodes.set(i, proNode  );
	    }
	    job.setGraphxml(tmpGraph.toXML());
	    
		SecureDao.update(job, sets, cond);
		job.setOozieGraph(graph);
		return job;
	}

	@Override
	public OozieJob getOozieJob(String oozieJobId) {
		// TODO Auto-generated method stub
		try {
			OozieJob oozieJob = new OozieJob();
			oozieJob.setId(oozieJobId);
			oozieJob = SecureDao.getObject(oozieJob);

			logger.info("************* Parse Graph Begin *****************");
			OozieGraph graph = OozieGraphXMLParser.parse(oozieJob.getGraphxml());
			oozieJob.setGraph(graph);
			logger.info(graph.toXML());
			logger.info("************* Parse Graph End*****************");
			return oozieJob;
		} catch (Exception ex) {
			logger.warning(ex.getMessage());
		}
		return null;
	}

	@Override
	public OozieJob getSynOozieJob(String oozieJobId) {
		// TODO Auto-generated method stub
		try {
			OozieJob oozieJob = new OozieJob();
			oozieJob.setId(oozieJobId);
			oozieJob = SecureDao.getObject(oozieJob);
			// Synchronize with oozie database
			syncDatabase(oozieJob);

			logger.info("query actions from bda database");
			OozieAction queryAction = new OozieAction();
			queryAction.setJobId(oozieJobId);
			List<OozieAction> actions = SecureDao.listAll(queryAction);
			oozieJob.setActions(actions);

			return oozieJob;

		} catch (Exception ex) {
			ex.printStackTrace();
		}
		return null;
	}

	@Override
	public Integer getRefOozieJobNum(String bdaJobId, Date startTime, Date endTime,boolean firstLoad) {
		// TODO Auto-generated method stub
		OozieJob oozieJob = new OozieJob();
		oozieJob.setJobid(bdaJobId);
		SimpleDateFormat formatter=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		try {
			if(firstLoad)//If is the first paging statistics, synchronize status for all the job
			{
				List<OozieJob> totalJobs = SecureDao.listAll(oozieJob); 
				for(OozieJob job : totalJobs) //Only synchronize status for running job
				{
					if(job.getStatus().toLowerCase().equals("running"))
						updateJobStatus(job.getId());
				}
			}
			List<OozieJob> totalJobs = SecureDao.listAll(oozieJob,"and createtime > \""+formatter.format(startTime)+"\""+"  and endtime < \""+formatter.format(endTime)+"\"");
			Integer oozieJobNum =totalJobs.size();
			return oozieJobNum;
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return null;
		}
	}

	@Override
	public void deleteOozieJob(String oozJobId) {
		OozieJob oozJob = new OozieJob();
		// Set the query condition to oozJobId
		oozJob.setId(oozJobId);

		try {
			// Delete HDFS directory
			OozieJob oozJobs_d = SecureDao.getObject(oozJob);
			String bdaJobId = oozJobs_d.getJobid();	
			HDFSIO.delete(oozJobs_d.getAppPath());
			// Delete by id
			SecureDao.delete(oozJob);

			//If the deleted ooziejob is the bdajob's current ooziejob, it should use current latest ooziejob to corresponding with the bdajob for replacement
			BdaJob bdaJob = new BdaJob();
			bdaJob.setJobId(bdaJobId);
			bdaJob = SecureDao.getObject(bdaJob);
			if(bdaJob!=null && bdaJob.getCurOozJobId().equals(oozJobId))
			{
				logger.info("Need to update bdajob: "+bdaJobId+" current ooziejob");
				oozJob = new OozieJob();
				oozJob.setJobid(bdaJobId);
				List<OozieJob> oozJobList = SecureDao.listAll(oozJob, "order by createtime desc");
				if(oozJobList!=null && oozJobList.size()>0)
				{
					bdaJob.setCurOozJobId(oozJobList.get(0).getId());
					SecureDao.update(bdaJob, "job_id");
					logger.info("Update bdajob: "+bdaJobId+" current ooziejob to "+oozJobList.get(0).getId());
				}
			}
		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}

	@Override
	public List<OozieJob> getRefOozieJobPage(String bdaJobId, int start,
			int size, Date startTime, Date endTime) {
		// TODO Auto-generated method stub
		OozieJob oozieJob = new OozieJob();
		SimpleDateFormat formatter=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		oozieJob.setJobid(bdaJobId);
		List<OozieJob> result = null;
		try {
			result =SecureDao.listAll(oozieJob, "and createtime > \""+formatter.format(startTime)+"\""+"  and endtime < \""+formatter.format(endTime)+"\""+" order by createtime desc limit " + start + ", " + size);
			return result;
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return null;
		}
	}

	@Override
	public void deleteBatchOozieJob(Set<String> oozJobIds) {
		// TODO Auto-generated method stub
		for(String jobId : oozJobIds)
		{
			deleteOozieJob(jobId);
		}
	}

	@Override
	public void updateJobActionStatus(String jobId) {
		// TODO Auto-generated method stub
		OozieJob job;
		try {
			job = OozieUtil.getJob(jobId);
			// Synchronize action information
			for (OozieAction action : job.getActions()) {
				action.setJobId(job.getId());
				SecureDao.update(action, new String[]{"jobid","ooziejobid", "name"});
			}
			for (OozieAction action : job.getActions()) {
				action.setAppPath(job.getAppPath());
			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

}