<?php
/**
 * Created by Error202
 * Date: 24.08.2018
 */

namespace core\behaviors;

use yii\base\Behavior;
use yii\base\InvalidConfigException;
use yii\db\ActiveRecord;
use yii\db\ActiveQuery;

class LanguageBehavior extends Behavior
{
    /**
     * Attributes for translate, ex.: ['name', 'content']
     * @var array
     */
    public $attributes;

    /**
     * Class name for language active record entity
     * @var string
     */
    public $virtualClassName = 'VirtualTranslate';

    /**
     * Field name for language in translate table
     * @var string
     */
    public $languageField = 'language';

    /**
     * Field name for tables relative, ex.: 'post_id'
     * @var array|string
     */
    public $relativeField;

    /**
     * Available languages, ex.: ['en', 'ru']
     * @var array
     */
    public $translatedLanguages;

    /**
     * Default language, ex.: 'en'
     * @var string
     */
    public $defaultLanguage;

    /**
     * Translate table name, ex.: 'post_lng'
     * @var string
     */
    public $tableName;

    /**
     * Abridge the language ID.
     * @var boolean whether to abridge the language ID.
     */
    public $abridge = true;

    /**
     * Delete relative if foreign key not set on delete: cascade
     * @var bool
     */
    public $forceDelete = false;

    private $_ownerClassName;
    private $_ownerClassShortName;
    private $_ownerPrimaryKey;
    private $_languageAttributes = [];

    /**
     * Control events firing
     * @return array
     */
    public function events()
    {
        return [
            ActiveRecord::EVENT_AFTER_FIND   => 'afterFind',
            ActiveRecord::EVENT_AFTER_UPDATE => 'afterUpdate',
            ActiveRecord::EVENT_AFTER_INSERT => 'afterInsert',
            ActiveRecord::EVENT_AFTER_DELETE => 'afterDelete',
        ];
    }

    public function attach($owner)
    {
        /** @var ActiveRecord $owner */
        parent::attach($owner);
        if (empty($this->translatedLanguages) || !is_array($this->translatedLanguages)) {
            throw new InvalidConfigException('Please specify array of available languages for the ' . get_class($this) . ' in the ' . get_class($this->owner) . ' or in the application parameters', 101);
        }

        if (array_values($this->translatedLanguages) !== $this->translatedLanguages) { //associative array
            $this->translatedLanguages = array_keys($this->translatedLanguages);
        }

        if (!$this->defaultLanguage) {
            throw new InvalidConfigException('Please specify default language for the ' . get_class($this));
        }

        if (empty($this->attributes) || !is_array($this->attributes)) {
            throw new InvalidConfigException('Please specify translated attributes for the ' . get_class($this) . ' in the '
                                             . get_class($this->owner), 103);
        }

        $this->_ownerClassName      = get_class($this->owner);
        $this->_ownerClassShortName = $this->getShortClassName($this->_ownerClassName);

        /** @var ActiveRecord $className */
        $className              = $this->_ownerClassName;
        $this->_ownerPrimaryKey = $className::primaryKey()[0];

        if (!isset($this->relativeField)) {
            throw new InvalidConfigException('Please specify relativeField for the ' . get_class($this) . ' in the '
                                             . get_class($this->owner), 105);
        }

        //$rules = $owner->rules();
        //$validators = $owner->getValidators();
        /*foreach ($rules as $rule) {
            if (in_array($rule[1], $this->excludedValidators))
                continue;
            $rule_attributes = is_array($rule[0]) ? $rule[0] : [$rule[0]];
            $attributes = array_intersect($this->attributes, $rule_attributes);
            if (empty($attributes))
                continue;
            $rule_attributes = [];
            foreach ($attributes as $key => $attribute) {
                foreach ($this->languages as $language)
                    if ($language != $this->defaultLanguage)
                        $rule_attributes[] = $this->getAttributeName($attribute, $language);
            }
            if (isset($rule['skipOnEmpty']) && !$rule['skipOnEmpty'])
                $rule['skipOnEmpty'] = !$this->requireTranslations;
            $params = array_slice($rule, 2);
            if ($rule[1] !== 'required' || $this->requireTranslations) {
                $validators[] = Validator::createValidator($rule[1], $owner, $rule_attributes, $params);
            } elseif ($rule[1] === 'required') {
                $validators[] = Validator::createValidator('safe', $owner, $rule_attributes, $params);
            }
        }*/

        $this->createLanguageClass();
        $translation = new $this->virtualClassName;

        foreach ($this->translatedLanguages as $language) {
            foreach ($this->attributes as $attribute) {
                $attributeName = $attribute;
                $this->setLanguageAttribute($attribute . '_' . $language, $translation->{$attributeName});

                //$this->owner->__set($attribute . '_' . $language, $translation->{$attributeName});
                //$this->owner->{$attribute . '_' . $language} = $translation->{$attributeName};
                //$this->owner->createProperty($attribute . '_' . $language, $translation->{$attributeName});

                if ($language == $this->defaultLanguage) {
                    $this->setLanguageAttribute($attribute, $translation->{$attributeName});
                    //$this->owner->__set($attribute, $translation->{$attributeName});
                }
            }
        }
    }

    /**
     * Insert event
     */
    public function afterInsert()
    {
        $this->saveTranslations();
    }

    /**
     * Update event
     */
    public function afterUpdate()
    {
        /** @var ActiveRecord $owner */
        $owner = $this->owner;
        //if ($owner->isRelationPopulated('translations')) {
        if ($translationRecords = $owner->translations) {
            $translations = $this->indexByLanguage($owner->getRelatedRecords()['translations']);
            //$translations = $this->indexByLanguage($translationRecords);
            $this->saveTranslations($translations);
        }
    }

    /**
     * Find event
     */
    public function afterFind()
    {
        /** @var ActiveRecord $owner */
        $owner = $this->owner;
        if ($owner->isRelationPopulated('translations') && $related = $owner->getRelatedRecords()['translations']) {
            $translations = $this->indexByLanguage($related);
            foreach ($this->translatedLanguages as $language) {
                foreach ($this->attributes as $attribute) {
                    foreach ($translations as $translation) {
                        if ($translation->{$this->languageField} == $language) {
                            $attributeName = $attribute;
                            $this->setLanguageAttribute($attribute . '_' . $language, $translation->{$attributeName});
                            if ($language == $this->defaultLanguage) {
                                $this->setLanguageAttribute($attribute, $translation->{$attributeName});
                            }
                        }
                    }
                }
            }
        } else {
            if (!$owner->isRelationPopulated('translation')) {
                $owner->translation;
            }
            $translation = $owner->getRelatedRecords()['translation'];
            if ($translation) {
                foreach ($this->attributes as $attribute) {
                    $attribute_name = $attribute;
                    $owner->setLanguageAttribute($attribute, $translation->$attribute_name);
                }
            }
        }
        foreach ($this->attributes as $attribute) {
            if ($owner->hasAttribute($attribute) && $this->getLangAttribute($attribute)) {
                $owner->setAttribute($attribute, $this->getLangAttribute($attribute));
            }
        }
    }

    public function afterDelete()
    {
        if ($this->forceDelete) {
            /** @var ActiveRecord $owner */
            $owner = $this->owner;
            $owner->unlinkAll('translations', true);
        }
    }

    public function createLanguageClass()
    {
        if (!class_exists($this->virtualClassName, false)) {
            eval('
            use yii\db\ActiveRecord;
            class ' . $this->virtualClassName . ' extends ActiveRecord
            {
                public static function tableName()
                {
                    return \'' . $this->tableName . '\';
                }
            }');
        }
    }

    private function saveTranslations($translations = [])
    {
        /** @var ActiveRecord $owner */
        $owner = $this->owner;
        if (!isset($owner->_form) || !$owner->_form) {
            return;
        }
        foreach ($this->translatedLanguages as $language) {
            $isDefaultLanguage = $language == $this->defaultLanguage;
            if (!isset($translations[$language])) {
                /** @var ActiveRecord $translation */
                $translation                         = new $this->virtualClassName;
                $translation->{$this->languageField} = $language;

                if (is_array($this->relativeField)) {
                    foreach ($this->relativeField as $field) {
                        $translation->{$field} = $owner->{$field};
                    }
                } else {
                    $translation->{$this->relativeField} = $owner->getPrimaryKey();
                }
            } else {
                $translation = $translations[$language];
            }
            $save = false;
            foreach ($this->attributes as $attribute) {
                //$value = $isDefaultLanguage ? $owner->$attribute : $owner->{$attribute . '_' . $language};
                $value = $isDefaultLanguage ? $owner->_form->$attribute : $owner->_form->{$attribute . '_' . $language};
                if ($value !== null) {
                    //$field = $isDefaultLanguage ? $attribute : $attribute . '_' . $language;
                    $field               = $attribute;
                    $translation->$field = $value;
                    $save                = true;
                }
            }
            if ($translation->isNewRecord && !$save) {
                continue;
            }
            $translation->save();
        }
    }

    private function getShortClassName($className)
    {
        return substr($className, strrpos($className, '\\') + 1);
    }

    public function setLanguageAttribute($name, $value)
    {
        $this->_languageAttributes[$name] = $value;
    }

    protected function indexByLanguage(array $records)
    {
        $sorted = [];
        foreach ($records as $record) {
            $sorted[$record->{$this->languageField}] = $record;
        }
        unset($records);

        return $sorted;
    }

    /**
     * Relation to model translations
     * @return ActiveQuery
     */
    public function getTranslations()
    {
        /** @var ActiveRecord */
        $condition = [];
        if (is_array($this->relativeField)) {
            foreach ($this->relativeField as $field) {
                //$condition[$field] = $this->owner->{$field};
                $condition[$field] = $field;
            }
            return $this->owner->hasMany($this->virtualClassName, $condition);
        } else {
            return $this->owner->hasMany($this->virtualClassName, [$this->relativeField => $this->_ownerPrimaryKey]);
        }
    }

    public function getTranslation($language = null)
    {
        //if (basename(\Yii::$app->getBasePath()) === 'backend') {
        //	$language = $language ?: $this->defaultLanguage;
        //}
        //else {
        $language = $language ?: \Yii::$app->language;
        //}
        // if translate exists
        if (is_array($this->relativeField)) {
            $condition = [];
            foreach ($this->relativeField as $field) {
                $condition[$field] = $this->owner->{$field};
            }
            $translate = $this->virtualClassName::find()
                                                ->andWhere($condition)
                                                ->andWhere([$this->languageField => $language])
                                                ->one();
        } else {
            $translate = $this->virtualClassName::find()
                                                ->andWhere([$this->relativeField => $this->owner->id])
                                                ->andWhere([$this->languageField => $language])
                                                ->one();
        }
        $language = $translate ? $language : $this->defaultLanguage;

        if (is_array($this->relativeField)) {
            $condition = [];
            foreach ($this->relativeField as $field) {
                $condition[$field] = $field;
            }
            return $this->owner->hasOne($this->virtualClassName, $condition)
                               ->where([$this->languageField => $language]);
        } else {
            return $this->owner->hasOne($this->virtualClassName, [$this->relativeField => $this->_ownerPrimaryKey])
                               ->where([$this->languageField => $language]);
        }
    }

    public function findTranslation($language = null)
    {
        $language = $language ?: $this->defaultLanguage;

        //$class = call_user_func(array($this->virtualClassName, 'getInstance'));

        if (is_array($this->relativeField)) {
            $condition = [];
            foreach ($this->relativeField as $field) {
                $condition[$field] = $this->owner{$field};
            }
            return $this->virtualClassName::find()
                                          ->andWhere($condition)
                                          ->andWhere([$this->languageField => $language])
                                          ->one();
        } else {
            return $this->virtualClassName::find()
                                          ->andWhere([$this->relativeField => $this->owner->id])
                                          ->andWhere([$this->languageField => $language])
                                          ->one();
        }
    }

    public function hasLangAttribute($name)
    {
        return array_key_exists($name, $this->_languageAttributes);
    }

    public function getLangAttribute($name)
    {
        return $this->hasLangAttribute($name) ? $this->_languageAttributes[$name] : null;
    }
}