<?php

namespace Velis\Bpm\Ticket;

use ArrayObject;
use InvalidArgumentException;
use ReflectionClass;
use ReflectionException;
use ReflectionObject;
use Velis\App;
use Velis\Model\DataObject;

/**
 * Ticket object factory
 * @author Olek Procki <olo@velis.pl>
 */
final class Factory
{
    /**
     * Registered ticket specs
     * @var array
     */
    private static $_specs;


    /**
     * Registers tickets specs (types)
     * @param string $spec
     * @param string $class
     */
    public static function register($spec, $class)
    {
        self::$_specs[$spec] = $class;
    }


    /**
     * Returns registered specs
     * @return array
     */
    public static function getRegisteredSpecs()
    {
        if (!isset(self::$_specs)) {
            self::$_specs = array();
            if (App::$config->settings->ticketFactory) {
                foreach (App::$config->settings->ticketFactory as $spec => $class) {
                    self::register($spec, $class);
                }
            }
        }
        return self::$_specs;
    }


    /**
     * Returns true if factory has spec registered
     *
     * @param string $spec
     * @return bool
     */
    public static function has($spec)
    {
        return array_key_exists($spec, self::getRegisteredSpecs());
    }


    /**
     * Return categorizable ticket specs
     * @return array
     * @throws ReflectionException
     */
    public static function getCategorizables()
    {
        $specs = self::getRegisteredSpecs();
        foreach ($specs as $spec => $class) {
            $classInfo = new ReflectionClass($class);
            if (!in_array('Velis\Bpm\Ticket\Categorizable', $classInfo->getInterfaceNames())) {
                unset($specs[$spec]);
            }
        }

        return $specs;
    }


    /**
     * Creates ticket instance as instance of registered spec class
     *
     * @param mixed $data
     * @param string $spec
     *
     * @return Ticket
     *
     * @throws InvalidArgumentException
     */
    public static function create($data, $spec = null)
    {
        if ($spec == null) {
            if ($data instanceof DataObject) {
                $spec = $data->ticket_type_id;
            } elseif ($data instanceof ArrayObject || is_array($data)) {
                $spec = $data['ticket_type_id'];
            } else {
                throw new InvalidArgumentException('Ticket factory must get array or proper object argument type');
            }
        } else {
            if (!isset($data['ticket_type_id'])) {
                $data['ticket_type_id'] = $spec;
            }
        }

        if (self::has($spec)) {
            $specClass = self::$_specs[$spec];
            return new $specClass($data);
        } else {
            return new Ticket($data);
        }
    }


    /**
     * Cast ticket to spec class
     * @param Ticket $source
     * @param string $spec
     * @return Ticket
     * @throws ReflectionException
     */
    public static function cast(Ticket $source, $spec)
    {
        if (self::has($spec)) {
            $specClass = self::$_specs[$spec];
            $destination = new $specClass();

            $sourceReflection = new ReflectionObject($source);
            $destinationReflection = new ReflectionObject($destination);
            $sourceProperties = $sourceReflection->getProperties();

            foreach ($sourceProperties as $sourceProperty) {
                $sourceProperty->setAccessible(true);
                $name = $sourceProperty->getName();
                $value = $sourceProperty->getValue($source);

                if ($destinationReflection->hasProperty($name)) {
                    $propDest = $destinationReflection->getProperty($name);
                    $propDest->setAccessible(true);
                    $propDest->setValue($destination, $value);
                } else {
                    $destination->$name = $value;
                }
            }

            return $destination;
        }
    }


    /**
     * Returns ticket type by ticket class
     * @param Ticket $ticket
     * @return string
     */
    public static function typeOf($ticket)
    {
        $class   = get_class($ticket);
        $specRef = array_flip(self::$_specs);

        return $specRef[$class];
    }
}
