<?php

namespace dominion\file;

use Yii;
use yii\web\UploadedFile;
use Imagine\Image\Box;
use Imagine\Image\ImageInterface;
use yii\imagine\Image;

/**
 * This is the model class for table "file".
 *
 * @property int $id
 * @property string $date
 * @property string $module
 * @property int $height
 * @property int $width
 * @property int $fileSize
 * @property string $md5
 * @property string $ext
 * @property string $description
 * @property string $originalName
 */
class File extends \yii\db\ActiveRecord
{

    public $file;
    public $cropFile = false;
    public $convertJpg = true;

    /**
     * {@inheritdoc}
     */
    public static function tableName()
    {
        return 'file';
    }

    /**
     * {@inheritdoc}
     */
    public function rules()
    {
        return [
            [['date'], 'safe'],
            [['module'], 'required'],
            [['height', 'width', 'fileSize'], 'integer'],
            [['description'], 'string'],
            [['module', 'originalName', 'name'], 'string', 'max' => 500],
            [['md5', 'ext'], 'string', 'max' => 50],
        ];
    }

    /**
     * {@inheritdoc}
     */
    /*public function attributeLabels()
    {
        return [
            'id' => Module::t('app', 'ID'),
            'date' => Module::t('app', 'Date'),
            'module' => Module::t('app', 'Module'),
            'height' => Module::t('app', 'Height'),
            'width' => Module::t('app', 'Width'),
            'fileSize' => Module::t('app', 'File Size'),
            'md5' => Module::t('app', 'Md5'),
            'ext' => Module::t('app', 'Ext'),
            'description' => Module::t('app', 'Description'),
            'originalName' => Module::t('app', 'Original Name'),
            'name' => Module::t('app', 'Name'),
        ];
    }*/

    public static function validateFormat($model, $attribute, $params, $fileName = 'file')
    {
        $fileModel = new File;

        $fileModel->file = UploadedFile::getInstance($fileModel, $fileName);
        if ($fileModel->file && $fileModel->file->tempName)
        {
            \yii\validators\Validator::createValidator('file', $fileModel, 'file', $params)->validateAttribute($fileModel, 'file');
            if ($fileModel->hasErrors())
            {
                $model->addError($attribute, 'Не верный формат файла');
            }
        }
    }

    /**
     * Папка с файлом
     * @param boolian $full
     * @return type
     */
    public function getDir($full = false, $resize = false)
    {
        $dir = $resize ? 'file_resize' : 'file';
        $output = '';
       // $full = $full && !Yii::$app->has('s3');
        if($full)
        {
            $output = Yii::getAlias('@app/web');
        }
        $output .=  '/data/' . $dir . '/' . $this->module . '/' . wordwrap(mb_substr($this->md5, 0, 6), 2, '/', true) . '/';
        if(Yii::$app->has('s3') && $full && $resize)
        {
            //если работаем с облаком не плодим папки миниатюр
            $output = Yii::getAlias('@app/web/data/file_cache/');
        }
        if ($full && !file_exists($output))
        {
            mkdir($output, 0755, true);
        }
        if(Yii::$app->has('s3') && $full && $resize)
        {
            //если работаем с облаком не плодим папки миниатюр
            $output .= $this->md5.'-';
        }
        return $output;
    }

    /**
     * Путь к папке кеша
     * @return string
     */
    public function getDirCache()
    {
        $dir = Yii::getAlias('@app/web/data/file_cache/');
        if (!file_exists($dir))
        {
            mkdir($dir, 0755, true);
        }
        return $dir;
    }

    public function getFilePath($type = false, $full = false)
    {
        $pref = $type ? ($type . '-') : '';
        return $this->getDir($full, $type != false) . $pref . $this->name . '.' . $this->ext;
    }

    public function getFilePathCache()
    {
        $this->cropFile = $this->cropFile ? $this->cropFile :$this->getDirCache() . rand(1000, 999999);
        return $this->cropFile;
    }

    public function getIsImage()
    {
        return in_array($this->ext, ['jpg', 'png', 'jpeg']);
    }

    public function getShowImage()
    {
        return in_array($this->ext, ['jpg', 'png', 'jpeg', 'svg']);
    }

    protected function getFileName()
    {
        $defaultName = empty($this->name) ? $this->md5 : $this->name;
        $name = $defaultName;
        $i = 0;
        while(($model = File::find()->andWhere(['ext' => $this->ext, 'name'=>$name])->andWhere('md5 like "'. mb_substr($this->md5, 0, 6).'%"')->one()) !== null)
        {
            $i++;
            $name = $defaultName . '-' .$i;
        }
        return $name;
    }

    /**
     * {@inheritdoc}
     */
    public function beforeSave($insert)
    {
        if ($this->file)
        {
            $this->ext = $this->file->extension;
            $this->md5 = md5_file($this->file->tempName);
            $patch = $this->getDir(true);
            if ($this->isImage && $this->ext != 'jpg' && $this->convertJpg) //не конвертируем изображение в jpg если оно уже jpg
            {
                $this->ext = 'jpg';
                $this->name = $this->getFileName();
                $fileName = $this->name . '.' . $this->ext;

                $image = Image::getImagine()->open($this->file->tempName);
                $size = $image->getSize();
                $this->width = $size->getWidth();
                $this->height = $size->getHeight();

                $box = new Box($this->width, $this->height);
                $palette = new \Imagine\Image\Palette\RGB();
                $color = $palette->color(Image::$thumbnailBackgroundColor, Image::$thumbnailBackgroundAlpha);
                $thumb = Image::getImagine()->create($box, $color);
                $thumb->paste($image, new \Imagine\Image\Point(0, 0));
             //    var_dump($thumb); die();
                $thumb->save($patch . $fileName);
                $this->fileSize = filesize($patch . $fileName);
            }
            else
            {
                $this->name = $this->getFileName();
                $this->fileSize = $this->file->size;
                $fileName = $this->name . '.' . $this->ext;
                if (is_uploaded_file($this->file->tempName))
                {
                    $this->file->saveAs($patch . $fileName);
                }
                else
                {
                    rename($this->file->tempName, $patch . $fileName);
                }
            }
            $this->originalName = $this->file->name;
            $this->date = date('Y-m-d H:i:s');
        }
        return parent::beforeSave($insert);
    }

    public function afterSave($insert, $changedAttributes)
    {
        if (isset($this->file))
        {
            $configs = Config::find()->andWhere(['module' => $this->module])->all();
            foreach ($configs as $config)
            {
                $fileTemplate = $config->file ? [
                    'filePatch' => $config->file->getFilePath(false, true),
                    'border_top' => $config->border_top,
                    'border_right' => $config->border_right,
                    'border_left' => $config->border_left,
                    'border_bottom' => $config->border_bottom,
                ] : [];
                if($config->file)
                {
                    $config->file->downloadOriginal();
                }
                $quality = isset($config->quality) ? $config->quality : 90;
                $this->resize($config->type, $config->height, $config->width, $config->crop, $config->cut, $config->addBorder, true, $fileTemplate, $quality);
            }
            if($this->cropFile)
            {
                @unlink($this->cropFile);
            }
            $this->moveToS3();
        }
        return parent::afterSave($insert, $changedAttributes);
    }

    /**
     * Возврашает путь к изображению
     * @param type $id
     * @return string
     */
    public static function getPath($id, $type = false)
    {
        $output = '';
        if ($id > 0 && ($model = File::findOne($id)) !== null)
        {
            $output = $model->getFilePath($type);
        }
        return $output;
    }

    /**
     * Возврашает путь к изображению
     * @param type $id
     * @return string
     */
    public static function getPathFull($id, $type = false, $default = false)
    {
        $img = File::getPath($id, $type);
        $fileUrlDomen = isset(Yii::$app->params['fileUrlDomen']) ? Yii::$app->params['fileUrlDomen'] : '';
        $img = empty($img) ? $default : $img;
        return empty($img) ? false : (preg_replace("#/$#", "", $fileUrlDomen) . $img);

    }

    public static function getPathsFull($id, $types = [])
    {
        $fileUrlDomen = isset(Yii::$app->params['fileUrlDomen']) ? Yii::$app->params['fileUrlDomen'] : '';
        return self::getPaths($id, $types, $fileUrlDomen);
    }

    /**
     * Возврашает пути к изображениям
     * @param type $id
     * @return array
     */
    public static function getPaths($id, $types = [], $prefix = '')
    {
        $output = [];
        if ($id > 0 && ($model = File::findOne($id)) !== null)
        {
            foreach($types as $type)
            {
                $output[$type] = $prefix. $model->getFilePath($type);
            }
        }
        return $output;
    }

    protected function deleteDisckFile($deleteOriginal = false)
    {
        if($deleteOriginal)
        {
            //удаляем оригинал
            $file = $this->getFilePath(false, true);
            if(file_exists($file))
            {
                unlink($file);
                $files = array_diff(scandir($this->getDir(true)), array('.', '..'));
                if(empty($files))
                {
                    Helper::delFolder($this->getDir(true));
                }
            }
        }
        //удаляем миниатюры
        $dir = $this->getDir(true, true);
        if(file_exists($dir))
        {
            $files = array_diff(scandir($dir), array('.', '..'));
            foreach ($files as $key => $value)
            {
                if(stripos($value, $this->md5 . '.' . $this->ext) !== false)
                {
                    unlink("$dir/$value");
                    unset($files[$key]);
                }
            }
            if(empty($files))
            {
                Helper::delFolder($this->getDir(true, true));
            }
        }
    }
    protected function deleteS3File()
    {
        if(Yii::$app->has('s3'))
        {
            $s3 = Yii::$app->get('s3');
            $s3->delete($this->getFilePath());
            $configs = Config::find()->andWhere(['module' => $this->module])->all();
            foreach ($configs as $config)
            {
                $s3->delete($this->getFilePath($config->type));
            }
        }
    }

    public function delete()
    {
        $this->deleteDisckFile(true);
        $this->deleteS3File();
        return parent::delete();
    }

    /**
     * Создание миниатюр
     * @param string $type
     * @param int $height
     * @param int $width
     * @param boolian $crop обрезать белую рамку
     * @param boolian $cut обрезать по расмеру без соблюдения пропорций сторон
     * @param boolian $addBorder добавить белую рамку
     * @param boolian $all обновить изображение если уже оно создано
     * @param array $fileTemplate Вписать в шаблон
     * @param int $quality качество (степень сжатия)
     *
     */
    public function resize($type, $height, $width, $crop = false, $cut = false, $addBorder = false, $all = false, $fileTemplate = [], $quality = 90)
    {
        $patch = $this->getDir(true);
        $fileName = $this->name . '.' . $this->ext;
        $filePatch = $patch . $fileName;
        $this->cropFile = $this->cropFile && file_exists($this->cropFile) ? $this->cropFile : null;
        $quality = $quality > 100 || $quality <= 0 ? 90 : $quality;
        if (file_exists($filePatch) && in_array(exif_imagetype($filePatch), [IMAGETYPE_JPEG, IMAGETYPE_PNG]))
        {
            $resizePatch = $this->getDir(true, true);
            $resizeFileName = $type . '-' . $fileName;

            if ($all || !file_exists($resizePatch . $resizeFileName))
            {
                if ($crop)
                {
                    $filePatch = $this->cropFile ? $this->cropFile : $this->deleteBorder($filePatch);
                }

                if(!empty($fileTemplate) && isset($fileTemplate['filePatch'], $fileTemplate['border_top'], $fileTemplate['border_right'], $fileTemplate['border_left'], $fileTemplate['border_bottom']))
                {
                    \dominion\file\Image::thumbnailFileTemplate($filePatch, $width, $height, $fileTemplate)->save($resizePatch . $resizeFileName, ['quality' => $quality]);
                }
                elseif ($cut || $addBorder)
                {
                    $class = $addBorder ? '\dominion\file\Image' : '\yii\imagine\Image';
                    $mode = $addBorder ? ImageInterface::THUMBNAIL_INSET : ImageInterface::THUMBNAIL_OUTBOUND;
                    $class::thumbnail($filePatch, $width, $height, $mode)->save($resizePatch . $resizeFileName, ['quality' => $quality]);
                }
                else
                {
                    Image::getImagine()->open($filePatch)->thumbnail(new Box($width, $height))->save($resizePatch . $resizeFileName, ['quality' => $quality]);
                }
            }
        }
    }

    /**
     * Обрезает белую рамку
     * @param type $filePatch
     * @return type
     */
    protected function deleteBorder($filePatch)
    {
        $img = $this->ext == 'jpg' ? imagecreatefromjpeg($filePatch) : imagecreatefrompng($filePatch);

        $this->width = $this->width > 0 ? $this->width : imagesx($img);
        $this->height = $this->height > 0 ? $this->height : imagesy($img);

        $border = array(
            'left' => $this->getBorderSize($img, $this->width, $this->height, 'r'),
            'right' => $this->getBorderSize($img, $this->width, $this->height, 'l'),
            'top' => $this->getBorderSize($img, $this->height, $this->width, 'b'),
            'bottom' => $this->getBorderSize($img, $this->height, $this->width, 't'),
        );

        $x = $border['left'];
        $y = $border['top'];
        $newWidth = $this->width - ($border['left'] + $border['right']);
        $newHeight = $this->height - ($border['top'] + $border['bottom']);

        if($newWidth == 0 || $newHeight == 0)
        {
            return $filePatch;
        }

        // create the working image
        $workingImage = function_exists('imagecreatetruecolor') ? imagecreatetruecolor($newWidth, $newHeight) : imagecreate($newWidth, $newHeight);

        // and create the newly sized image
        imagecopy(
            $workingImage,
            $img,
            0,
            0,
            $x,
            $y,
            $newWidth,
            $newHeight
        );

        $temp = $this->getFilePathCache();
        imagejpeg($workingImage, $temp, 100);
        return $temp;
    }

    /**
     * Возвращает позицию первого не белого пикселя
     * @param type $image
     * @param type $width
     * @param type $height
     * @param type $direction
     * @return int
     * @throws Exception
     */
    protected function getBorderSize($image, $width, $height, $direction)
    {
        $border = 0;
        $realWidth = imagesx($image);
        $realHeight = imagesy($image);
        for ($i = 0; $i < $width; $i++)
        {
            $isWhite = true;
            $errors = round($height / 100);
            for ($j = 0; $j < $height; $j++)
            {
                switch ($direction)
                {
                    case 'l':
                        $x = $width - $i - 1;
                        $y = $j;
                        break;
                    case 'b':
                        $x = $j;
                        $y = $i;
                        break;
                    case 't':
                        $x = $j;
                        $y = $width - $i - 1;
                        break;
                    default:
                        $x = $i;
                        $y = $j;
                        break;
                }
                if ($x > $realWidth || $y > $realHeight || $x < 0 || $y < 0)
                {
                    throw new Exception('Не верные координаты изображения');
                }
                // Получаем RGB пикселя по координате
                $color = imageColorAt($image, $x, $y);
                // Разбиваем RGB на Red,Green,Blue и записываем каждую составляющую в свою переменную
                $pixel = imageColorsForIndex($image, $color);
                list($r, $g, $b) = array_values($pixel);
                //Сравнение белого ли цвета пиксель
                if ($r > 241 && $g > 241 && $b > 241)
                {
                    // белый
                }
                else
                {
                    // не белый
                    $errors--;
                    if ($errors <= 0)
                    {
                        $isWhite = false;
                        break;
                    }
                }
            }
            if ($isWhite)
            {
                $border++;
            }
            else
            {
                break;
            }
        }
        return $border;
    }

    public function loadFile($module, $url, $id = false)
    {
        $file = @file_get_contents($url);
        if ($file !== false)
        {
            $temp = $this->getFilePathCache();
            file_put_contents($temp, $file);
            unset($file);
            if (!$id || ($model = File::findOne($id)) === null)
            {
                $model = new File();
                $model->module = $module;
            }
            $arName = explode('/', $url);
            $model->file = new UploadedFile(
                [
                'name' => array_pop($arName),
                'tempName' => $temp,
                'type' => mime_content_type($temp),
                'size' => filesize($temp),
                'error' => UPLOAD_ERR_OK,
                ]
            );
            $model->save();
            return $model->id;
        }
    }

    /**
     * Сохранение файла из строки Base64
     * @param type $module
     * @param type $fileBase64
     * @param type $fileName
     * @param type $id
     * @return type
     */
    public static function saveBase64File($module, $fileBase64, $fileName, $id = false, $convertJpg = true)
    {
        if (!$id || ($model = File::findOne($id)) === null)
        {
            $model = new File();
            $model->module = $module;
        }
        $temp = $model->getFilePathCache();
        file_put_contents($temp, base64_decode($fileBase64));
        $model->file = new UploadedFile(
            [
            'name' => $fileName,
            'tempName' => $temp,
            'type' => mime_content_type($temp),
            'size' => filesize($temp),
            'error' => UPLOAD_ERR_OK,
            ]
        );
        $model->convertJpg = $convertJpg;
        $model->save();

        return $model->id;
    }

    public function getSize()
    {
        $fix = array(' B', ' KB', ' MB', ' GB', ' TB');
        $round = 2;

        $mult = 1E3;
        $range = 0;
        while (1 < $this->fileSize / pow($mult, $range))
        {
            $range++;
        }
        $mult = pow($mult, $range - 1);
        $postfix = $fix[$range - 1];
        return round($this->fileSize / $mult, $round) . $postfix;
    }

    public function moveToS3()
    {
        if(Yii::$app->has('s3'))
        {
            $s3 = Yii::$app->get('s3');
            if(file_exists($this->getFilePath(false, true)))
            {
                $s3->upload($this->getFilePath(), $this->getFilePath(false, true));
                $configs = Config::find()->andWhere(['module' => $this->module])->all();
                foreach ($configs as $config)
                {
                    if(file_exists($this->getFilePath($config->type, true)))
                    {
                        $s3->upload($this->getFilePath($config->type), $this->getFilePath($config->type, true));
                    }
                }
            }
        }
        //var_dump($this->getFilePath());
    }

    public function downloadOriginal()
    {
        if(Yii::$app->has('s3'))
        {
            $filePatch = $this->getFilePath(false, true);
            if (!file_exists($filePatch) && isset(Yii::$app->params['fileUrlDomen']))
            {
                $file = @file_get_contents(Yii::$app->params['fileUrlDomen']. $this->getFilePath());
                if($file)
                {
                    @file_put_contents($filePatch, $file);
                }
            }
        }
    }

    public static function upload($oldFile, $newFile)
    {
        if(Yii::$app->has('s3'))
        {
            $s3 = Yii::$app->get('s3');
            if(file_exists($oldFile))
            {
                $s3->upload($newFile, $oldFile);
            }
        }
    }

}