<?php

namespace App\lib\Html;

use App\lib\Html\Json;
use Illuminate\Support\Facades\Session;

class AppTable
{

    const ORDER_ASC = 'ASC';
    const ORDER_DESC = 'DESC';
    const SELECT_SINGLE = 'single';
    const SELECT_MULTIPLE = 'multiple';

    protected $_columns = array();
    protected $_actions = array();
    public $selectable = false;
    public $tableId = null;
    public $dataSourceUrl = null;
    public $visibleColumns = null;
    public $defaultSort = null;
    public $defaultSortDir = null;
    public $defaultLength = 10;
    public $stateSave = true;
    public $selectType = 'multiple';
    public $emptyText = 'No data available in table';
    public $rowIdentifier = null;
    public $rowIdentifierName = null;
    public $searchForm = null;
    public $columnSortable = true;
    public $rowSortable = false;
    public $rowSortableFieldName = 'sort_order';
    public $sortOrderUrl = null;
    public $sDom = '<"datatable-header"fl><"datatable-scroll-lg"t><"datatable-footer"ip>';
    private $_tableSettings = array();

    public function __construct($options = array())
    {
        $this->setOptions($options);
        $this->init();
    }

    public function setOptions($options)
    {
        $this->_tableSettings = $options;
        foreach ($options as $optionKey => $option)
        {
            if (property_exists($this, $optionKey))
            {
                $this->{$optionKey} = $option;
            }
        }
    }

    public function addColumn($columnId, $options = array())
    {
        $this->_columns[$columnId] = $options + $this->getDefaultColConfig($columnId);
    }
	
    public function addActionLink($actionId, $options = array())
    {
        $this->_actions[$actionId] = $options + $this->getDefaultActConfig($actionId);
    }

    public function addAction($actionId, $options = array())
    {
        $this->_actions['HEADER'][$actionId] = $options + $this->getDefaultActConfig($actionId);
    }

    public function removeAction($actionId)
    {
        unset($this->_actions['HEADER'][$actionId]);
        return $this;
    }

    public function getColumns()
    {
        return $this->_columns;
    }

    public function getActions()
    {
        return $this->_actions;
    }

    public function getDefaultColConfig($columnId)
    {
        return array(
            'label'      => ucwords(str_replace('_', ' ', $columnId)),
            'id'         => $columnId,
            'sortable'   => true,
            'searchable' => true,
            'render'     => false,
            'class'      => 'row',
            'width'      => 'auto'
        );
    }

    public function getDefaultActConfig($actionId)
    {
        return array(
            'title'      => $actionId,
            'id'         => $actionId,
            'sortable'   => false,
            'searchable' => false,
            'class'      => 'row'
        );
    }

    public function __toString()
    {
        $tableSettings = $this->_tableSettings;
        return $this->render($tableSettings);
    }

    public function render($options = array())
    {
        $options += $this->_tableSettings;
        $options += array(
            'includeJs'  => false,
            'includeCss' => false
        );

        $html = $this->_getTableStructure($options) . "\n\n";
        $html .= $this->_getDependency($options) . "\n\n";
        //$html .= $this->_getTrigger($options) . "\n\n";
        $html .= $this->_getTableDependency($options) . "\n\n";
        return $html;
    }

    protected function _getTableStructure($options = array())
    {
        if ($this->rowSortable)
        {
            if (is_null($this->sortOrderUrl))
            {
                // No order url provided so use data source url.
                $this->sortOrderUrl = $this->dataSourceUrl;
            }
            $this->addAction('sortOrder', array(
                'title'    => 'Save sort order',
                'class'    => 'btn btn-info',
                'type'     => 'form',
                'submitFn' => '$.fn.mydataTable.saveSortOrder',
                'href'     => $this->sortOrderUrl
            ));
        }
        if (is_null($this->tableId))
        {
            $this->tableId = uniqid(get_class($this) . '_');
        }
        $options += array(
            'tableId'         => $this->tableId,
            'headerCellClass' => null,
            'bodyClass'       => null,
            'headerClass'     => null,
            'tableClass'      => 'table datatable-responsive'
        );
        $columns = $this->getColumns();
        $rows = '';
        $colspan = 0;
        if (count($columns))
        {

            // Row Selector
            if ($this->selectable)
            {
                $input = null;
                if ($this->selectType !== self::SELECT_SINGLE)
                {
                    $input = $this->_tag('input', false, array(
                        'type'  => 'checkbox',
                        'name'  => $this->tableId . '_selectall',
                        'id'    => $this->tableId . '_selectall',
                        'class' => 'selectall',
                    ));
                }
                $rows .= $this->_tag('th', $input, array('class' => 'chkwdth rowidentifier'));
                $colspan++;
            }

            // Column list
            $visibleColumns = $this->getVisibleColumns();
            foreach ($columns as $colId => $column)
            {
                if (!in_array($colId, $visibleColumns))
                {
                    continue;
                }
                $rows .= $this->_tag('th', $column['label'], $options, array('class' => 'headerCellClass'));
            }
            $colspan += count($columns);

            // Actions
            if ($this->getActions() && $this->isActionVisible())
            {
                $rows .= $this->_tag('th', 'Action'); // Action column name as blank
                $colspan++;
            }
        }

        $header = $this->_tag('tr', $rows, $options, array('class' => 'headerClass'));
        $header = $this->_tag('thead', $header);
        $body = $this->_tag('td', 'Loading ...', $options + array('colspan' => $colspan), array('colspan'));
        $body = $this->_tag('tr', $body, $options, array('class' => 'bodyClass'));
        $body = $this->_tag('tbody', $body, array('class' => 'row-sort'));
        $footer = $this->_tag('td', '', $options + array('colspan' => $colspan), array('colspan'));
        $footer = $this->_tag('tr', $footer);
        $footer = $this->_tag('tfoot', $footer);
        $footer = null;

        $table = $this->_tag('table', $header . $body . $footer, $options, array('id' => 'tableId', 'class' => 'tableClass'));
        return $this->_appendHeaderActions($options) . "\n" . $table;
    }

    protected function _appendHeaderActions($options = array())
    {
        if (isset($options['noHeader']) && $options['noHeader'] === false)
        {
            return null;
        }
        $actions = $this->getActions();
        $header = null;
        if (isset($actions['HEADER']) && is_array($actions['HEADER']))
        {
            $headerActions = $actions['HEADER'];
            foreach ($headerActions as $action)
            {
                if (isset($action['type']) && $action['type'] == 'form')
                {
                    $confirm = '';
                    if (isset($action['confirm']))
                    {
                        $confirm = $action['confirm'];
                    }
                    if (isset($action['submitFn']) && !empty($action['submitFn']))
                    {
                        $action['onClick'] = sprintf('return %s(this)', $action['submitFn']);
                        unset($action['submitFn']);
                    } else
                    {
                        $action['onClick'] = 'return $.fn.mydataTable.submitCheckForm(\'' . $confirm . '\',this)';
                    }
                    $action['datatable'] = $this->tableId;
                }
                $header .= '<li>' . $this->_tag('a', $action['label'], $action, array(), false) . '</li>' . "\n";
            }
            $header = $this->_tag('ul', $header, array('class' => 'nav nav-tabs'));
            $header = $this->_tag('div', $header, array('class' => 'action-tabs'));
        }
        return $header;
    }

    protected function _getDependency($options = array())
    {
        $dataTableCss = array(
            'href' => '/css/datatables/css/jquery.dataTables.css',
            'rel'  => 'stylesheet',
        );
        $dataTable = array(
            'src'      => '//cdn.datatables.net/1.10.3/js/jquery.dataTables.min.js',
            'language' => 'javascript',
        );
        $myDatatable = array(
            'src'      => '/js/datatables/js/mydt.js',
            'language' => 'javascript',
        );
        $dependency = '';
        if ($options['includeCss'])
        {
            $dependency .= $this->_tag('link', false, $dataTableCss);
        }
        if ($options['includeJs'])
        {
            $dependency .= $this->_tag('script', null, $dataTable);
            $dependency .= $this->_tag('script', null, $myDatatable);
        }
        return $dependency;
    }

    public function getTrigger($options = array())
    {
        $tableId = $this->tableId;
        $settings = $this->_getTableSettings($options);
        $settings = Json::encode($settings);
        $script = <<<TRIGGERSCRIPT
                jQuery(document).ready(function () {
                    window.{$tableId} = $('#{$tableId}').mydataTable({$settings});
                        
                        $('#{$tableId} tbody').on('click', 'tr', function() {
                            var href = $(this).find("a#view").attr('href');
                            if(href){
                                $(location).attr('href', href);
                                return
                            } 
                        });
                        
                        $('.dataTables_length select').select2({
                            minimumResultsForSearch: "-1"
                        });
                        
                }(jQuery));
TRIGGERSCRIPT;
        return $this->_tag('script', $script, array('type'=>'text/javascript','language' => 'javascript'));
    }

    protected function _getTableDependency($options = array())
    {
        return null;
    }

    protected function _getTableSettings($options = array())
    {
        $sDom = $this->sDom;
        $oLanguage = $this->getLanguage();

        if (isset($options['recordLimit']))
        {
            $sDom = 't<"footer"r>';
            $options['perPage'] = ($options['recordLimit'] === false) ? 999 : (int) $options['recordLimit'];
            $oLanguage['sInfo'] = sprintf('Showing top %s records', $options['recordLimit']);
            $oLanguage['sInfoFiltered'] = '';
        }

        $settings = array(
            'sMyInstance'          => $this->tableId,
            'bProcessing'          => true,
            'bStateSave'           => $this->stateSave,
            'bFilter'              => true,
            'selectable'           => $this->selectable,
            'rowIdentifier'        => $this->rowIdentifier,
            'sPaginationType'      => 'simple_numbers',
            'oLanguage'            => $oLanguage,
            'sDom'                 => $sDom,
            'rowSortable'          => $this->rowSortable,
            'rowSortableFieldName' => $this->rowSortableFieldName,
            'iDisplayLength'       => (isset($options['perPage']) ? $options['perPage'] : $this->defaultLength),
        );

        if (!is_null($this->dataSourceUrl))
        {
            $settings['bServerSide'] = false;
            $settings['sAjaxSource'] = $this->dataSourceUrl;
            if (!is_null($this->searchForm))
            {
                $settings['searchForm'] = $this->searchForm;
            }
            if (isset($options['filters']) && is_array($options['filters']))
            {
                $settings['filters'] = $options['filters'];
            } else if ($this->getFilters())
            {
                $settings['filters'] = $this->getFilters();
            }
        }

        $columns = $this->getColumns();
        if ($this->defaultSort)
        {
            $tableColumns = array_keys($columns);
            if (in_array($this->defaultSort, $tableColumns))
            {
                $searchColId = array_search($this->defaultSort, $tableColumns);
                $searchDir = (strtoupper($this->defaultSortDir) === self::ORDER_DESC ? self::ORDER_DESC : self::ORDER_ASC);
                $settings['aaSorting'] = array(array($searchColId, $searchDir));
            } else
            {
                $settings['aaSorting'] = array();
            }
        }
        if (count($columns))
        {
            $settings['aoColumns'] = array();

            // Row Selector
            if ($this->selectable)
            {
                $inputName = !is_null($this->rowIdentifierName) ?
                        $this->rowIdentifierName : $this->_normalise($this->rowIdentifier);
                $inputOptions = array(
                    'type'  => 'checkbox',
                    'name'  => $inputName . '[]',
                    'class' => 'row-check',
                    'value' => "[" . $this->_normalise($this->rowIdentifier) . "]",
                );
                if ($this->selectType === self::SELECT_SINGLE)
                {
                    $inputOptions['type'] = 'radio';
                    $inputOptions['name'] = $inputName;
                }
                $inputOptions = Json::encode($inputOptions);
                $col = array(
                    'mData'       => $this->_normalise($this->rowIdentifier),
                    'sName'       => $this->rowIdentifier,
                    'bSortable'   => false,
                    'bSearchable' => false,
                    'sWidth'      => '20px',
                    'mRender'     => sprintf('function(data,sType,dataRow){ return $.fn.mydataTable.inputBox(%s,dataRow);}', $inputOptions)
                );
                $settings['aoColumns'][] = $col;
            }


            // Normal Columns
            $visibleColumns = $this->getVisibleColumns();
            foreach ($columns as $colId => $column)
            {
                if (!in_array($colId, $visibleColumns))
                {
                    continue;
                }
                $col = array(
                    'mData'       => $this->_normalise($colId),
                    'sName'       => $colId,
                    'bSortable'   => ($column['sortable'] === true && $this->columnSortable === true),
                    'bSearchable' => ($column['searchable'] === true),
                    'sWidth'      => $column['width'],
                );
                if (isset($column['render']) && !empty($column['render']))
                {
                    $renderOptions = array();
                    if (isset($column['renderOptions']))
                    {
                        $renderOptions = (array) $column['renderOptions'];
                    }
                    if ($column['render'] === 'html')
                    {
                        $renderOptions['html'] = $this->_searchAndNormalise($renderOptions['html']);
                    }
                    $column['render'] = self::getRenderer($column['render']);
                    $renderOptions    = Json::encode($renderOptions);
                    $col['mRender']   = sprintf('function(data,sType,dataRow){return %s(data,"%s",%s,dataRow,sType);}', $column['render'], $colId, $renderOptions);
                }
                $settings['aoColumns'][] = $col;
            }

            if (($actions = $this->getActions()) && $this->isActionVisible())
            {
                $actionHtml = $this->_getActionRowHtml();
                $col = array(
                    'mData'       => null,
                    'bSortable'   => false,
                    'bSearchable' => false,
                    'sWidth'      => '10%',
                    'mRender'     => sprintf('function(data,sType,dataRow){ return $.fn.mydataTable.replaceData(\'%s\',dataRow);}', $actionHtml)
                );
                $settings['aoColumns'][] = $col;
            }
        }
        return $settings;
    }

    protected function _normalise($columnId)
    {
        return preg_replace('/\.+/', '_', $columnId);
    }

    protected function _searchAndNormalise($string)
    {
        preg_match_all('/\[(.*?)\]/', $string, $matches);
        if (count($matches))
        {
            foreach ($matches[0] as $match)
            {
                $string = preg_replace('/' . preg_quote($match) . '/', $this->_normalise($match), $string);
            }
        }
        return $string;
    }

    private function _buildAttributes($options, $whiteList = array(), $addSlashes = true, $isTable = false)
    {
        $attribs = array();
        if (count($options))
        {
            $iCount = 0;
            foreach ($options as $optionKey => $optionValue)
            {
                $iCount++;
                $wkey = null;
                if (count($whiteList))
                {
                    if (($wkey = array_search($optionKey, $whiteList)) !== false)
                    {
                        if (is_numeric($wkey))
                        {
                            $wkey = $optionKey;
                        }
                        $attribs[$wkey] = sprintf('%s="%s"', $wkey, addslashes($optionValue));
                    }
                } else
                {
                    $wkey = $optionKey;
                }

                if ($addSlashes && is_string($optionValue))
                {
                    $optionValue = addslashes($optionValue);
                }

                if ($wkey && is_string($optionValue)) {
                    if(!array_key_exists( $wkey,$attribs)){
                        $attribs[] = sprintf('%s="%s"', $wkey, $optionValue);
                    }
                }
            }
        }
        return implode(' ', $attribs);
    }

    protected function _getActionsRowHtml()
    {
        
    }

    protected function _getActionRowHtml($options = array())
    {
        if (!($actions = $this->getActions()) && $this->isActionVisible())
        {
            return null;
        }

        $containerOptions = array(
            'class' => 'icons-list'
        );
        unset($actions['HEADER']);
        $actionHtml = null;
        foreach ($actions as $actId => $action)
        {
            $href = isset($action['href']) ? $action['href'] : '#';
            $href = $this->_searchAndNormalise($href);
            if (isset($action['confirm']))
            {
                $action['onClick'] = 'return confirm(\'' . $action['confirm'] . '\')';
            }
            $action['href'] = $href;
            unset($action['confirm']);
            $aHref = $this->_tag('a', $action['label'], $action) . " ";
            $deleteHtml = "";
            if ($actId == "delete")
            {
                $formAction = $action["form_action"];
                $deleteHtml = '<li><form method="POST" '
                        . 'action="' . $formAction . '" accept-charset="UTF-8"'
                        . 'name="delete" class="form-inline pull-right">'
                        . '<input name="_method" type="hidden" value="DELETE">'
                        . '<input name="_token" type="hidden" value="' . Session::getToken() . '">'
                        . $aHref .
                        '</form></li>';
            } else
            {
                $actionHtml .= $this->_tag('li', $aHref);
            }
            $actionHtml .= $deleteHtml;
        }
        return $this->_tag('ul', $actionHtml, $containerOptions);
    }

    protected function _tag($tag, $data, $options = array(), $attributes = array(), $addSlashes = true)
    {
        $innerHtmlTag = '';
        $outerHtmlTag = '';
        $title = '';
        if (isset($options['innerHtmlTag']) && $options['innerHtmlTag'] != '')
        {
            $innerHtmlTag = $options['innerHtmlTag'];
            unset($options['innerHtmlTag']);
        }
        if (isset($options['outerHtmlTag']) && $options['outerHtmlTag'] != '')
        {
            $outerHtmlTag = $options['outerHtmlTag'];
            unset($options['outerHtmlTag']);
        }

        if($tag == 'table'){
            $attribs = $this->_buildAttributes($options, $attributes, $addSlashes,true);
        } else {
            $attribs = $this->_buildAttributes($options, $attributes, $addSlashes,false);
        }
        if (isset($options['title']) && $options['title'] != '')
        {
            $title = 'title="' . $options['title'] . '"';
            unset($options['title']);
            $attribs .= ' ' . $title;
        }
        if ($data === false)
        {
            return sprintf('<%1$s %2$s />', $tag, $attribs);
        }

        return sprintf('<%1$s %3$s>%5$s%2$s%4$s</%1$s>', $tag, $data, $attribs, $innerHtmlTag, $outerHtmlTag);
    }

    public static function getRenderer($renderer)
    {
        $renderers = array(
            'date'       => '$.fn.mydataTable.date',
            'html'       => '$.fn.mydataTable.html',
            'tags'       => '$.fn.mydataTable.tags',
            'cropString' => '$.fn.mydataTable.cropString',
        );
        return isset($renderers[$renderer]) ? $renderers[$renderer] : $renderer;
    }

    public function getVisibleColumns()
    {
        $columns = $this->getColumns();
        if ($this->visibleColumns === null)
        {
            $this->visibleColumns = array_keys($columns);
            if ($this->getActions())
            {
                $this->visibleColumns[] = '__actions';
            }
        }
        return $this->visibleColumns;
    }

    public function isActionVisible()
    {
        $visibleColumns = $this->getVisibleColumns();
        return in_array('__actions', $visibleColumns);
    }

    public function setFilters($filters = array())
    {
        $this->_tableSettings['filters'] = array_merge($this->getFilters(), $filters);
        return $this;
    }

    public function getFilters()
    {
        return isset($this->_tableSettings['filters']) ? $this->_tableSettings['filters'] : array();
    }

    public function setLimit($recordLimit)
    {
        $this->_tableSettings['recordLimit'] = $recordLimit;
        return $this;
    }

    public function getLanguage()
    {
        return array(
            'sEmptyTable' => $this->emptyText
        );
    }

}
