<?php
/**
 * A class for handling html forms.
 *
 * This class provides a way to filter, validate and render a form and its
 * inputs. It also features a decorator pattern for layout flexiblity.
 */
class form extends input
{
    /**
     * @var array The form attributes.
     */
    protected $attribute;
    
    /**
     * @var array The form fields.
     */
    protected $field;
    
    /**
     * @var form_decorator The decorator.
     */
    protected $decorator;

    ////////////////////////////////////////////////////////////////////////////
    // Construct and destruct functions.

    /**
     * @param array $field The form fields.
     * @param form_decorator (optional) $decorator The form decorator.
     */
    public function __construct(array $field, form_decorator $decorator = null)
    {
        list($field, $rule, $data) = $this->split_field($field);
        parent::__construct($rule);
        
        if ($data)
        {
            $this->set($data);
        }
        
        $this->field = $this->create_field($field);
        $this->decorator = $decorator;
    }

    ////////////////////////////////////////////////////////////////////////////
    // Overload functions.
    
    /**
     * A shortcut for using a form widget object.
     * 
     * @param string $function The class name.
     * @param array $parameter The parameters to provide.
     * @return string The rendered widget.
     */
    public static function __callstatic($function, array $parameter)
    {
        return call_user_func_array('form_widget_' . $function . '::render', $parameter);
    }
    
    ////////////////////////////////////////////////////////////////////////////
    // Interface functions.

    /**
     * Renders the form.
     *
     * @param string/array (optional) $attribute The form attributes.
     * @return string The rendered form.
     */
    public function render($attribute = array())
    {
        if (!$this->decorator)
        {
            $this->decorator = new form_decorator_table;
        }
        
        if (!is_array($attribute))
        {
            $attribute = array('id' => $attribute);
        }
        
        $form = '<form';
        
        foreach ($attribute as $k => $v)
        {
            $form .= ' ' . $k . '="' . htmlspecialchars($v) . '"';
        }
        
        $form .= '>' . $this->render_set() . '</form>';
        return $form;
    }

    ////////////////////////////////////////////////////////////////////////////
    // Internal functions.
    
    /**
     * Renders the set.
     * 
     * @return string The rendered set.
     */
    protected function render_set()
    {
        $field = $this->field;
        $option = $decorator = array();
        $option['submit'] = (array_key_exists('submit', $field)) ? $field['submit'] : array();
        $option['reset'] = (array_key_exists('reset', $field)) ? $field['reset'] : array();
        $decorator['field'] = (isset($this->field['submit']['decorator']['field'])) ? $this->field['submit']['decorator']['field'] : null;
        $decorator['entry'] = (isset($this->field['submit']['decorator']['entry'])) ? $this->field['submit']['decorator']['entry'] : null;
        unset($field['submit'], $field['reset'], $option['submit']['decorator'], $option['reset']['decorator']);
        $submit = form_widget_submit::render($option['submit']) . form_widget_reset::render($option['reset']);
        $submit = $this->decorator->render_field($submit, $decorator['field']);
        $submit = $this->decorator->render_entry('', '', $submit, $decorator['entry']);
        $set = '';
        
        foreach (array_keys($field) as $k)
        {
            $set .= $this->render_entry($k);
        }
        
        $set .= $submit;
        return $this->decorator->render_set($set);
    }
    
    /**
     * Renders an entry (error, label and field).
     * 
     * @param string $name The variable name.
     * @return string The rendered entry.
     */
    protected function render_entry($name)
    {
        if (array_key_exists('type', $this->field[$name]) && $this->field[$name]['type'] === 'hidden')
        {
            return $this->render_field($name, false);
        }
        
        $error = ($this->is_error($name)) ? $this->render_error($name) : '';
        $label = $this->render_label($name);
        $field = $this->render_field($name);
        $decorator = (isset($this->field[$name]['decorator']['entry'])) ? $this->field[$name]['decorator']['entry'] : null;
        return $this->decorator->render_entry($error, $label, $field, $decorator);
    }
    
    /**
     * Renders an error message.
     * 
     * @param string $name The variable name.
     * @return string The rendered error.
     */
    protected function render_error($name)
    {
        $error = htmlspecialchars($this->get_error($name));
        $decorator = (isset($this->field[$name]['decorator']['error'])) ? $this->field[$name]['decorator']['error'] : null;
        return $this->decorator->render_error($error, $decorator);
    }
    
    /**
     * Renders a label.
     * 
     * @param string $name The variable name.
     * @return string The rendered label.
     */
    protected function render_label($name)
    {
        $label = form_widget_label::render($this->field[$name]['label']);
        $decorator = (isset($this->field[$name]['decorator']['label'])) ? $this->field[$name]['decorator']['label'] : null;
        return $this->decorator->render_label($label, $decorator);
    }
    
    /**
     * Renders a field.
     * 
     * @param string $name The variable name.
     * @param boolean (optional) $decorate Whether to decorate the field or not.
     * @return string The rendered field.
     */
    protected function render_field($name, $decorate = true)
    {
        $option = $this->field[$name];
        $type = (array_key_exists('type', $option)) ? $option['type'] : 'text';
        unset($option['type'], $option['decorator'], $option['label']);
        $option['name'] = $name;
        $option['value'] = $this->get($name);
        $field = call_user_func('form_widget_' . $type . '::render', $option);
        $decorator = (isset($this->field[$name]['decorator']['field'])) ? $this->field[$name]['decorator']['field'] : null;
        return ($decorate) ? $this->decorator->render_field($field, $decorator) : $field;
    }
    
    /**
     * Splits the field list into fields, rules and default data.
     *
     * @param array $field The form fields.
     * @return array The fields, rules and raw data.
     */
    protected function split_field(array $field)
    {
        $new = $rule = $data = array();
        
        foreach ($field as $k => $v)
        {
            if ($k === '*')
            {
                $rule['*'] = $v;
                continue;
            }
            
            if (is_numeric($k))
            {
                $k = $v;
                $v = array();
            }
            
            if ($k === 'submit' || $k === 'reset')
            {
                $new[$k] = $v;
                continue;
            }
            
            if (!is_array($v))
            {
                $v = array($v);
            }
            
            $new[$k] = array();
            $rule[$k] = array();
            
            foreach ($v as $l => $w)
            {
                if (is_numeric($l))
                {
                    $rule[$k][] = $w;
                }
                elseif ($l === 'default')
                {
                    $data[$k] = $w;
                }
                else
                {
                    $new[$k][$l] = $w;
                }
            }
        }
        
        return array($new, $rule, $data);
    }
    
    /**
     * Creates a list of organized fields.
     *
     * @param array $field The fields.
     * @return array The organized fields.
     */
    protected function create_field(array $field)
    {
        foreach (array('submit', 'reset') as $k)
        {
            if (!array_key_exists($k, $field))
            {
                $field[$k] = array();
            }
            
            if (!is_array($field[$k]))
            {
                $field[$k] = array('value' => $field[$k]);
            }
            
            if (!array_key_exists('value', $field[$k]))
            {
                $field[$k]['value'] = ucfirst($k);
            }
        }
        
        foreach ($field as $k => &$v)
        {
            if (array_key_exists('decorator', $v) && !is_array($v['decorator']))
            {
                $v['decorator'] = array('field' => $v['decorator']);
            }
            
            if ($k !== 'submit' && $k !== 'reset')
            {
                if (!array_key_exists('label', $v))
                {
                    $v['label'] = ucfirst(strtolower(str_replace('_', ' ', $k)));
                }
                
                if (!is_array($v['label']))
                {
                    $v['label'] = array('value' => $v['label']);
                }
            }
        }
        
        return $field;
    }
}
