<?php

namespace App;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;
use Input;
use Redirect;
use Session;

/**
 * Created by PhpStorm.
 * User: jirislav
 * Date: 18.5.17
 * Time: 21:12
 */
class EntityClass extends Model
{

    /**
     * Constraint that is specified within pivot table for relation between entity_class & parent_entity_class.
     *
     * Serves to mark parent relation in the way, that it's parent can't have a parent.
     */
    const PARENT_MUST_NOT_HAVE_PARENT_CONSTRAINT = 'parent_must_not_have_parent';

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'id',
        'name',
        'is_type',
        'require_parent',
        'entity_type_class_id',
        'has_manufacturer',
        'has_supplier',
        'has_location'
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        //
    ];

    /**
     * Fetches entity class from the DB including it's parameters as an array.
     *
     * @param int $id
     * @param int $isType
     * @param bool $includeParams
     * @param bool $includeParents
     * @return \StdClass
     * @throws \Exception
     */
    public static function get(int $id, $isType, bool $includeParams = true, bool $includeParents = false)
    {
        if ($isType !== 0 && $isType !== 1 && $isType !== null) {
            throw new \Exception('type must be logical integer <0,1> !');
        }

        $where = [
            ['class.id', '=', $id],
        ];

        if ($isType !== null)
            $where[] = ['class.is_type', '=', $isType];

        $query = DB::table('entity_classes as class')
            ->leftJoin('entity_classes as type', 'class.entity_type_class_id', '=', 'type.id')
            ->where($where);

        $select = [
            'type.id as type_id',
            'type.name as type_name',

            'class.id as id',
            'class.name as name',
            'class.is_type as is_type',
            'class.require_parent as require_parent',
            'class.has_manufacturer as has_manufacturer',
            'class.has_location as has_location',
            'class.has_supplier as has_supplier',
            'class.updated_at as updated_at',
        ];

        $groupBy = [
            'class.id'
        ];

        if ($includeParents) {
            // Build valid JSON object of parent entity classes
            $select[] = DB::raw('CONCAT(\'{\', GROUP_CONCAT( DISTINCT \'"\', parent.id, \'":"\', parent.name, \'"\' SEPARATOR \',\'), \'}\') as parents');
            //$groupBy[] = 'parent.id';

            $query = $query
                ->leftJoin('possible_parent_classes as child_to_parent', 'class.id', '=', 'child_to_parent.child_entity_class_id')
                ->leftJoin('entity_classes as parent', 'child_to_parent.parent_entity_class_id', '=', 'parent.id');
        }

        if ($includeParams) {
            $select[] = DB::raw("
                    -- Build a JSON array of parameters
                    CONCAT('[', 
                        GROUP_CONCAT( DISTINCT
                            '{',
                                '\"id\": \"',
                                    `param`.`id`, '\",',
                                    
                                '\"name\": \"',
                                    `param`.`parameter_name`, '\",',
                                    
                                '\"type\": \"',
                                    `param_type`.`name`, '\",',
                                    
                                '\"max\": \"',
                                    COALESCE(`param`.`max_param_len_or_val`, 'null'), '\",',
                                    
                                '\"required\": \"',
                                    `param`.`required`, '\",',
                                    
                                '\"unique\": \"',
                                    `param`.`unique`, '\"', 
                            '}' SEPARATOR ', '
                        ),
                    ']') as parameters");
            //$groupBy[] = 'param.id';
            //$groupBy[] = 'param_type.id';

            $query = $query
                ->leftJoin('class_parameters as param', 'class.id', '=', 'param.entity_class_id')
                ->leftJoin('parameter_datatypes as param_type', 'param.parameter_type_id', '=', 'param_type.id');
        }

        $query = $query->groupBy($groupBy);

        DB::statement('SET group_concat_max_len = 10000000');

        $entityClass = $query
            ->select($select)
            ->first();

        if (!$entityClass)
            return $entityClass;

        if ($includeParents)
            // Parse parents into PHP object
            if ($entityClass->parents !== null)
                $entityClass->parents = json_decode($entityClass->parents, true);
            else
                $entityClass->parents = [];

        if ($includeParams) {
            // Parse parameters into PHP object
            if ($entityClass->parameters !== null)
                $entityClass->parameters = json_decode($entityClass->parameters, true);
            else
                $entityClass->parameters = [];

            foreach ($entityClass->parameters as &$parameter) {
                if (isset($parameter['max']) && $parameter['max'] !== null && $parameter['max'] !== 'null')
                    $parameter['max'] = intval($parameter['max']);
                else
                    $parameter['max'] = null;
                $parameter['required'] = ($parameter['required'] === '1') ? true : false;
                $parameter['unique'] = ($parameter['unique'] === '1') ? true : false;
            }
        }
        return $entityClass;
    }

    /**
     * Get available sorting for provided entity class (respects class parameters)
     *
     * @param \stdClass $entityClass
     * @param \stdClass $entityClassType
     * @return array
     */
    public static function availableSorting(\stdClass $entityClass, $entityClassType)
    {

        if ($entityClass->parameters !== null) {
            $availableSorting = array_map(function ($parameter) {
                return $parameter['name'];
            }, $entityClass->parameters);
        } else {
            $availableSorting = [];
        }

        $availableSorting[] = 'id';
        $availableSorting[] = 'state';
        $availableSorting[] = 'updated_at';
        $availableSorting[] = 'children_ids';
        $availableSorting[] = 'parent_id';

        if ($entityClass->has_manufacturer)
            $availableSorting[] = 'manufacturer';

        if ($entityClass->has_location)
            $availableSorting[] = 'location';

        if ($entityClass->has_supplier)
            $availableSorting[] = 'supplier';

        // Also add parameters from type
        if ($entityClassType !== null) {

            if ($entityClassType->has_manufacturer)
                $availableSorting[] = 'manufacturer';

            if ($entityClassType->parameters !== null) {

                $availableSorting = array_merge(
                    $availableSorting,
                    array_map(function ($parameter) {
                        return $parameter['name'];
                    }, $entityClassType->parameters)
                );
            } else
                $entityClassType->parameters = [];
        }

        return $availableSorting;
    }

    /**
     * @param $classId
     * @param $isType
     * @param bool $includeParentsParams
     * @return \Illuminate\Http\RedirectResponse|\StdClass
     * @deprecated Because you probably don't even need all parents' parameters & their types parameters!
     */
    public static function getAllIncludingNested($classId, $isType, $includeParentsParams = true)
    {/*
         * Select 1 entity class using provided Id +
         *  it's type entity class name & id (1:1 relation) +
         *  all it's parents' name & id (M:N relation) +
         *  all it's parameters' metadata (1:N relation)
         */
        $entityClass = EntityClass::get($classId, $isType, true, true);

        if (!$entityClass) {
            return null;
        }

        if (!$isType) {

            /**
             * Get all possible parent metadata
             */
            $possibleParents = [];
            // Here will be maximally as much iterations, as one entity class has maximally parents (currently transceiver has 3, the most)
            foreach ($entityClass->parents as $parentClassId => $parentClassName) {
                $parentEntityClass = EntityClass::get($parentClassId, 0, $includeParentsParams);

                if (!$parentEntityClass) {
                    Session::flash('error', "Somebody is currently messing with the database, please try it again");
                    Input::flash();
                    return Redirect::back();
                }

                $parentEntityClassType = null;

                if (!$isType && $parentEntityClass->type_id !== null && $includeParentsParams) {
                    $parentEntityClassType = EntityClass::get($parentEntityClass->type_id, 1);

                    if (!$parentEntityClassType) {
                        Session::flash('error', "Somebody is currently messing with the database, please try it again");
                        Input::flash();
                        return Redirect::back();
                    }
                }

                // FIXME ! Do not let return all possible parents! Search the ID !!
                //$possibleParents[$parentClassName] = Entity::getEntitiesWithEntityClass($parentEntityClass, $parentEntityClassType, 'id', 'asc', null);
            }

            $entityClass->possibleParents = $possibleParents;
        }
        return $entityClass;
    }

    /**
     * @return string
     */
    public function name()
    {
        return $this->name;
    }

    /**
     * @return bool
     */
    public function isType()
    {
        return $this->is_type;
    }

    /**
     * @return int
     */
    public function requireParent()
    {
        return $this->require_parent;
    }

    /**
     * @return int
     */
    public function entityTypeClassId()
    {
        return $this->entity_type_class_id;
    }

    /**
     * @return bool
     */
    public function hasManufacturer()
    {
        return $this->has_manufacturer;
    }

    /**
     * @return bool
     */
    public function hasSupplier()
    {
        return $this->has_supplier;
    }

    /**
     * @return bool
     */
    public function hasLocation()
    {
        return $this->has_location;
    }

    /**
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
     */
    public function entities()
    {
        return $this->hasMany('App\Entity');
    }

    /**
     * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
     */
    public function parentEntityClasses()
    {
        return $this->belongsToMany('App\EntityClass',
            'possible_parent_classes',
            'child_entity_class_id',
            'parent_entity_class_id')
            ->withPivot(self::PARENT_MUST_NOT_HAVE_PARENT_CONSTRAINT);
    }

}