<?php

/**
 * Questo file è parte di Pongho.
 *
 * @author    Daniele Termini
 * @copyright Web Agency Meta Line S.r.l.
 * @package   Application\Admin
 */

namespace Application\Admin\Utilities;

use Pongho\Http\Request;

class TablesManager implements \Stringable
{
    /**
     * @var array
     * @example
     * Definizione di una colonna:
     * array(
     *   'name'  => 'column',
     *   'label' => 'Colonna',
     *   'orderable' => true, // opzionale, indica se la colonna può essere ordinata
     *   'class' => 'classe-css', // opzionale, classe per la colonna (es. main, large, id, ckb)
     *
     *   // queste vengono compilate dall'ArchiveManager
     *   'order' => array(  // opzionale
     *     'class' => 'ui-icon ui-blablabla', // indica l'ordinamento corrente (per visualizzare la freccina)
     *     'link'  => 'http://sitename.it/pongho/filter/?o[column]=DESC', // il prossimo ordinamento della sequenza [null, 'ASC', 'DESC']
     *   ),
     * )
     */
    protected $columns;

    /**
     * @var \Pongho\Http\Request
     */
    protected $request;

    /**
     * @var array
     */
    protected $config = [
        'columns'        => [],
        'add_url'        => false,
        'add_first_text' => 'Add first row',
        'no_results'     => 'No results found',
        'show_ckb'       => true,
        'show_add'       => true,
    ];

    /**
     * @var array
     */
    protected $rows;

    public static $directions = [
        null   => 'ASC',
        'ASC'  => 'DESC',
        'DESC' => null,
    ];

    public static $arrows = [
        null   => 'ui-icon-triangle-2-n-s',
        'ASC'  => 'ui-icon-triangle-1-n',
        'DESC' => 'ui-icon-triangle-1-s',
    ];

    /**
     * @param array|\Traversable   $rows
     * @throws \LogicException
     */
    public function __construct(array $config, Request $request, $rows = null)
    {
        if (!isset($config['columns'])) {
            throw new \LogicException('Please define the table columns in the controller');
        }

        $this->request = $request;

        $this->setConfig($config);

        if ($rows !== null) {
            $this->setRows($rows);
        }
    }

    /**
     * @return $this
     */
    public function setConfig(array $config)
    {
        $this->config = array_merge($this->config, $config);

        if (isset($config['columns'])) {
            $this->columns = $this->parseColumns($config['columns']);
        }

        return $this;
    }

    /**
     * @param array|callable $cols
     * @throws \InvalidArgumentException
     * @return array
     */
    protected function parseColumns($cols)
    {
        $columns = is_callable($cols) ? call_user_func($cols) : $cols;

        if (!is_array($columns)) {
            throw new \InvalidArgumentException(
                sprintf(
                    'The "cols" attribute must be an Array or a valid callback returning an Array, %s given',
                    gettype($columns)
                )
            );
        }

        foreach ($columns as $name => $column) {
            if (isset($column['orderable']) && $column['orderable']) {
                $query = $this->request->query->all();

                $orders = [];
                if (isset($query['o'])) {
                    $orders = $query['o'];
                    unset($query['o']);
                }

                $direction = null;

                if (isset($orders[$name])) {
                    $direction = $orders[$name];

                    if (($next_direction = static::$directions[$direction]) !== null) {
                        $query['o'][$name] = $next_direction;
                    }
                } else {
                    $query['o'][$name] = static::$directions[$direction];
                }

                $columns[$name]['order']['link'] = current_url() . '?' . http_build_query($query);
                $columns[$name]['order']['class'] = 'ui-icon ' . static::$arrows[$direction];
            }
        }

        return $columns;
    }

    /**
     * @return array
     */
    public function getColumns()
    {
        return $this->columns;
    }

    /**
     * @param array|\Traversable $rows
     * @return $this
     */
    public function setRows($rows)
    {
        if (!is_array($rows) && !$rows instanceof \Traversable) {
            throw new \InvalidArgumentException(
                sprintf('%s expects an array or an instance of Traversable.', __METHOD__)
            );
        }

        $this->rows = $this->parseRows($rows);

        return $this;
    }

    /**
     * @param array|\Traversable $rows
     * @return array
     */
    protected function parseRows($rows)
    {
        return $rows;
    }

    /**
     * @return string
     */
    public function renderHeader()
    {
        $code = '';

        if ($this->config['show_ckb']) {
            $code .= '<th class="ckb"><input type="checkbox" name="checker" /></th>' . PHP_EOL;
        }

        foreach ($this->columns as $column) {
            $class = '';
            if (isset($column['class'])) {
                $class = ' class="' . $column['class'] . '"';
            }

            $label = $column['label'];
            if (isset($column['order'])) {
                $label = '<a class="orderable" href="' . $column['order']['link'] . '">'
                    . $label . '<span class="' . $column['order']['class'] . '"></span></a>';
            }

            $code .= '<th' . $class . '>' . $label . '</th>' . PHP_EOL;
        }

        return $code;
    }

    /**
     * @return string
     */
    public function renderBody()
    {
        $code = '';

        if (empty($this->rows)) {
            $colspan = count($this->columns);

            if ($this->config['show_ckb']) {
                $colspan++;
            }

            $code .= '<tr><td colspan="' . $colspan . '" class="empty">' . $this->config['empty_recordset'] . '</td></tr>';
        } else {
            foreach ($this->rows as $row) {
                $attributes = [];
                if (isset($this->config['row_attributes']) && is_callable($this->config['row_attributes'])) {
                    $result = call_user_func($this->config['row_attributes'], $row);

                    if (is_array($result)) {
                        foreach ($result as $key => $value) {
                            $attributes[] = sprintf(' %s="%s"', $key, $value);
                        }
                    }
                }

                $code .= '<tr' . implode('', $attributes) . '>';

                if ($this->config['show_ckb']) {
                    $code .= '<td class="ckb"><input type="checkbox" name="ids[' . $this->extractId($row) . ']" /></td>';
                }

                foreach ($this->columns as $column_name => $column) {
                    $class = '';
                    if (isset($column['class'])) {
                        $class = ' class="' . $column['class'] . '"';
                    }

                    if (isset($column['render']) && is_callable($column['render'])) {
                        $cell = call_user_func($column['render'], $row);
                    } else {
                        $cell = $row->$column_name;
                    }

                    $code .= '<td' . $class . '>' . $cell . '</td>';
                }

                $code .= '</tr>';
            }
        }

        return $code;
    }

    /**
     * @return string
     */
    public function render()
    {
        $headers = $this->renderHeader();
        $table = $this->renderBody();

        return <<<HTML
    <table class="pongho-table">
        <thead>
            <tr>
                {$headers}
            </tr>
        </thead>
        <tfoot>
            <tr>
                {$headers}
            </tr>
        </tfoot>
        <tbody>
            {$table}
        </tbody>
    </table>
HTML;
    }

    public function __toString(): string
    {
        return $this->render();
    }

    /**
     * @param array|object $var
     * @return mixed
     */
    private function extractId($var)
    {
        if (is_array($var)) {
            return $var['id'] ?? null;
        }

        if (is_object($var)) {
            if (method_exists($var, 'getId')) {
                return $var->getId();
            }

            if (method_exists($var, '__get')) {
                return $var->id;
            }

            return $var->id ?? null;
        }

        return null;
    }
}
