<?php
/**
 * Copyright 2007 Maintainable Software, LLC
 * Copyright 2008-2017 Horde LLC (http://www.horde.org/)
 *
 * See the enclosed file LICENSE for license information (BSD). If you
 * did not receive this file, see http://www.horde.org/licenses/bsd.
 *
 * @author     Mike Naberezny <mike@maintainable.com>
 * @author     Derek DeVries <derek@maintainable.com>
 * @author     Chuck Hagenbuch <chuck@horde.org>
 * @category   Horde
 * @license    http://www.horde.org/licenses/bsd
 * @package    Db
 * @subpackage Adapter
 */

/**
 *
 *
 * @author     Mike Naberezny <mike@maintainable.com>
 * @author     Derek DeVries <derek@maintainable.com>
 * @author     Chuck Hagenbuch <chuck@horde.org>
 * @category   Horde
 * @copyright  2007 Maintainable Software, LLC
 * @copyright  2008-2017 Horde LLC
 * @license    http://www.horde.org/licenses/bsd
 * @package    Db
 * @subpackage Adapter
 */
class Horde_Db_Adapter_Base_Column
{
    protected $_name;
    protected $_type;
    protected $_null;
    protected $_limit;
    protected $_precision;
    protected $_scale;
    protected $_unsigned;
    protected $_default;
    protected $_sqlType;
    protected $_isText;
    protected $_isNumber;


    /*##########################################################################
    # Construct/Destruct
    ##########################################################################*/

    /**
     * Constructor.
     *
     * @param string $name     The column's name, such as "supplier_id" in
     *                         "supplier_id int(11)".
     * @param string $default  The type-casted default value, such as "new" in
     *                         "sales_stage varchar(20) default 'new'".
     * @param string $sqlType  Used to extract the column's type, length and
     *                         signed status, if necessary. For example
     *                         "varchar" and "60" in "company_name varchar(60)"
     *                         or "unsigned => true" in "int(10) UNSIGNED".
     * @param boolean $null    Whether this column allows NULL values.
     */
    public function __construct($name, $default, $sqlType = null, $null = true)
    {
        $this->_name      = $name;
        $this->_sqlType   = $sqlType;
        $this->_null      = $null;

        $this->_limit     = $this->_extractLimit($sqlType);
        $this->_precision = $this->_extractPrecision($sqlType);
        $this->_scale     = $this->_extractScale($sqlType);
        $this->_unsigned  = $this->_extractUnsigned($sqlType);

        $this->_setSimplifiedType();
        $this->_isText    = $this->_type == 'text'  || $this->_type == 'string';
        $this->_isNumber  = $this->_type == 'float' || $this->_type == 'integer' || $this->_type == 'decimal';

        $this->_default   = $this->extractDefault($default);
    }

    /**
     * @return  boolean
     */
    public function isText()
    {
        return $this->_isText;
    }

    /**
     * @return  boolean
     */
    public function isNumber()
    {
        return $this->_isNumber;
    }

    /**
     * Casts value (which is a String) to an appropriate instance.
     */
    public function typeCast($value)
    {
        if ($value === null) return null;

        switch ($this->_type) {
        case 'string':
        case 'text':
            return $value;
        case 'integer':
            return strlen($value) ? (int)$value : null;
        case 'float':
            return strlen($value) ? (float)$value : null;
        case 'decimal':
            return $this->valueToDecimal($value);
        case 'datetime':
        case 'timestamp':
            return $this->stringToTime($value);
        case 'time':
            return $this->stringToDummyTime($value);
        case 'date':
            return $this->stringToDate($value);
        case 'binary':
            return $this->binaryToString($value);
        case 'boolean':
            return $this->valueToBoolean($value);
        default:
            return $value;
        }
    }

    public function extractDefault($default)
    {
        return $this->typeCast($default);
    }


    /*##########################################################################
    # Accessor
    ##########################################################################*/

    /**
     * @return  string
     */
    public function getName()
    {
        return $this->_name;
    }

    /**
     * @return  string
     */
    public function getDefault()
    {
        return $this->_default;
    }

    /**
     * @return  string
     */
    public function getType()
    {
        return $this->_type;
    }

    /**
     * @return  int
     */
    public function getLimit()
    {
        return $this->_limit;
    }

    /**
     * @return  int
     */
    public function precision()
    {
        return $this->_precision;
    }

    /**
     * @return  int
     */
    public function scale()
    {
        return $this->_scale;
    }

    /**
     * @return  boolean
     */
    public function isUnsigned()
    {
        return $this->_unsigned;
    }

    /**
     * @return  boolean
     */
    public function isNull()
    {
        return $this->_null;
    }

    /**
     * @return  string
     */
    public function getSqlType()
    {
        return $this->_sqlType;
    }


    /*##########################################################################
    # Type Juggling
    ##########################################################################*/

    /**
     * Used to convert from BLOBs to Strings
     *
     * @return  string
     */
    public function binaryToString($value)
    {
        return (string)$value;
    }

    /**
     * @param   string  $string
     * @return  Horde_Date
     */
    public function stringToDate($string)
    {
        if (empty($string) ||
            // preserve '0000-00-00' (http://bugs.php.net/bug.php?id=45647)
            preg_replace('/[^\d]/', '', $string) == 0) {
            return null;
        }

        $d = new Horde_Date($string);
        $d->setDefaultFormat('Y-m-d');

        return $d;
    }

    /**
     * @param   string  $string
     * @return  Horde_Date
     */
    public function stringToTime($string)
    {
        if (empty($string) ||
            // preserve '0000-00-00 00:00:00' (http://bugs.php.net/bug.php?id=45647)
            preg_replace('/[^\d]/', '', $string) == 0) {
            return null;
        }

        return new Horde_Date($string);
    }

    /**
     * @param   string  $string
     * @return  Horde_Date
     */
    public function stringToDummyTime($value)
    {
        if (empty($string)) {
            return null;
        }
        return $this->stringToTime('2000-01-01 ' . $string);
    }

    /**
     * @param   mixed  $value
     * @return  boolean
     */
    public function valueToBoolean($value)
    {
        if ($value === true || $value === false) {
            return $value;
        }

        $value = Horde_String::lower($value);
        return $value == 'true' || $value == 't' || $value == '1';
    }

    /**
     * @param   mixed  $value
     * @return  decimal
     */
    public function valueToDecimal($value)
    {
        return (float)$value;
    }


    /*##########################################################################
    # Protected
    ##########################################################################*/

    /**
     * @param   string  $sqlType
     * @return  int
     */
    protected function _extractLimit($sqlType)
    {
        if (preg_match("/\((.*)\)/", $sqlType, $matches)) {
            return (int)$matches[1];
        }
        return null;
    }

    /**
     * @param   string  $sqlType
     * @return  int
     */
    protected function _extractPrecision($sqlType)
    {
        if (preg_match("/^(numeric|decimal|number)\((\d+)(,\d+)?\)/i", $sqlType, $matches)) {
            return (int)$matches[2];
        }
        return null;
    }

    /**
     * @param   string  $sqlType
     * @return  int
     */
    protected function _extractScale($sqlType)
    {
        switch (true) {
            case preg_match("/^(numeric|decimal|number)\((\d+)\)/i", $sqlType):
                return 0;
            case preg_match("/^(numeric|decimal|number)\((\d+)(,(\d+))\)/i",
                            $sqlType, $match):
                return (int)$match[4];
        }
    }

    /**
     * @param   string  $sqlType
     * @return  int
     */
    protected function _extractUnsigned($sqlType)
    {
        return (boolean)preg_match('/^int.*unsigned/i', $sqlType);
    }

    /**
     */
    protected function _setSimplifiedType()
    {
        switch (true) {
        case preg_match('/int/i', $this->_sqlType):
            $this->_type = 'integer';
            return;
        case preg_match('/float|double/i', $this->_sqlType):
            $this->_type = 'float';
            return;
        case preg_match('/decimal|numeric|number/i', $this->_sqlType):
            $this->_type = $this->_scale == 0 ? 'integer' : 'decimal';
            return;
        case preg_match('/datetime/i', $this->_sqlType):
            $this->_type = 'datetime';
            return;
        case preg_match('/timestamp/i', $this->_sqlType):
            $this->_type = 'timestamp';
            return;
        case preg_match('/time/i', $this->_sqlType):
            $this->_type = 'time';
            return;
        case preg_match('/date/i', $this->_sqlType):
            $this->_type = 'date';
            return;
        case preg_match('/clob|text/i', $this->_sqlType):
            $this->_type = 'text';
            return;
        case preg_match('/blob|binary/i', $this->_sqlType):
            $this->_type = 'binary';
            return;
        case preg_match('/char|string/i', $this->_sqlType):
            $this->_type = 'string';
            return;
        case preg_match('/boolean/i', $this->_sqlType):
            $this->_type = 'boolean';
            return;
        }
    }
}
