<?php

namespace Maghead\Schema\Relationship;

use Magsql\Universal\Syntax\Conditions;
use Maghead\Schema\SchemaLoader;
use Maghead\Runtime\Collection;
use LogicException;
use ArrayAccess;
use IteratorAggregate;
use ArrayObject;

class Relationship implements IteratorAggregate, ArrayAccess
{
    const HAS_MANY = 1;

    const HAS_ONE = 2;

    const BELONGS_TO = 3;

    const MANY_TO_MANY = 4;



    /**
     * @var array The stashed data
     */
    public $data = [];

    /**
     * @var string The accessor name
     */
    public $accessor;

    /**
     * @var Conditions The Magsql Condition Syntax Object
     */
    public $where;

    public $orderBy = [];

    public $onUpdate;

    public $onDelete;

    public $usingIndex;

    public function __construct($accessor, array $data = [])
    {
        $this->accessor = $accessor;
        $this->data = $data;
    }

    public function by($column)
    {
        $this->data['self_column'] = $column;

        return $this;
    }

    public function usingIndex($index = true)
    {
        $this->usingIndex = $index;
    }

    public function newForeignSchema()
    {
        return SchemaLoader::load($this->data['foreign_schema']);
    }

    public function newForeignModel()
    {
        $schema = $this->newForeignSchema();
        $modelClass = $schema->getModelClass();

        return new $modelClass();
    }

    public function newForeignCollection()
    {
        $schema = $this->newForeignSchema();
        $collectionClass = $schema->getCollectionClass();

        return new $collectionClass();
    }

    /**
     * Resolve the junction relationship to retrieve foreign collection of the foreign collection.
     *
     * This method is only for many-to-many relationship object.
     *
     * @return Maghead\Runtime\Collection The foreign foreign collection.
     */
    public function newForeignForeignCollection($junctionRelation)
    {
        $junctionSchema = SchemaLoader::load($junctionRelation['foreign_schema']);
        $foreignRelation = $junctionSchema->getRelation($this['relation_foreign']);
        $collection = $foreignRelation->newForeignCollection();
        $this->applyFilter($collection); // apply this filter to the foreign collection.
        return $collection;
    }

    public function applyFilter(Collection &$collection)
    {
        if (isset($this->data['filter'])) {
            $collection = call_user_func_array($this->data['filter'], array($collection));
        }
    }

    public function applyWhere(Collection &$collection)
    {
        if ($this->where) {
            $collection->setWhere($this->where);
        }
    }

    public function applyOrder(Collection &$collection)
    {
        if (isset($this->data['orderBy']) && $this->data['orderBy']) {
            foreach ($this->data['orderBy'] as $o) {
                $collection->orderBy($o[0], $o[1]);
            }
        }
    }

    public function getForeignCollection()
    {
        $collection = $this->newForeignCollection();
        $this->applyFilter($collection);
        $this->applyWhere($collection);
        $this->applyOrder($collection);

        return $collection;
    }

    /**
     * Provide dynamic cascading accessors.
     *
     * $relationship->foreign_schema('something')
     * $relationship->view('something')
     */
    public function __call($m, $as)
    {
        $this->data[ $m ] = $as[0];

        return $this;
    }

    /**
     * Define filter for collection.
     *
     * @param callback $filter filter callback.
     */
    public function filter($filter)
    {
        $this->data['filter'] = $filter;

        return $this;
    }

    public function order()
    {
        throw new LogicException('order(column, ordering) is now deprecated, please use orderBy(column, ordering)');
    }

    /**
     * Save order on the relationship.
     *
     * @param string $column
     * @param string $ordering
     */
    public function orderBy($column, $ordering)
    {
        $this->orderBy[] = array($column, $ordering);

        return $this;
    }

    public function where($expr = null, array $args = array())
    {
        if (!$this->where) {
            $this->where = new Conditions();
        }
        if ($expr) {
            if (is_string($expr)) {
                $this->where->appendExpr($expr, $args);
            } elseif (is_array($expr)) {
                foreach ($expr as $key => $val) {
                    $this->where->equal($key, $val);
                }
            } else {
                throw new InvalidArgumentException("Unsupported argument type of 'where' method.");
            }
        }

        return $this->where;
    }

    /**
     * To support foreach operation.
     */
    public function getIterator()
    {
        return new ArrayIterator($this->data);
    }

    public function offsetSet($name, $value)
    {
        $this->data[$name] = $value;
    }

    public function offsetExists($name)
    {
        return isset($this->data[$name]);
    }

    public function offsetGet($name)
    {
        return $this->data[$name];
    }

    public function offsetUnset($name)
    {
        unset($this->data[$name]);
    }

    /**
     * To support var_export.
     */
    public static function __set_state(array $stash)
    {
        $r = new static($stash['accessor'], $stash['data']);
        if (isset($stash['where'])) {
            $r->where = $stash['where'];
        }
        if (isset($stash['orderBy'])) {
            $r->orderBy = $stash['orderBy'];
        }
        if (isset($stash['onUpdate'])) {
            $r->onUpdate = $stash['onUpdate'];
        }
        if (isset($stash['onDelete'])) {
            $r->onDelete = $stash['onDelete'];
        }

        return $r;
    }

    public function onUpdate($action)
    {
        $this->onUpdate = $action;

        return $this;
    }

    public function onDelete($action)
    {
        $this->onDelete = $action;

        return $this;
    }

    public function __get($key)
    {
        if (isset($this->data[$key])) {
            return $this->data[$key];
        }
    }

    public function __set($key, $val)
    {
        $this->data[$key] = $val;
    }
}
