<?php

namespace App\Admin\Exporters;

use Encore\Admin\Grid\Exporters\ExcelExporter;

class NewCouponExport extends ExcelExporter
{
    protected $fileName       = null;
    protected $debug          = false;
    protected $dealColumnList = [];
    protected $columnCallback = null;
    protected $addColumnList  = [];
    protected $appendColumn   = [];
    protected $perLimit       = 1500;

    /**
     * 导出
     *
     * @return mixed|void
     */
    public function export()
    {
        $model   = $this;
        $grid    = $this->grid;
        $columns = $this->getColumns();
        $columns = $this->addingColumn($columns);

        $call_back = function ($page, $limit) use ($model, $grid, $columns) {
            $model->page = $page;
            $grid->paginate($limit);
            $model->setGrid($grid);
            request()->offsetSet('per_page', $limit);

            $list = $model->getQuery()->get()->toArray();
            $list = $this->dealColumn($columns, $list);
            if ($this->columnCallback) {
                $list = call_user_func($this->columnCallback, $columns, $list);
            }
            return $list;
        };
        if ($this->debug) {
            dd($columns, call_user_func($call_back, 1, 20));
        }
        $file_name  = $this->fileName ? $this->fileName : $this->getTable().date('YmdHis');
        $data_count = $this->getQuery()->count();
        $perPage    = request()->get('per_page', $this->perLimit);
        $this->exportCsv($file_name, $columns, $call_back, $data_count, $perPage);

    }

    /**
     * 设置文件名
     *
     * @param  null  $file_name
     */
    public function setName($file_name = null)
    {
        if ($file_name) {
            $this->fileName = $file_name;
        }
    }

    /**
     * Notes: 设置处理字段值的规则
     *
     * @Author: 玄尘
     * @Date: 2023/2/1 15:42
     * @param $column
     * @param  \Closure  $call_back
     */
    public function setColumn($column, \Closure $call_back)
    {
        $this->dealColumnList[$column] = $call_back;
    }

    /**
     * 添加字段
     *
     * @param $column
     * @param $name
     * @param  null|string|\Closure  $after_column
     * @param  null|\Closure  $call_back
     */
    public function addColumn($column, $name, $after_column = null, $call_back = null)
    {
        if ($after_column instanceof \Closure) {
            $this->addColumnList[$column] = $after_column;
            $this->appendColumn[$column]  = [$name, null];
        } else {
            if ($call_back instanceof \Closure) {
                $this->addColumnList[$column] = $call_back;
                $this->appendColumn[$column]  = [$name, $after_column];
            } else {
                $this->appendColumn[$column] = [$name, $after_column];
            }
        }

    }

    /**
     * 正式追加字段
     *
     * @param $columns
     * @return array
     */
    protected function addingColumn($columns): array
    {
        if (! $this->appendColumn) {
            return $columns;
        }
        $keys = array_keys($columns);
        foreach ($this->appendColumn as $column => $item) {
            list ($name, $after_column) = $item;
            if (! empty($after_column) && in_array($after_column, $keys)) {
                $index = array_search($after_column, $keys);
                array_splice($keys, $index + 1, 0, $column);
            } else {
                array_push($keys, $column);
            }
            $columns[$column] = $name;
        }
        $result = [];
        foreach ($keys as $column) {
            $result[$column] = $columns[$column] ?? $column;
        }
        return $result;
    }

    /**
     * 字段值重新处理 回调函数
     *
     * @param  \Closure  $call_back
     */
    public function setList(\Closure $call_back)
    {
        $this->columnCallback = $call_back;
    }

    /**
     * 处理有共同特征的字段值
     *
     * @param $columns
     * @param $list
     * @return mixed
     */
    protected function dealColumn($columns, $list)
    {
        if (! $list) {
            return $list;
        }
        $deal = array_keys($this->dealColumnList);
        $add  = array_keys($this->addColumnList);
        foreach ($list as $index => $data) {
            foreach ($columns as $column => $name) {
                switch ($column) {
                    case strpos($column, '.') !== false :
                        $keys  = explode('.', $column);
                        $value = $data;
                        for ($i = 0; $i < count($keys); $i++) {
                            $value = $value[$keys[$i]] ?? [];
                        }
                        if (is_array($value)) {
                            $value = empty($value) ? '' : json_encode($value, JSON_UNESCAPED_UNICODE);
                        }
                        $data[$column] = $value;
                        break;
                }
                if ($this->dealColumnList && in_array($column, $deal)) {
                    $data[$column] = call_user_func_array($this->dealColumnList[$column],
                        [$data[$column] ?? '', $data]);
                }
                if ($this->addColumnList && in_array($column, $add)) {
                    $data[$column] = call_user_func($this->addColumnList[$column], $data);
                }
            }
            $list[$index] = $data;
        }
        return $list;
    }

    /**
     * 获取选中的导出字段
     *
     * @return array
     */
    protected function getColumns(): array
    {
        $columns = [];
        foreach ($this->grid->getColumns() as $column) {
            $columns[$column->getName()] = $column->getLabel();
        }
        $column_string = request()->get('_columns_', '');
        if (! $column_string) {
            return $columns;
        }
        $column_array = explode(',', $column_string);
        $column_list  = [];
        foreach ($columns as $column => $name) {
            if (in_array($column, $column_array)) {
                $column_list[$column] = $name;
            }
        }
        return $column_list;
    }

    /**
     * 打印
     */
    public function dd()
    {
        $this->debug = true;
    }

    /**
     * 导出CSV
     *
     * @param  string  $file_name  文件名
     * @param  array  $head_list  表头
     * @param  int  $data_count  总数
     * @param  int  $page_limit  每页数量
     * @param  \Closure  $call_back  获取分页数据的回调函数
     */
    function exportCsv(
        string $file_name,
        array $head_list,
        \Closure $call_back,
        int $data_count,
        int $page_limit = 0
    ) {
        set_time_limit(0);
        ini_set('memory_limit', -1);
        header('Content-Type: application/vnd.ms-excel');
        header('Content-Disposition: attachment;filename="'.$file_name.'.csv"');
        header('Cache-Control: max-age=0');
        //打开PHP文件句柄,php://output 表示直接输出到浏览器
        $fp   = fopen('php://output', 'a');
        $head = [];
        //输出Excel列名信息
        foreach ($head_list as $name) {
            //CSV的Excel支持GBK编码，一定要转换，否则乱码
            $head[] = iconv('utf-8', 'gbk', $name);
        }
        //将数据通过fputcsv写到文件句柄
        fputcsv($fp, $head);
        $page_limit = $data_count > $page_limit ? $page_limit : $data_count;
        $page_count = ceil($data_count / $page_limit);
        for ($page = 1; $page <= $page_count; $page++) {
            //逐页取出数据，不浪费内存
            $data_list = call_user_func($call_back, $page, $page_limit);
            if (! $data_list) {
                continue;
            }
            foreach ($data_list as $data) {
                $row = [];
                foreach ($head_list as $key => $name) {
                    $value = $data[$key] ?? '';
                    $row[] = iconv('utf-8', 'gbk', $value);
                }

                fputcsv($fp, $row);
                unset($row);
            }
            //刷新一下输出buffer，防止由于数据过多内存不足
            ob_flush();
            flush();
            info($page.'/'.$page_count.' time= '.$this->getElapsedTime().' memory= '.$this->getMemoryUsage());
        }
        fclose($fp);
        exit();
    }

    public function getElapsedTime(int $decimals = 2): string
    {
        return number_format(microtime(true) - request()->server('REQUEST_TIME_FLOAT'), $decimals).' s';
    }

    public function getMemoryUsage($precision = 2): string
    {
        $size = memory_get_usage(true);

        $unit = ['b', 'kb', 'mb', 'gb', 'tb', 'pb'];

        return round($size / pow(1024, ($i = floor(log($size, 1024)))), $precision).' '.$unit[$i];
    }


}