<?php

namespace XF\Job;

use function is_string;

class Atomic extends AbstractJob
{
	protected $defaultData = [
		'steps' => 0,
		'execute' => [],
	];

	protected $statusMessage = '';

	public function run($maxRunTime)
	{
		$start = microtime(true);

		$this->data['steps']++;
		$remaining = $maxRunTime;

		do
		{
			$jobInfo = $this->getFirstJob();
			if (!$jobInfo)
			{
				return $this->complete();
			}

			/** @var AbstractJob $job */
			$job = $jobInfo['job'];
			if (!$job)
			{
				// broken job, skip
				$this->deleteAtomicJob($jobInfo['key']);
				continue;
			}

			$result = $job->run($remaining);
			if (!($result instanceof JobResult))
			{
				throw new \LogicException("Jobs must return JobResult objects");
			}

			$this->statusMessage = $job->getStatusMessage();

			if ($result->result === JobResult::RESULT_COMPLETED)
			{
				// all finished
				$this->deleteAtomicJob($jobInfo['key']);
			}
			else
			{
				$this->updateAtomicJob($jobInfo['key'], $result->data);
			}

			\XF::triggerRunOnce();

			$remaining = $maxRunTime - (microtime(true) - $start);
			if ($remaining < 1)
			{
				break;
			}
		}
		while ($this->data['execute']);

		if (!$this->data['execute'])
		{
			return $this->complete();
		}

		return $this->resume();
	}

	protected function getFirstJob()
	{
		$value = reset($this->data['execute']);
		if (!$value)
		{
			return null;
		}

		$key = key($this->data['execute']);

		if (is_string($value))
		{
			$class = $value;
			$data = [];
		}
		else
		{
			$class = $value[0];
			$data = $value[1];
		}

		$job = $this->app->job($class, 0, $data);
		return [
			'key' => $key,
			'job' => $job,
		];
	}

	protected function deleteAtomicJob($key)
	{
		unset($this->data['execute'][$key]);
	}

	protected function updateAtomicJob($key, array $data)
	{
		if (!isset($this->data['execute'][$key]))
		{
			return false;
		}

		$value = $this->data['execute'][$key];
		$class = is_string($value) ? $value : $value[0];
		$this->data['execute'][$key] = [$class, $data];

		return true;
	}

	public function getStatusMessage()
	{
		return $this->statusMessage;
	}

	public function canCancel()
	{
		return false;
	}

	public function canTriggerByChoice()
	{
		return false;
	}
}
