<?php

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

// Load simple types
require_once __DIR__ . '/rtgrid_simple_types.php';

/**
 * Description of rtgrid_model
 *
 * @author Igor Ratsun
 */
class rtgrid_model extends CI_Model implements SplSubject {

    /**
     * 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 $_order_by;
    protected $_order_type;
    protected $_where = array();
    protected $_where_in = array();
    protected $_join = array();
    protected $_search_column;
    protected $_search_text = '';
    protected $_actions = array();
    protected $_top_actions = array();
    protected $_before_action = array();
    protected $observers = 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',
    );

    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?')));

        // Get filter data
        $filter_data = $this->session->userdata('rtgrid_filter');

        if (!empty($filter_data[$this->uri->uri_string()])) {
            foreach ($filter_data[$this->uri->uri_string()] as $field => $value) {
                $this->{$field} = $value;
            }
        }
    }
    
    /**
     * 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})) {
                $this->{$opt} = $opt_value;
            } else {
                show_error('Property `' . $opt . '` not found');
            }
        }
    }

    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 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() {
        $this->_run_query_rules();

        return $this->db->count_all_results($this->_table);
    }

    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 observer to grid
     * @param SplObserver $obs
     */
    public function attach(SplObserver $obs) {
        $id = spl_object_hash($obs);
        $this->observers[$id] = $obs;
    }

    /**
     * Remove observer from grid
     * @param SplObserver $obs
     */
    public function detach(SplObserver $obs) {
        $id = spl_object_hash($obs);
        unset($this->observers[$id]);
    }

    /**
     * Notify observers
     */
    public function notify() {
        foreach ($this->observers as $obs) {
            $obs->update($this);
        }
    }

    /**
     * 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');
            }

            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);
    }
    
    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() {

        // Ajax
        if (!empty($_POST['unique'])) {

            if ($_POST['unique'] != $this->_unique) {

                return FALSE;
            }

            // 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);
                }
            }

            $page = explode('/', $this->input->post('page'));
            $this->_record_offset = intval(end($page));
            $this->_record_limit = intval($this->input->post('perpage'));
            $order = end(explode('/', $this->input->post('order')));
            $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->_search_text = substr($this->input->post('searchtext'), 0, $this->config->item('search_text_max_length'));
            $search_column = $this->input->post('searchcolumn');
            $this->_search_column = (!empty($this->_columns_array[$search_column])) ? $this->_columns_array[$search_column]->search_name : '';

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

            // Set filter data
            $rtgrid_filter = $this->session->userdata('rtgrid_filter');
            $rtgrid_filter[$this->uri->uri_string()] = array(
                '_record_offset' => $this->_record_offset,
                '_record_limit' => $this->_record_limit,
                '_order_by' => $this->_order_by,
                '_order_type' => $this->_order_type,
                '_search_text' => $this->_search_text,
                '_search_column' => $this->_search_column
            );
            $this->session->set_userdata('rtgrid_filter', $rtgrid_filter);

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

            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' => htmlspecialchars($this->_search_text),
            'search_text_max_length' => $this->config->item('search_text_max_length'),
            'search_btn' => $this->lang->line('Search'),
            'clear_btn' => $this->lang->line('Clear'),
            'pagination' => $this->_get_pagination(),
            'form_close' => form_close()
        );

        // 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),
            );
            // Add search column
            $content['search_column'][] = array(
                'value' => $alias,
                'selected' => ($this->_search_column == $this->_columns_array[$alias]->search_name) ? 'selected="selected"' : '',
                'name' => $name
            );
        }

        // Add action column
        if (!empty($this->_actions)) {
            $content['column'][] = array(
                'value' => '',
                '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));
        
        return $this->parser->parse($this->template['main'], $content, TRUE);
    }

    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 '';
        }

        $rs = '<div class="btn-group"><a href="#" data-toggle="dropdown" class="btn dropdown-toggle">' . $this->lang->line('Action') . ' <span class="caret"></span></a><ul class="dropdown-menu pull-right">';
        foreach ($this->_actions as $action) {
            $rs .= '<li> ' . $action->get_template($row) . '</li>';
        }
        $rs .= '</ul></div>';

        return $rs;
    }

    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
        if (!empty($this->_search_text) && !empty($this->_search_column)) {
            switch ($this->db->dbdriver) {
                case 'postgre':
                    $this->db->where('"' . $this->_search_column . '"' . '::TEXT LIKE \'%' . $this->_search_text . '%\'', '', FALSE);
                    break;

                default :
                    $this->db->like($this->_search_column, $this->_search_text);
                    break;
            }
        }
    }

    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()));
		}
        
        if ($data->get_original() && !is_dir(FCPATH . $data->get_original_folder())) {
            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_original_folder(), $data->get_field()));
        }
        
        $this->_image_uploads[$data->get_field()] = $data;
        $this->callback_column($data->get_field(), array($this, 'show_image_upload'));
    }
    
    public function show_image_upload($img, $data) {
        
        $this->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->parse_js('rtgrid/edit/image_upload/js/main.js');
        $this->js_css->add_css('/' . $this->config->item('assets_folder') . '/plugins/fancybox/css/jquery.fancybox.css');
        $this->js_css->parse_css('rtgrid/edit/image_upload/css/fileuploader.css');
        $this->_jsDynActionData .= 'refresh_fancybox();';
        
        $data = @array_flip($data);
        $photo = (!empty($img) && !empty($data[$img]) && !empty($this->_image_uploads[$data[$img]])) ? $this->_image_uploads[$data[$img]]->get_path() .'/' . $img : '';
        
        return (!empty($img) && file_exists(ltrim($photo, '/'))) ? $this->parser->parse('rtgrid/edit/image_upload/preview_block', array('link' => $photo), 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);
                }
                
                if ($this->_image_uploads[$field]->get_original()) {
                    $original_photo = ltrim($this->_image_uploads[$field]->get_original_folder() . '/' . $value[$field], '/');
                    if (file_exists($original_photo) && !is_dir($original_photo)) {
                        unlink($original_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;
    }
    
}
