<?php

namespace Hura8\System\Controller;

use Hura8\System\ReadExcel;
use PhpOffice\PhpSpreadsheet\Shared\Date;


abstract class aExcelUploadController
{

    protected $update_option = [];

    protected static $column_names = [
        'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
        'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
        'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
        //...
    ];

    protected $file_input_name = "file_excel"; //

    protected $client_excel_col_config = [
        /*"A" => array(
            'name'  => 'ID Sản phẩm Web',
            'width' => '10',
            'data_field_name'   => 'id',
        ),
        "B" => array(
            'name'  => 'Mã kho (SKU)',
            'width' => '10',
            'data_field_name'   => 'storeSKU',
        ),*/
        //...
    ];

    protected $field_column_mappings = [
        //'name' => "A",
        //'price' => "B",
    ];

    // hold cache for some operations
    protected $cache = [];

    protected $format_item_middlewares = []; // list of middleware object to format item


    public function __construct($client_config_file_name = '', $file_input_name = '', array $update_option = [])
    {

        if(!$client_config_file_name) {
            return true;
        }

        //this from a config file for each client
        $client_config_file = "config/client/excel/" . $client_config_file_name;
        if (!file_exists(ROOT_DIR . '/' . $client_config_file)) {
            die("Please create config file: " . $client_config_file);
        }
        $client_fields_config = include ROOT_DIR . '/' . $client_config_file;

        if($file_input_name) {
            $this->file_input_name = $file_input_name;
        }

        // auto add excel column names based on fields' index
        $this->client_excel_col_config = $this->_make_columns(array_values($client_fields_config));

        // create field-col map
        $field_column_mappings = [];
        foreach ($this->client_excel_col_config as $column_name => $_prop) {
            if(!$_prop['data_field_name']) continue;

            // skip column which is not for upload
            if(isset($_prop['for_upload']) && !$_prop['for_upload']) continue;

            $field_column_mappings[$_prop['data_field_name']] = $column_name;
        }

        $this->field_column_mappings = $field_column_mappings;

        $this->update_option = $update_option;

        return true;
    }


    public function updateUpdateOptions(array $update_option = []) {
        foreach ($update_option as $key => $value) {
            $this->update_option[$key] = $value;
        }
    }


    public function getColumnNotForUpload() {
        $result = [];
        foreach ($this->client_excel_col_config as $column_name => $_prop) {
            // skip column which is not for upload
            if(isset($_prop['for_upload']) && !$_prop['for_upload']) $result[$column_name] = $_prop['name'] ;
        }

        return $result;
    }


    public function getColumnCanUpdate() {
        $result = [];
        foreach ($this->client_excel_col_config as $column_name => $_prop) {
            // skip column which is not for upload
            if(isset($_prop['for_upload']) && !$_prop['for_upload']) continue;

            // skip column which is not for update
            if(isset($_prop['can_update']) && !$_prop['can_update']) continue;

            $result[] = $_prop ;
        }

        return $result;
    }


    protected function getRequiredFields() {
        $result = [];
        foreach ($this->client_excel_col_config as $column_name => $_prop) {
            // skip column which is not for upload
            if(isset($_prop['required']) && $_prop['required']) $result[] = $_prop['data_field_name'] ;
        }

        return $result;
    }


    protected function _make_columns($fields_config)
    {
        $new_array = [];
        $total_names = sizeof(static::$column_names);
        foreach ($fields_config as $index => $config) {
            if ($index >= $total_names) break;
            $new_array[static::$column_names[$index]] = $config;
        }

        return $new_array;
    }


    protected function getExcelFileExt($excel_file){
        $ext = substr(strrchr($excel_file, "."), 1);
        return (in_array($ext, ["xls", "xlsx"])) ? $ext : false;
    }


    public function start($sheet_start_row = 3, $batch_mode = false, $batch_size=100)
    {
        $ext = $this->getExcelFileExt($_FILES[$this->file_input_name]["name"]);
        if(!$ext) {
            return [
                'status' => 'error',
                'message' => 'Invalid excel file',
            ];
        }

        $objReadExcel = new ReadExcel($ext);

        $all_rows = $objReadExcel->read(
            $_FILES[$this->file_input_name]["tmp_name"],
            $sheet_start_row,
            $this->field_column_mappings,
            '',
            true
        );

        $this->beforeProcessRows($all_rows);

        $success_row_counter = 0;

        if($batch_mode) {
            // batch mode
            $small_batch = [];
            $counter = 0;
            foreach ($all_rows as $sheet_index => $sheet_content) {
                foreach ($sheet_content as $row_id => $row_content) {

                    $formatted_info = $this->formatItemInfo($this->convertColToField($row_content));
                    if(!$formatted_info) continue;

                    $counter += 1;

                    $small_batch[] = $formatted_info;
                    if($counter % $batch_size == 0) {
                        $success_row_counter += $this->processBatchItems($small_batch);
                        // reset
                        $counter = 0;
                        $small_batch = [];
                    }
                }
            }

            // process the remain
            if(sizeof($small_batch)) {
                $success_row_counter += $this->processBatchItems($small_batch);
            }

        } else {
            // single item mode
            foreach ($all_rows as $sheet_index => $sheet_content) {
                foreach ($sheet_content as $row_index => $row_content) {

                    $formatted_info = $this->formatItemInfo($this->convertColToField($row_content));
                    if(!$formatted_info) continue;

                   if($this->processItem($formatted_info, $row_index)){
                       $success_row_counter += 1;
                   }
                }
            }
        }

        //unset($all_rows);

        return [
            'status' => 'success',
            'message' => '',
            'success_counter' => $success_row_counter,
        ];
    }


    protected function convertExcelDateValue($value) {
        // Date value in Excel's cell can be displayed as text or date value, we need to check for both
        $format_value = $this->formatExcelUploadDate($value);

        if(!$format_value) {
            // check if it's excel date
            try {
                $format_value = (is_numeric($value)) ? date("d-m-Y", Date::excelToTimestamp($value, date_default_timezone_get())) : '';
            }catch (\Exception $e) {
                $format_value = '';
            }
        }

        return $format_value;
    }


    protected function formatExcelUploadDate($input_date) {
        $check_date_pattern = "/\d{1,2}-\d{1,2}-\d{4}/i";
        $format_date = str_replace("/", "-", $input_date);

        if(preg_match($check_date_pattern, $format_date)) {
            return date("d-m-Y", strtotime($format_date));
        }

        return null;
    }

    protected function convertExcelHourMinuteValue($value) {
        $format_value = $this->formatExcelUploadHourMinute($value);

        if(!$format_value) {
            // check if it's excel date
            try {
                $format_value = (is_numeric($value)) ? date("H:i", Date::excelToTimestamp($value, date_default_timezone_get())) : '';
            }catch (\Exception $e) {
                $format_value = '';
            }
        }

        return $format_value;
    }


    protected function formatExcelUploadHourMinute($input_date) {
        $check_date_pattern = "/\d{1,2}:\d{1,2}/i";
        //$format_date = str_replace("/", "-", $input_date);

        if(preg_match($check_date_pattern, $input_date)) {
            return date("H:i", strtotime($input_date));
        }

        return null;
    }

    // ['A' => '12', 'B' => 'ten sp'] => ['id' => '12', 'product_name' => 'ten sp']
    protected function convertColToField(array $row_content ) {

        if(!$this->field_column_mappings) {
            return array_values($row_content);
        }

        $item_info = [];
        foreach ($this->field_column_mappings as $field => $col) {
            //if(!isset($row_content[$col])) continue;
            $item_info[$field] = $row_content[$col];
        }

        return $item_info;
    }

    // abstract methods
    protected function beforeProcessRows(array &$all_read_rows){
        // default nothing
        // derived class should overwrite this method
    }
    abstract protected function processItem(array $item_info, $row_index=0);
    abstract protected function processBatchItems(array $item_list);

    /**
     * @param array $item_info
     * @return array|null
     */
    abstract protected function formatItemInfoBeforeProcess(array $item_info);

    public function registerFormatItemInfoMiddleware($middleware_ojb)
    {
        $this->format_item_middlewares[] = $middleware_ojb;
    }


    protected $date_fields = [];
    public function setDateFields(array $date_fields) {
        $this->date_fields = $date_fields;
    }

    protected $hour_minute_fields = [];
    public function setHourMinuteFields(array $hour_minute_fields) {
        $this->hour_minute_fields = $hour_minute_fields;
    }

    /**
     * @param array $item_info
     * @return array|null
     */
    protected function formatItemInfo(array $item_info)
    {
        $copy = $item_info;

        // apply middleware
        if (sizeof($this->format_item_middlewares)) {
            foreach ($this->format_item_middlewares as $_middleware) {
                $copy = call_user_func($_middleware, $copy);
            }
        }

        foreach ($this->date_fields as $field) {
            $copy[$field]  = $this->convertExcelDateValue($item_info[$field]);
        }

        foreach ($this->hour_minute_fields as $field) {
            $copy[$field]  = $this->convertExcelHourMinuteValue($item_info[$field]);
        }

        return $this->formatItemInfoBeforeProcess($copy);
    }

}
