package it.fabioformosa.quartzmanager.controllers;

import java.util.Collections;
import java.util.Map;

import javax.annotation.Resource;

import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.SimpleTrigger;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.triggers.SimpleTriggerImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import io.swagger.annotations.Api;
import it.fabioformosa.quartzmanager.dto.SchedulerConfigParam;
import it.fabioformosa.quartzmanager.dto.TriggerStatus;
import it.fabioformosa.quartzmanager.enums.SchedulerStates;
import it.fabioformosa.quartzmanager.scheduler.TriggerMonitor;

/**
 * This controller provides scheduler info about config and status. It provides
 * also methods to set new config and start/stop/resume the scheduler.
 *
 * @author Fabio.Formosa
 *
 */
@RestController
@RequestMapping("/scheduler")
@Api(value = "scheduler")
public class SchedulerController {

	private static final int MILLS_IN_A_DAY = 1000 * 60 * 60 * 24;
	private static final int SEC_IN_A_DAY = 60 * 60 * 24;

	private final Logger log = LoggerFactory.getLogger(SchedulerController.class);

	@Resource
	private Scheduler scheduler;

	@Resource
	private TriggerMonitor triggerMonitor;

	@GetMapping("/config")
	public SchedulerConfigParam getConfig() {
		log.debug("SCHEDULER - GET CONFIG params");
		SimpleTrigger simpleTrigger = (SimpleTrigger) triggerMonitor.getTrigger();

		int maxCount = simpleTrigger.getRepeatCount() + 1;
		long triggersPerDay = fromMillsIntervalToTriggerPerDay(simpleTrigger.getRepeatInterval());

		return new SchedulerConfigParam(triggersPerDay, maxCount);
	}

	@GetMapping("/progress")
	public TriggerStatus getProgressInfo() throws SchedulerException {
		log.trace("SCHEDULER - GET PROGRESS INFO");
		TriggerStatus progress = new TriggerStatus();

		SimpleTriggerImpl jobTrigger = (SimpleTriggerImpl) scheduler.getTrigger(triggerMonitor.getTrigger().getKey());
		if (jobTrigger != null && jobTrigger.getJobKey() != null) {
			progress.setJobKey(jobTrigger.getJobKey().getName());
			progress.setJobClass(jobTrigger.getClass().getSimpleName());
			progress.setTimesTriggered(jobTrigger.getTimesTriggered());
			progress.setRepeatCount(jobTrigger.getRepeatCount());
			progress.setFinalFireTime(jobTrigger.getFinalFireTime());
			progress.setNextFireTime(jobTrigger.getNextFireTime());
			progress.setPreviousFireTime(jobTrigger.getPreviousFireTime());
		}

		return progress;
	}

	@GetMapping(produces = "application/json")
	public Map<String, String> getStatus() throws SchedulerException {
		log.trace("SCHEDULER - GET STATUS");
		String schedulerState = "";
		if (scheduler.isShutdown() || !scheduler.isStarted())
			schedulerState = SchedulerStates.STOPPED.toString();
		else if (scheduler.isStarted() && scheduler.isInStandbyMode())
			schedulerState = SchedulerStates.PAUSED.toString();
		else
			schedulerState = SchedulerStates.RUNNING.toString();
		return Collections.singletonMap("data", schedulerState.toLowerCase());
	}

	@GetMapping("/pause")
	@ResponseStatus(HttpStatus.NO_CONTENT)
	public void pause() throws SchedulerException {
		log.info("SCHEDULER - PAUSE COMMAND");
		scheduler.standby();
	}

	@PostMapping("/config")
	public SchedulerConfigParam postConfig(@RequestBody SchedulerConfigParam config) throws SchedulerException {
		log.info("SCHEDULER - NEW CONFIG {}", config);
		SimpleTrigger trigger = (SimpleTrigger) triggerMonitor.getTrigger();

		TriggerBuilder<SimpleTrigger> triggerBuilder = trigger.getTriggerBuilder();

		int intervalInMills = fromTriggerPerDayToMillsInterval(config.getTriggerPerDay());
		Trigger newTrigger = triggerBuilder.withSchedule(SimpleScheduleBuilder.simpleSchedule()
				.withIntervalInMilliseconds(intervalInMills).withRepeatCount(config.getMaxCount() - 1)).build();

		scheduler.rescheduleJob(triggerMonitor.getTrigger().getKey(), newTrigger);
		triggerMonitor.setTrigger(newTrigger);
		return config;
	}

	@GetMapping("/resume")
	@ResponseStatus(HttpStatus.NO_CONTENT)
	public void resume() throws SchedulerException {
		log.info("SCHEDULER - RESUME COMMAND");
		scheduler.start();
	}

	@GetMapping("/run")
	@ResponseStatus(HttpStatus.NO_CONTENT)
	public void run() throws SchedulerException {
		log.info("SCHEDULER - START COMMAND");
		scheduler.start();
	}

	@GetMapping("/stop")
	@ResponseStatus(HttpStatus.NO_CONTENT)
	public void stop() throws SchedulerException {
		log.info("SCHEDULER - STOP COMMAND");
		scheduler.shutdown(true);
	}

	private long fromMillsIntervalToTriggerPerDay(long repeatIntervalInMills) {
		return (int) Math.ceil(MILLS_IN_A_DAY / repeatIntervalInMills);
	}

	private int fromTriggerPerDayToMillsInterval(long triggerPerDay) {
		return (int) Math.ceil(Long.valueOf(MILLS_IN_A_DAY) / triggerPerDay); // with ceil the triggerPerDay is a max value
	}

	@SuppressWarnings("unused")
	private int fromTriggerPerDayToSecInterval(long triggerPerDay) {
		return (int) Math.ceil(Long.valueOf(SEC_IN_A_DAY) / triggerPerDay);
	}

}