<?php

namespace CHH;

trait MetaObject
{
    protected static $__metaClass;

    static function setMetaClass(MetaClass $metaClass)
    {
        static::$__metaClass = $metaClass;
    }

    static function getMetaClass()
    {
        if (null === static::$__metaClass) {
            static::$__metaClass = new MetaClass;
        }
        return static::$__metaClass;
    }

    function __call($method, array $argv = array())
    {
        $metaClass = static::getMetaClass();

        if (!$metaClass->respondsTo($method)) {
            throw new \BadMethodCallException(sprintf(
                'Call to undefined method %s', $method
            ));
        }
        return $metaClass->send($method, $argv, $this);
    }

    function __get($property)
    {
        $metaClass = static::getMetaClass();

        if (property_exists($metaClass, $property)) {
            return $this->$property = $metaClass->$property;
        }
    }

    function __isset($property)
    {
        return property_exists(static::getMetaClass(), $property);
    }
}

class MetaClass
{
    protected $methods = array();

    function extend($methods)
    {
        if ($methods instanceof MetaClass) {
            $methods = $methods->getMethods();
        }

        foreach ($methods as $method => $body) {
            $this->method($method, $body);
        }

        return $this;
    }

    function getMethods()
    {
        return $this->methods;
    }

    function method($name, \Closure $body) 
    {
        $this->methods[$name] = $body;
        return $this;
    }

    function property($name, $default = null)
    {
        $this->{$name} = $default;
        return $this;
    }

    function respondsTo($method)
    {
        return isset($this->methods[$method]);
    }

    function send($method, array $argv = array(), $context = null)
    {
        if (!$this->respondsTo($method)) {
            throw new \BadMethodCallException("Call to undefined Method $method");
        }

        $body = $this->methods[$method];

        if (null !== $context) {
            $body = $body->bindTo($context, get_class($context));
        }

        return call_user_func_array($body, $argv);
    }
}