<?php

/**
 * Interface for Danga's Gearman job scheduling system
 *
 * PHP version 5.1.0+
 *
 * LICENSE: This source file is subject to the New BSD license that is
 * available through the world-wide-web at the following URI:
 * http://www.opensource.org/licenses/bsd-license.php. If you did not receive
 * a copy of the New BSD License and are unable to obtain it through the web,
 * please send a note to license@php.net so we can mail you a copy immediately.
 *
 * @category  Net
 * @package   ShSo\Net\Gearman
 * @author    Joe Stump <joe@joestump.net>
 * @copyright 2007-2008 Digg.com, Inc.
 * @license   http://www.opensource.org/licenses/bsd-license.php New BSD License
 * @version   CVS: $Id$
 * @link      http://pear.php.net/package/Net_Gearman
 * @link      http://www.danga.com/gearman/
 */

namespace ShSo\Net\Gearman;
/**
 * Task class for creating ShSo\Net\Gearman tasks
 *
 * @category  Net
 * @package   ShSo\Net\Gearman
 * @author    Joe Stump <joe@joestump.net>
 * @copyright 2007-2008 Digg.com, Inc.
 * @license   http://www.opensource.org/licenses/bsd-license.php New BSD License
 * @version   Release: @package_version@
 * @link      http://www.danga.com/gearman/
 * @see       ShSo\Net\Gearman\Set, ShSo\Net\Gearman\Client
 */
class Task
{
    /**
     * The function/job to run
     *
     * @var string $func
     */
    public $func = '';

    /**
     * Arguments to pass to function/job
     *
     * @var array $arg
     */
    public $arg = array();

    /**
     * Type of job
     *
     * Which type of job you wish this task to be ran as. Keep in mind that
     * background jobs are "fire and forget" and DO NOT return results to the
     * job server in a manner that you can actually retrieve.
     *
     * @var integer $type
     * @see ShSo\Net\Gearman\Task::JOB_NORMAL
     * @see ShSo\Net\Gearman\Task::JOB_BACKGROUND
     * @see ShSo\Net\Gearman\Task::JOB_HIGH
     * @see ShSo\Net\Gearman\Task::JOB_HIGH_BACKGROUND
     * @see ShSo\Net\Gearman\Task::JOB_LOW
     * @see ShSo\Net\Gearman\Task::JOB_LOW_BACKGROUND
     */
    public $type = self::JOB_NORMAL;

    /**
     * Handle returned from job server
     *
     * @var string $handle
     * @see ShSo\Net\Gearman\Client
     */
    public $handle = '';

    /**
     * The unique identifier for this job
     *
     * Keep in mind that a unique job is only unique to the job server it is
     * submitted to. Gearman servers don't communicate with each other to
     * ensure a job is unique across all workers.
     *
     * That being said, Gearman does group identical jobs sent to it and runs
     * that job only once. If you send the job Sum with args 1, 2, 3 to the
     * server 10 times in a second Gearman will only run that job once and then
     * return the result 10 times.
     *
     * @var string $uniq
     */
    public $uniq = '';

    /**
     * Is this task finished?
     *
     * @var boolean $finished
     * @see ShSo\Net\Gearman\Set::finished()
     * @see ShSo\Net\Gearman\Task::complete()
     * @see ShSo\Net\Gearman\Task::fail()
     */
    public $finished = false;

    /**
     * The result returned from the worker
     *
     * @var object $result
     */
    public $result = '';

    /**
     * Callbacks registered for each state
     *
     * @var array $callback
     * @see ShSo\Net\Gearman\Task::attachCallback()
     * @see ShSo\Net\Gearman\Task::complete()
     * @see ShSo\Net\Gearman\Task::status()
     * @see ShSo\Net\Gearman\Task::fail()
     */
    protected $callback = array(
        self::TASK_COMPLETE => array(),
        self::TASK_FAIL     => array(),
        self::TASK_STATUS   => array()
    );

    /**
     * Normal job
     *
     * Normal jobs are ran against a worker with the result being returned
     * all in the same thread (e.g. Your page will sit there waiting for the
     * job to finish and return it's result).
     *
     * @var integer JOB_NORMAL
     */
    const JOB_NORMAL = 1;

    /**
     * Background job
     *
     * Background jobs in Gearman are "fire and forget". You can check a job's
     * status periodically, but you can't get a result back from it.
     *
     * @var integer JOB_BACKGROUND
     */
    const JOB_BACKGROUND = 2;

    /**
     * High priority job
     *
     * @var integer JOB_HIGH
     */
    const JOB_HIGH = 3;

    /**
     * High priority, background job
     *
     * @var integer JOB_HIGH
     */
    const JOB_HIGH_BACKGROUND = 4;

    /**
     * LOW priority job
     *
     * @var integer JOB_LOW
     */
    const JOB_LOW = 5;

    /**
     * Low priority, background job
     *
     * @var integer JOB_LOW_BACKGROUND
     */
    const JOB_LOW_BACKGROUND = 6;

    /**
     * Callback of type complete
     *
     * The callback provided should be ran when the task has been completed. It
     * will be handed the result of the task as its only argument.
     *
     * @var integer TASK_COMPLETE
     * @see ShSo\Net\Gearman\Task::complete()
     */
    const TASK_COMPLETE = 1;

    /**
     * Callback of type fail
     *
     * The callback provided should be ran when the task has been reported to
     * have failed by Gearman. No arguments are provided.
     *
     * @var integer TASK_FAIL
     * @see ShSo\Net\Gearman\Task::fail()
     */
    const TASK_FAIL = 2;

    /**
     * Callback of type status
     *
     * The callback provided should be ran whenever the status of the task has
     * been updated. The numerator and denominator are passed as the only
     * two arguments.
     *
     * @var integer TASK_STATUS
     * @see ShSo\Net\Gearman\Task::status()
     */
    const TASK_STATUS = 3;

    /**
     * Constructor
     *
     * @param string  $func Name of job to run
     * @param mixed   $arg  List of arguments for job
     * @param string  $uniq The unique id of the job
     * @param integer $type Type of job to run task as
     *
     * @return ShSo\Net\Gearman\Task
     * @throws ShSo\Net\Gearman\Exception
     */
    public function __construct($func, $arg, $uniq = null,
                                $type = self::JOB_NORMAL)
    {
        $this->func = $func;
        $this->arg  = $arg;

        if (is_null($uniq)) {
            $this->uniq = md5($func . serialize($arg) . $type);
        } else {
            $this->uniq = $uniq;
        }

        $type = (int) $type;
        if ($type > 6) {
            throw new Exception(
                "Unknown job type: {$type}. Please see ShSo\\Net\\Gearman\\Task::JOB\\* constants."
            );
        }

        $this->type = $type;
    }

    /**
     * Attach a callback to this task
     *
     * @param callback $callback A valid PHP callback
     * @param integer  $type     Type of callback
     *
     * @return $this
     * @throws ShSo\Net\Gearman\Exception When the callback is invalid.
     * @throws ShSo\Net\Gearman\Exception When the callback's type is invalid.
     */
    public function attachCallback($callback, $type = self::TASK_COMPLETE)
    {
        if (!is_callable($callback)) {
            throw new Exception('Invalid callback specified');
        }

        if (!in_array(
            $type,
            array(self::TASK_COMPLETE, self::TASK_FAIL, self::TASK_STATUS)
        )) {
            throw new Exception('Invalid callback type specified');
        }

        $this->callback[$type][] = $callback;
        return $this;
    }

    /**
     * Return all callbacks.
     *
     * @return array
     */
    public function getCallbacks()
    {
        return $this->callback;
    }

    /**
     * Run the complete callbacks
     *
     * Complete callbacks are passed the name of the job, the handle of the
     * job and the result of the job (in that order).
     *
     * @param object $result JSON decoded result passed back
     *
     * @return void
     * @see ShSo\Net\Gearman\Task::attachCallback()
     */
    public function complete($result)
    {
        $this->finished = true;
        $this->result   = $result;

        if (!count($this->callback[self::TASK_COMPLETE])) {
            return;
        }

        foreach ($this->callback[self::TASK_COMPLETE] as $callback) {
            call_user_func($callback, $this->func, $this->handle, $result);
        }
    }

    /**
     * Run the failure callbacks
     *
     * Failure callbacks are passed the task object job that failed
     *
     * @return void
     * @see ShSo\Net\Gearman\Task::attachCallback()
     */
    public function fail()
    {
        $this->finished = true;
        if (!count($this->callback[self::TASK_FAIL])) {
            return;
        }

        foreach ($this->callback[self::TASK_FAIL] as $callback) {
            call_user_func($callback, $this);
        }
    }

    /**
     * Run the status callbacks
     *
     * Status callbacks are passed the name of the job, handle of the job and
     * the numerator/denominator as the arguments (in that order).
     *
     * @param integer $numerator   The numerator from the status
     * @param integer $denominator The denominator from the status
     *
     * @return void
     * @see ShSo\Net\Gearman\Task::attachCallback()
     */
    public function status($numerator, $denominator)
    {
        if (!count($this->callback[self::TASK_STATUS])) {
            return;
        }

        foreach ($this->callback[self::TASK_STATUS] as $callback) {
            call_user_func($callback,
                           $this->func,
                           $this->handle,
                           $numerator,
                           $denominator);
        }
    }
}