<?php

namespace Velis\Xls;

use DateTimeInterface;
use PhpOffice\PhpSpreadsheet\Cell\Cell;
use PhpOffice\PhpSpreadsheet\Cell\DataType;
use PhpOffice\PhpSpreadsheet\Cell\IValueBinder;
use PhpOffice\PhpSpreadsheet\RichText\RichText;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
use Velis\Filter;
use Velis\Output;
use Velis\Xls;

/**
 * PhpSpreadsheet value binder
 * @author Damian Kurek <damian.kurek@velis.pl>
 */
class ValueBinder implements IValueBinder
{
    /**
     *
     * @var boolean
     */
    protected $_allowFormula;


    public function __construct($allowFormula = true)
    {
        $this->_allowFormula = $allowFormula;
    }

    /**
     * Bind value to a cell.
     *
     * @param Cell  $cell  Cell to bind value to
     * @param mixed $value Value to bind in cell
     *
     * @throws \PhpOffice\PhpSpreadsheet\Exception
     *
     * @return bool
     */
    public function bindValue(Cell $cell, $value)
    {
        // sanitize UTF-8 strings
        if (is_string($value)) {
            $value = StringHelper::sanitizeUTF8($value);
        } elseif (is_object($value)) {
            // Handle any objects that might be injected
            if ($value instanceof DateTimeInterface) {
                $value = $value->format('Y-m-d H:i:s');
            } elseif (!($value instanceof RichText)) {
                $value = (string) $value;
            }
        }

        $valueType = self::dataTypeForValue($value);

        if ($valueType == Xls::TYPE_NUMERIC) {
            $value = Output::toNumeric($value);
            $cell->setValueExplicit($value, Xls::TYPE_STRING2);
            $cell->setDataType(Xls::TYPE_NUMERIC);
            return true;
        } else if (!$this->_allowFormula && $valueType == Xls::TYPE_FORMULA) {
            $value = '"' . $value . '"';
            $valueType = self::dataTypeForValue($value);
        }

        // Set value explicit
        $cell->setValueExplicit($value, $valueType);

        // Done!
        return true;
    }

    /**
     * DataType for value.
     *
     * @param mixed $pValue
     *
     * @return string
     */
    public static function dataTypeForValue($pValue)
    {
        // Match the value against a few data types
        if ($pValue === null) {
            return Xls::TYPE_NULL;
        } elseif ($pValue === '') {
            return Xls::TYPE_STRING;
        } elseif ($pValue instanceof RichText) {
            return Xls::TYPE_INLINE;
        } elseif (is_string($pValue) && in_array($pValue[0], ['=', '@', '+', '-']) && strlen($pValue) > 1) {
            return Xls::TYPE_FORMULA;
        } elseif (is_bool($pValue)) {
            return Xls::TYPE_BOOL;
        } elseif (is_float($pValue) || is_int($pValue)) {
            return Xls::TYPE_NUMERIC;
        } elseif (Filter::validateNumber($pValue)) {
            return Xls::TYPE_NUMERIC;
        } elseif (is_string($pValue)) {
            $errorCodes = DataType::getErrorCodes();
            if (isset($errorCodes[$pValue])) {
                return Xls::TYPE_ERROR;
            }
        }

        return Xls::TYPE_STRING;
    }
}
