<?php

if (!defined('BASEPATH'))
    exit('No direct script access allowed');

// Load simple types
require_once 'vendor/ciext/rtgrid/models/rtgrid_simple_types.php';

/**
 * Description of rtgrid_model
 *
 * @author Igor Ratsun
 */
class rtgrid_model extends \Ciext\Model\AbstractEventModel
{

    /**
     * Item ids
     * @var array
     */
    public $itemId = array();

    /**
     * Current action
     * @var string
     */
    public $current_action = '';

    /**
     * Callback of action
     * @var mixed
     */
    public $current_action_return;

    /**
     * Callback of before action
     * @var mixed
     */
    public $current_before_action_return;
    protected $_table;
    protected $_unique;
    protected $_primery_key;
    protected $_columns;
    protected $_columns_array = array();
    protected $_display_as;
    protected $_callback_column;
    protected $_record_limit = 25;
    protected $_record_offset = 0;
    protected $_record_count = 0;
    protected $_order_by;
    protected $_order_type;
    protected $_where = array();
    protected $_where_in = array();
    protected $_join = array();
    protected $_actions = array();
    protected $_top_actions = array();
    protected $_before_action = array();
    protected $_multilang = array();
    protected $_image_uploads = array();
    protected $_jsDynActionData = ''; // This data add to js method dynAction();
    protected $_multifield = array();
    protected $tooltip = array();
    protected $template = array(
        'main'        => 'rtgrid/template',
        'rows'        => 'rtgrid/rows',
        'row_actions' => 'rtgrid/row_actions'
    );
    protected $_filter = array();

    public function __construct()
    {
        $this->load->config('rtgrid/config');

        $this->_record_limit = $this->config->item('default_per_page');

        // Set default actions and top actions
        $this->load->model('rtgrid/rtgrid_action_model');
        $this->add_top_action(new rtgrid_top_action('add', '<a href="/' . $this->uri->uri_string() . '/edit/add" class="btn"><i class="icon-plus"></i> ' . $this->lang->line('Add') . '</a>'));
        $this->add_top_action(new rtgrid_top_action('delete', '<i class="icon-trash"></i> ' . $this->lang->line('Delete'), array($this->rtgrid_action_model, 'delete'), $this->lang->line('Delete this data?')));
        $this->add_action(new rtgrid_action('edit', '<a href="/' . $this->uri->uri_string() . '/edit/{primery_key}"><i class="icon-pencil"></i> ' . $this->lang->line('Edit') . '</a>'));
        $this->add_action(new rtgrid_action('delete', '<i class="icon-trash"></i> ' . $this->lang->line('Delete'), array($this->rtgrid_action_model, 'delete'), $this->lang->line('Delete this data?')));
    }

    /**
     * Set public and protected property of class
     * @param array $options
     * Ex. array(
     *  'template' => array('main' => 'rtgrid/template', 'rows' => 'rtgrid/rows')
     * )
     */
    public function set_options($options = array())
    {
        foreach ($options as $opt => $opt_value) {
            if (isset($this->{$opt})) {
                if (is_array($opt_value) && is_array($this->{$opt})) {
                    $this->{$opt} = array_merge($this->{$opt}, $opt_value);
                } else {
                    $this->{$opt} = $opt_value;
                }
            } else {
                show_error('Property `' . $opt . '` not found');
            }
        }
    }

    /**
     * Get public and protected property of class
     * @param $opt
     * @return mixed|null
     */
    public function get_option($opt)
    {
        return $this->{$opt} ?: null;
    }

    public function set_table($table = '')
    {
        $this->_table = $table;
        $this->_unique = substr(md5($table . $this->config->item('encryption_key')), 0, 8);
    }

    public function get_table()
    {
        return $this->_table;
    }

    public function set_primery_key($primery_key = '')
    {
        $this->_primery_key = $primery_key;
    }

    public function set_order_by($column, $direction = 'ASC')
    {
        if (empty($this->_order_by)) {
            $this->_order_by = $column;
        }

        if (empty($this->_order_type)) {
            $this->_order_type = $direction;
        }
    }

    public function columns($columns = '')
    {
        $this->_columns_array = array();

        foreach (explode(',', $columns) as $value) {
            $value = trim($value);
            $key = $this->_column_alias($value);
            $this->_columns_array[$key] = new stdClass();
            $this->_columns_array[$key]->real_name = $value;
            $this->_columns_array[$key]->short_name = preg_replace('/(.*\sas\s)?(.*\.)?/', '', $value);
            switch ($this->db->dbdriver) {
                case 'postgre':
                    $this->_columns_array[$key]->search_name = str_replace('.', '"."', preg_replace('/(\sas\s.*)/', '', $value));
                    break;

                default :
                    $this->_columns_array[$key]->search_name = preg_replace('/(\sas\s.*)/', '', $value);
                    break;
            }
        }

        $this->_columns = $columns;
    }

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

    public function display_as($column, $value)
    {

        $this->_display_as[$column] = $value;
    }

    /**
     * Set tooltip to column/field
     * @param string $field column/field name
     * @param string $html html or text
     * @param bool $grid show in grid or not
     * @return void
     */
    public function tooltip($column, $html, $grid)
    {
        if ($grid) {
            $this->tooltip[$column] = $html;
        }
    }

    public function callback_column($column, $callback)
    {
        $this->_callback_column[$column] = $callback;
    }

    public function where($key, $value = NULL, $escape = TRUE)
    {
        $where = new stdClass();
        $where->key = $key;
        $where->value = $value;
        $where->escape = $escape;

        $this->_where[] = $where;
    }

    public function where_in($key, $value = NULL, $escape = TRUE)
    {
        $where_in = new stdClass();
        $where_in->key = $key;
        $where_in->value = $value;
        $where_in->escape = $escape;

        $this->_where_in[] = $where_in;
    }

    public function join($table, $cond, $type = 'left')
    {
        $join = new stdClass();
        $join->table = $table;
        $join->cond = $cond;
        $join->type = $type;

        $this->_join[] = $join;
    }

    public function record_count()
    {
        if (empty($this->_record_count)) {
            $this->_run_query_rules();
            $this->_record_count = $this->db->count_all_results($this->_table);
        }

        return $this->_record_count;
    }

    public function column_callback($column, $func)
    {
        $this->_column_callback[$column] = $func;
    }

    public function add_action(rtgrid_action $data)
    {
        $this->_actions[$data->get_name()] = $data;
    }

    public function add_top_action(rtgrid_top_action $data)
    {
        $this->_top_actions[$data->get_name()] = $data;
    }

    public function unset_action($action_name)
    {
        unset($this->_actions[$action_name]);
    }

    public function unset_top_action($action_name)
    {
        unset($this->_top_actions[$action_name]);
    }

    public function before_action($action, $callback)
    {
        $this->_before_action[$action][] = $callback;
    }

    /**
     * Add multilang field
     * @param multilang $multilang
     * @return boolean
     */
    public function multilang(multilang $multilang)
    {
        if (!empty($this->_columns_array[$this->_column_alias($multilang->get_data('field'))])) {
            $this->load->library('db_lang_lib');
            $new_column = $multilang->get_data('lang_table') . '.' . $multilang->get_data('lang_table_field') . ' as ' . $multilang->get_data('field');
            $this->columns(str_replace($multilang->get_data('field'), $new_column, $this->_columns));

            if (empty($this->_multilang[$multilang->get_data('lang_table')])) {
                $this->_multilang[$multilang->get_data('lang_table')][] = $multilang;
                $id_lan = $this->db_lang_lib->get_current_lang_id();
                $pk = $this->_primery_key;
                $this->join($multilang->get_data('lang_table'), $multilang->get_data('lang_table') . '.' . $pk . '=' . $this->_table . '.' . $pk . ' AND ' . $multilang->get_data('lang_table') . '.id_lan=' . $id_lan, 'left');
            }

            // Set callback
            $this->callback_column($multilang->get_data('field'), [new \Rtgc_input(), 'column']);

            return TRUE;
        }

        // Multilang. Delete all related records before delete main record
        $pkey = $this->_primery_key;
        $this->before_action('delete', function ($items = array()) use ($pkey, $multilang) {
            $ci = &get_instance();
            $db = clone $ci->db;
            $db->where_in($pkey, $items)->delete($multilang->get_data('lang_table'));

            return TRUE;
        });

        return FALSE;
    }

    public function set_relation($field_name, $related_table, $related_title_field, $where, $order_by)
    {
        // Get table pkey
        $db = clone $this->db;
        $fields = $db->list_fields($related_table);
        $pkey_array = array(
            $field_name,
            'id',
            'id_' . $related_table,
            $related_table . '_id',
            'id_' . $field_name,
            $field_name . '_id',
        );
        foreach ($fields as $field) {
            $key = array_search($field, $pkey_array);
            if ($key !== FALSE) {
                $primery_field = $pkey_array[$key];
                break;
            }
        }

        if (empty($primery_field)) {
            $message = $this->lang->line('Primary key of table %s not found. Method set_relation()');
            show_error(sprintf($message, $related_table));
        }

        if (is_array($where)) {
            $rs = '';
            foreach ($where as $key => $val) {
                $rs .= ' AND ' . $key . '=' . $val;
            }
            $where = ltrim($rs, ' AND ');
        }
        $table_alias = 't_' . substr(md5($field_name), 0, 6);
        $ext = (!is_null($where)) ? ' AND ' . str_replace($related_table . '.', $table_alias . '.', $where) : '';
        $new_column = $table_alias . '.' . $related_title_field . ' as ' . $field_name;
        $columns = preg_replace('/(' . $field_name . ')([\s,]+)/', $new_column . '$2', $this->_columns);
        $this->columns($columns == $this->_columns ? preg_replace('/' . $field_name . '$/', $new_column, $columns) : $columns);
        $this->join($related_table . ' as ' . $table_alias, $table_alias . '.' . $primery_field . '=' . $this->_table . '.' . $field_name . $ext);

        $this->callback_column($field_name, [new \Rtgc_input(), 'column']);
    }

    public function set_relation_n_n($field_name, $mapping_table, $selected_table, $selected_table_alias_field)
    {
        $pkey = $this->_primery_key;
        $this->before_action('delete', function ($items = array()) use ($mapping_table, $pkey) {
            $ci = &get_instance();
            $ci->db
                ->where_in($pkey, $items)
                ->delete($mapping_table);

            return TRUE;
        });
    }

    public function add_photogallery(photogallery $photogallery)
    {
        $main_table = $photogallery->get_table();
        $foreign_key = $this->_primery_key;
        $photo_path = $photogallery->get_photo_path();

        $this->before_action('delete', function ($items) use ($main_table, $foreign_key, $photo_path) {
            $CI = &get_instance();
            $CI->load->module('alphagallery/alphagallery_admin');
            $config['main_table'] = $main_table;
            $config['foreign_key'] = $foreign_key;
            $config['photo_path'] = $photo_path;

            foreach ($items as $id) {
                $config['foreign_key_value'] = $id;
                $CI->alphagallery_admin->empty_gallery($config);
            }
        });
    }

    public function render()
    {
        $this->notify('PRE_RENDER');

        // Ajax
        if ($this->input->post('unique')) {

            if ($this->input->post('unique') != $this->_unique) {

                return FALSE;
            }

            // Set predicates
            $page = explode('/', $this->input->post('page'));
            $this->_record_offset = intval(end($page));
            $this->_record_limit = intval($this->input->post('perpage'));
            $o_explode = explode('/', $this->input->post('order'));
            $order = end($o_explode);
            $this->_order_by = (!empty($this->_columns_array[$order])) ? $this->_columns_array[$order]->search_name : $this->_order_by;
            $ordertype = intval($this->input->post('ordertype'));
            $this->_order_type = (!empty($ordertype)) ? 'DESC' : 'ASC';
            $this->_filter = [];
            foreach ((array)$this->input->post('filter') as $key => $value) {
                if (isset($this->_columns_array[$key])) {
                    $this->_filter[$this->_columns_array[$key]->short_name] = $value;
                }
            }

            // If action
            $this->current_action = $this->input->post('action');
            $action_list = array_merge($this->_actions, $this->_top_actions);
            if (!empty($this->current_action) && !empty($action_list[$this->current_action])) {
                // Prepare default actions model
                $this->rtgrid_action_model->table = $this->_table;
                $this->rtgrid_action_model->primery_key = $this->_primery_key;
                $this->rtgrid_action_model->where = $this->_where;

                $this->itemId = array_map('intval', array_keys($this->input->post('itemId')));

                // Callback before action
                if (!empty($this->_before_action[$this->current_action])) {
                    $this->current_before_action_return = FALSE;
                    foreach ($this->_before_action[$this->current_action] as $b_a_item) {
                        $tmp = call_user_func($b_a_item, $this->itemId);
                        $this->current_before_action_return = ($tmp) ?: $this->current_before_action_return;
                    }
                }

                //remove upload images
                if (!empty($this->_image_uploads) && $this->current_action == 'delete') {
                    $this->remove_image();
                }

                // Action callback
                $callback = $action_list[$this->current_action]->get_callback();
                if (!empty($callback)) {
                    $this->current_action_return = call_user_func($callback, $this->itemId);
                }
            }

            $pagination = $this->_get_pagination();
            $rs = array(
                'data'        => $this->parser->parse($this->template['rows'], array('row' => $this->_get_rows()), TRUE),
                'pagination'  => $pagination,
                'recordCount' => $this->record_count(),
            );

            // Notify observers
            $this->notify('POST_RENDER_AJAX');

            die(json_encode($rs));
        }

        $this->load->helper('form');
        $content = array(
            'form_open'              => form_open(),
            'unique'                 => $this->_unique,
            'top_action'             => $this->_get_top_actions(),
            'search_text_max_length' => $this->config->item('search_text_max_length'),
            'pagination'             => $this->_get_pagination(),
            'form_close'             => form_close(),
            'record_count'           => $this->record_count(),
        );

        // Add columns
        if (!empty($this->_top_actions)) {
            $content['column'][] = array(
                'value'   => '<input type="checkbox" class="check_all"/>',
                'style'   => 'style="width:3%;"',
                'tooltip' => ''
            );
        }
        $href = '/' . preg_replace('/\/page\/d+?/', '', $this->uri->uri_string());
        foreach ($this->_columns_array as $value) {
            $alias = $this->_column_alias($value->real_name);
            $name = (!empty($this->_display_as[$value->real_name])) ?
                $this->_display_as[$value->real_name] :
                (!empty($this->_display_as[$value->short_name]) ?
                    $this->_display_as[$value->short_name] :
                    ucfirst(preg_replace('/[\_\-]/', ' ', $value->short_name)));

            if ($this->_order_by == $this->_columns_array[$alias]->search_name) {

                $arrow = ($this->_order_type == 'DESC') ? 'arrow-down' : 'arrow-up';
            } else {
                $arrow = '';
            }

            $content['column'][] = array(
                'value'   => '<a class="orderBy ' . $arrow . '" href="' . $href . '/' . $alias . '">' . $name . '</a>',
                'style'   => '',
                'tooltip' => $this->render_tooltip($value->short_name, $name),
                'filter'  => $this->render_filter($value),
            );
        }

        // Add action column
        if (!empty($this->_actions)) {
            $content['column'][] = array(
                'value'   => '<a href="#" class="btn btn-small en_filter"><i class="fa fa-filter"></i></a>',
                'style'   => 'style="width: 75px"',
                'tooltip' => '',
            );
        }

        // Add rows
        $content['rows'] = $this->parser->parse($this->template['rows'], array('row' => $this->_get_rows()), TRUE);

        // Add per page
        $per_page = $this->config->item('per_page_items');
        array_unshift($per_page, $this->lang->line('All'));
        foreach ($per_page as $value) {
            $content['per_page'][] = array(
                'value'    => $value,
                'selected' => ($value == $this->_record_limit || ($value == $this->lang->line('All') && $this->_record_limit == 0)) ? 'selected="selected"' : ''
            );
        }

        // Load jquery
        $this->ciext->load_jquery();
        // Add js
        $js_content = array(
            'unique' => $this->_unique
        );
        $this->js_css->parse_js('rtgrid/dynAction.js', array_merge($js_content, array('_jsDynActionData' => $this->_jsDynActionData)));
        $this->js_css->parse_js('rtgrid/main.js', $js_content);

        // Add CSS
        $this->js_css->add_css_string($this->parser->parse('rtgrid/style.css', array(), TRUE));

        $this->notify('POST_RENDER');

        return $this->parser->parse($this->template['main'], $content, TRUE);
    }

    public function export($filter = [])
    {
        $this->_filter = [];
        foreach ($filter as $key => $value) {
            if (isset($this->_columns_array[$key])) {
                $this->_filter[$this->_columns_array[$key]->short_name] = $value;
            }
        }

        $this->_run_query_rules();

        return $this->db->select($this->_columns)->get($this->_table)->result_array();
    }

    protected function _column_alias($column_name)
    {
        return substr(md5($this->_unique . $column_name), 0, 8);
    }

    protected function _get_actions($row = array())
    {
        if (empty($this->_actions)) {

            return '';
        }

        return $this->parser->parse($this->template['row_actions'], [
            'actions' => $this->_actions,
            'row'     => $row,
        ], true);
    }

    protected function _get_top_actions()
    {
        $rs = '';
        foreach ($this->_top_actions as $action) {
            $rs .= $action->get_template();
        }

        return $rs;
    }

    protected function _run_query_rules()
    {
        // Add where
        foreach ($this->_where as $where) {
            $this->db->where($where->key, $where->value, $where->escape);
        }
        // Add where_in
        foreach ($this->_where_in as $where_in) {
            $this->db->where_in($where_in->key, $where_in->value, $where_in->escape);
        }
        // Add join
        foreach ($this->_join as $join) {
            $this->db->join($join->table, $join->cond, $join->type);
        }
        // Add search
        foreach ($this->_columns_array as $column) {
            if (is_null($this->get_filter_value($column->short_name))) {
                continue;
            }

            if (isset($this->_callback_column[$column->short_name][0]) && $this->_callback_column[$column->short_name][0] instanceof \Rtgc_filtrable) {
                $obj = $this->_callback_column[$column->short_name][0];
                $obj->filter_by($column->search_name, $this->get_filter_value($column->short_name));
            }
        }
    }

    protected function _get_pagination()
    {
        // Add pagination
        $this->load->library('pagination');
        $config['base_url'] = '/' . preg_replace('/\/page\/d+?/', '', $this->uri->uri_string()) . '/page/';
        $config['total_rows'] = $this->record_count();
        $config['per_page'] = $this->_record_limit;
        $config['cur_page'] = $this->_record_offset;
        $config['next_link'] = '>>';
        $config['prev_link'] = '<<';
        $config['cur_tag_open'] = '<li class="active"><a href="#">';
        $config['cur_tag_close'] = '</a></li>';
        $config['num_tag_open'] = '<li>';
        $config['num_tag_close'] = '</li>';
        $config['next_tag_open'] = '<li>';
        $config['next_tag_close'] = '</li>';
        $config['prev_tag_open'] = '<li>';
        $config['prev_tag_close'] = '</li>';
        $config['first_tag_open'] = '<li>';
        $config['first_tag_close'] = '</li>';
        $config['last_tag_open'] = '<li>';
        $config['last_tag_close'] = '</li>';
        $this->pagination->initialize($config);

        return $this->pagination->create_links();
    }

    protected function _get_rows()
    {
        $rs = array();

        // Add rules
        $this->_run_query_rules();

        // Add order by
        if (!empty($this->_order_by)) {
            $this->db->order_by($this->_order_by, $this->_order_type);
        } else {
            $this->db->order_by($this->_primery_key, 'ASC');
        }

        // Get date
        $this->db->
        select($this->_table . '.' . $this->_primery_key . ', ' . $this->_columns);
        $data = ($this->_record_limit == 0) ? $this->db->get($this->_table) : $this->db->get($this->_table, $this->_record_limit, $this->_record_offset);

        // Add date
        foreach ($data->result_array() as $row) {
            $col = array();

            if (!empty($this->_top_actions)) {
                $col[] = array(
                    'value' => '<input type="checkbox" name="itemId[' . $row[$this->_primery_key] . ']"/>'
                );
            }

            foreach ($this->_columns_array as $value) {
                $col[] = array(
                    'value' => (!empty($this->_multifield[$value->short_name])) ? $this->show_multifield($value->short_name, $row) : ((!empty($this->_callback_column[$value->short_name])) ? call_user_func($this->_callback_column[$value->short_name], $row[$value->short_name], $row) : $row[$value->short_name])
                );
            }

            //Add actions
            if (!empty($this->_actions)) {
                $col[] = array(
                    'value' => $this->_get_actions($row)
                );
            }

            $rs[] = array(
                'col' => $col
            );
        }

        return $rs;
    }

    public function set_image_upload(image_upload $data)
    {
        if (!is_dir(FCPATH . $data->get_path())) {
            show_error(sprintf($this->lang->line('Folder %s for field %s does not exist. Please create a new folder and try again.'), FCPATH . $data->get_path(), $data->get_field()));
        }

        // Load js
        $this->load->library('ciext/ciext')->load_jquery();
        $this->js_css->add_js('/' . $this->config->item('assets_folder') . '/plugins/fancybox/js/jquery.fancybox-1.3.4.js');
        $this->js_css->add_css('/' . $this->config->item('assets_folder') . '/plugins/fancybox/css/jquery.fancybox.css');
        $this->js_css->parse_js('rtgrid/edit/image_upload/js/column.js');

        $this->_image_uploads[$data->get_field()] = $data;
        $this->_jsDynActionData = str_replace('rtgc_upload_image_refresh();', '', $this->_jsDynActionData) . 'rtgc_upload_image_refresh();';

        $image = (new Rtgc_upload_image([
            'upload_path' => './' . trim(ltrim($data->get_path(), '.'), '/'),
        ]));
        $this->callback_column($data->get_field(), [$image, 'column']);

        $pkey = $this->_primery_key;
        $table = $this->_table;
        $field = $data->get_field();
        $this->before_action('delete', function ($ids = []) use ($image, $pkey, $table, $field) {
            $ci = &get_instance();
            $q = $ci->db->
            select($field)->
            where_in($pkey, $ids)->
            get($table)->result_array();
            foreach ($q as $item) {
                $image->remove_file($item[$field]);
            }

            return true;
        });
    }

    public function remove_image()
    {
        $fileds = array_keys($this->_image_uploads);

        $sql = $this->db->
        select($fileds)->
        where_in($this->_primery_key, $this->itemId)->
        get($this->_table)->
        result_array();

        if (empty($sql)) {
            return FALSE;
        }

        foreach ($sql as $value) {
            foreach ($fileds as $field) {
                if (!isset($value[$field])) {
                    continue;
                }
                $photo = ltrim($this->_image_uploads[$field]->get_path() . '/' . $value[$field], '/');
                if (file_exists($photo) && !is_dir($photo)) {
                    unlink($photo);
                }
            }
        }

        return TRUE;
    }

    public function multifield($field)
    {
        $this->_multifield[$field] = $field;
    }

    public function show_multifield($short_name, $row)
    {
        $field_data = json_decode(htmlspecialchars_decode($row[$short_name]));

        if (!empty($field_data) && is_array($field_data)) {

            $column_value = implode('; ', $field_data);

            return (!empty($this->_callback_column[$short_name])) ? call_user_func($this->_callback_column[$short_name], $column_value, $row) : $column_value;
        } else {
            return '';
        }
    }

    /**
     * Render tooltip
     *
     * @param string $field
     * @param string $name
     * @return string
     */
    protected function render_tooltip($field, $name)
    {
        $result = '';

        if (!empty($this->tooltip[$field])) {
            $rtgrid_tooltip = $this->load->library('rtgrid/rtgrid_tooltip');
            $result = $rtgrid_tooltip->render_tooltip($field, $name, $this->tooltip[$field]);
        }

        return $result;
    }

    protected function render_filter($field)
    {
        if (isset($this->_callback_column[$field->short_name])
            && is_array($this->_callback_column[$field->short_name])
            && $this->_callback_column[$field->short_name][0] instanceof \Rtgc_filtrable) {
            $obj = $this->_callback_column[$field->short_name][0];
            $name = 'filter[' . $this->_column_alias($field->real_name) . ']';
            $value = $this->get_filter_value($field->short_name);

            return $obj->filter($name, $value);
        }

        return '';
    }

    protected function get_filter_value($short_name)
    {
        return isset($this->_filter[$short_name]) ? $this->_filter[$short_name] : null;
    }
}
