<?php

namespace App\Helpers;

use App\Databases;
use App\Constraints;
use App\Repository;
use App\RepositoryColumns;
use Illuminate\Support\Arr;
use function Ramsey\Uuid\v1;

class InputValidation {

    private $rules = [];
    private $formrequest;
    private $model;
    private $skipped = [];

    public function skip(Array $array) {
        $this->skipped = $array;
    }

    public function all() {
        #dd($this->rules);
        return $this->rules;
    }


    public function __construct($formrequest, &$model) {


        $this->model = &$model;
        $this->formrequest = $formrequest;

        try {

            if(!$this->model->exists):

                $form = $this->model->form;

                $forms = \Runtime::make('forms')->with('_scope')->where('_repository', $this->model->getConfig()->getKey())
                    ->whereHas('_scope', function($query) {
                        $query->where('identifier', 'create');
                    })
                    ->get();

                if($input = \Request::get($this->getColumnName('form'))) {
                    $this->model->form = $forms->firstWhere('ID', $input);
                    if(!$form) throw new \Exception('invalid form');
                }

                elseif($form && !$forms->firstWhere('ID', $form)) {
                    throw new \Exception('invalid form');
                }

                elseif($form && $this->model->form = $forms->firstWhere('ID', $form));

                elseif(1 == $forms->count()) {
                    $this->model->form = $forms->first();
                }

                elseif($forms->count()) {
                    #dd($forms->filter(function($item) { return in_array($item->_scope->identifier, ['insert']); }));
                    throw new \Exception('too much forms. please select one');
                }

                else throw new \Exception('no forms found');


            else:

                $form = $this->model->form;


                $forms = \Runtime::make('forms')->with('_scope')->where('_repository', $this->model->getConfig()->getKey())
                    ->whereHas('_scope', function($query) {
                        $query->where('identifier', 'edit');
                    })
                    ->get();

                if($input = \Request::get($this->getColumnName('form'))) {
                    $this->model->form = $forms->firstWhere('ID', $input);
                    if(!$form) throw new \Exception('invalid form');
                }

                elseif($form) {

                    $this->model->form = $forms->firstWhere('ID', $form);
                    if(!$this->model->form) {
                        throw new \Exception('have a look');
                    }
                }

                elseif(!$this->model->_meta OR !$this->model->_meta->_doctype) {
                    throw new \Exception('doctyype misssing');
                }

                // Wenn kein Formular gewünscht ist prüfen wir, ob zum Doctype ein eindeutiges
                // Edit-Formular existiert
                elseif($this->model->_meta->_doctype->_update_form) {

                    $this->model->form = $forms->firstWhere('ID', $this->model->_meta->_doctype->getAttribute('_update_form'));
                    //$this->model->form->setElements($model);
                }

                elseif(1 == $forms->count()) {
                    $this->model->form = $forms->first();
                }

                elseif($forms->count()) {
                    throw new \Exception('too much forms. please select one');
                }

                else throw new \Exception('no update form found for '. $this->model->getConfig()->signifier);

            endif;

            $this->model->form->setElements($model);

            if($this->model instanceof \App\Models\Pivot) {
                foreach($this->model->form->_elements->where('identifier', '!=', 'backlink') AS $element)
                    if(Arr::has($formrequest->data, $this->getColumnName($element->signifier)))
                        $this->model->setAttribute($element->signifier, Arr::get($formrequest->data, $this->getColumnName($element->signifier)));

            } else foreach($this->model->form->_elements->where('identifier', '!=', 'backlink') AS $element)
                if(Arr::has($formrequest->data, $this->getColumnName($element->signifier)))
                    $this->model->setAttribute($element->signifier, Arr::get($formrequest->data, $this->getColumnName($element->signifier)));


            $this->model->dataPath = $this->formrequest->stack->implode('.');


        } catch(\Throwable $e) {
            throw $e;
        }


    }

    public function getColumnName($column)
        {
        if(is_object($column)) $column = $column->signifier;
        #return $this->model->path->implode('.') . '.' . $column;
            return $this->formrequest->stack->implode('.') . '.' . $column;
        }

    public function makeRules() {
   
        foreach ($this->model->form->_elements->load('_format')->getInlineElements() AS $column) {

            if (in_array($column->signifier, $this->skipped)) return;

            if (!in_array($column->min, [-1, 0])) $this->appendRule($this->getColumnName($column), 'min:' . $column->min);
            if (!in_array($column->max, [-1, 0])) $this->appendRule($this->getColumnName($column), 'max:' . $column->max);

            $this->appendRule($this->getColumnName($column), $column->type_validator);

            if ($this->model->exists):
                switch($column->_meta->_doctype->identifier) {
                    case "column":
                        $this->appendRule($this->getColumnName($column), $column->_format->update_validations);
                        break;
                }

                $this->appendRule($this->getColumnName($column), $column->update_validations);
            else:
                switch($column->_meta->_doctype->identifier) {
                    case "column":
                        $this->appendRule($this->getColumnName($column), $column->_format->create_validations);
                        break;
                }
                $this->appendRule($this->getColumnName($column), $column->create_validations);
            endif;
        }

        $columns = [];

        foreach($this->model->form->_elements->whereIn('doctype', ['backlink','attachment']) AS $column) {
        #        ->concat($this->model->form->_elements->getSingleAssignmentsElements()
        #            ->concat($this->model->form->_elements->getSingleChoiceElements()
        #                ->concat($this->model->form->_elements->getMultipleChoiceElements()))) AS $column) {
                            $columns[] = $column->_right_key;
                            $columns[] = $column->_left_key;
                        }

        $cache = \Runtime::make('repository_columns')->with('_repository._database')->whereIn('ID', array_unique($columns))->get();

        foreach($this->model->form->_elements->getSingleChoiceElements() AS $column) {

            $input = \Request::input($this->getColumnName($column));

            if (is_null($input)):

            elseif(is_string($input)):
                $right_column = $cache->where('ID', $column->_right_key)->first();
                $right = $right_column->_repository;
                $database = $right_column->_repository->_database;
                $this->appendRule($this->getColumnName($column), 'exists:' . $database->getKey() . '.' . $right->signifier . ',' . $right->getKeyName());

            else: throw new \Exception('invalid input type');

            endif;
        }


        foreach($this->model->form->_elements->getMultipleChoiceElements() AS $column) {


            $input = \Request::input($this->getColumnName($column));

            if($input) throw new \Exception('not supported yet');

            // Form input
            if (\Request::has('form')) {

                // Hierbei handelt es sich um eine Select-Element mit Attribute multiple.
                // Es werden nur die referenzierten Schlüssel übergeben. Etwaige Änderungen
                // an den im Select-Element gelisteten Datensätzen oder das Hinzufügen neuer
                // Auswahlmöglichketen werden in einem eigenen Workflow-Formular durchgeführt.
                // Somit sind Änderungen/Erweiterungen der Auswahlelemente hier nicht möglich
                // und werden auch nicht übernommen.

                if (is_null($input)):
                    // No input data provided, so no deeper checks
                    // If data required this is checked by global column rules

                else:


                    // Die refernzierten Modelle liegen als Liste von Schlüsseln vor.
                    // Es bleibt daher zu prüfen ob die Modelle alle Existieren.
                    // Die im Konstruktor definierte Schlüssel-Prüfung greift hier nicht.

                    $this->appendRule($this->getColumnName($column), 'array');

                    $right_column = $cache->where('ID', $column->_right_key)->first();
                    $right = $right_column->_repository;
                    $database = $right_column->_repository->_database;

                    if (is_iterable($input)) foreach ($input AS $index => $key) {
                        $this->appendRule($this->getColumnName($column) . '.' . $index, 'exists:' . $database->getKey() . '.' . $right->getTable() . ',', $right->make()->getKeyName());
                    }

                endif;

            }

            else {

                $this->appendRule($this->getColumnName($column), 'array');

                $right_column = $cache->where('ID', $column->_right_key)->first();
                $right = $right_column->_repository->make();

                $this->formrequest->stack->push($column->signifier);

                if (is_iterable($input)) foreach ($input AS $index => $row) {

                    $this->formrequest->stack->push($index);

                    if (isset($row[$right->getKeyName()])) {

                        $m = $right->with('_meta')->find($row[$right->getKeyName()]);
                        #$m = $this->model->{$column->signifier}()->with('_meta')->where($right->getTable() . '.ID', $row[$right->getKeyName()])->first();

                        #$rules = new InputValidation($this->formrequest, $m);
                        #$this->formrequest->disposed->push($m);
                        #$rules->makeRules();
                        #$this->rules = array_merge($this->rules, $rules->all());
                    } else {
                        // Validate new option
                        $m = $right->newInstance();# ->using(new \App\Models\Autoflow\PHPXPress\Joins())->withPivot();
                        #$rules = new InputValidation($this->formrequest, $m);
                        #$this->formrequest->disposed->push($m);
                        #$rules->makeRules();
                        #$this->rules = array_merge($this->rules, $rules->all());
                    }

                    $m->constraint = $column;
                    $m->parent = $this->model;

                    $rules = new InputValidation($this->formrequest, $m);
                    $this->formrequest->disposed->push($m);
                    $rules->makeRules();
                    $this->rules = array_merge($this->rules, $rules->all());

                    $this->formrequest->stack->pop();
                }

                $this->formrequest->stack->pop();
                #$repository->resetModel();

            }

        }

        foreach($this->model->form->_elements->getAttachedToElements() AS $column) {

            $input = \Request::input($this->getColumnName($column));
            if($this->model->form) {

                if (is_null($input)):
                    // No input data provided, so no deeper checks
                    // If data required this is checked by global column rules

                else:
                    $left_column = $cache->where('ID', $column->_left_key)->first();

                if(!$left_column) throw new \Exception('have a look');


                    $left = $left_column->_repository;
                    $database = $left_column->_repository->_database;

                    $this->appendRule($this->getColumnName($column), 'exists:' . $database->getKey() . '.' . $left->signifier . ',' . $left->make()->getKeyName());
                endif;

            }
            /**
             *
             * Handle ParamQuery/API Requests from now on ..
             *
             **/

            elseif (is_string($input)) {

                // @TOOO
                // Gute Idee, funktioniert aber nicht wenn anstelle "ID" "ROWID" zum Einsatz kommt.
                //$this->appendRule($this->getColumnName($column), 'sha1');

                // Handle paramquery/API single selection request (Key-based)

                $left_column = $cache->where('ID', $column->_left_key)->first();
                $left = $left_column->_repository;
                $database = $left_column->_repository->_database;
                $this->appendRule($this->getColumnName($column), 'exists:' . $database->getKey() . '.' . $left->signifier . ',' . $left->make()->getKeyName());

            } else {

                // TODO: Input is null or anything else than string or array .. so we only have to check if this is allowed
                //dd($input);
                throw new \Exception('have a look - unknown input type');
            }

        }

        foreach($this->model->form->_elements->getBelongsToManyElements() AS $column) {

            $input = \Request::input($this->getColumnName($column));

            if($input) throw new \Exception('not supported yet');

            // Form input
            if (\Request::has('form')) {

                // Hierbei handelt es sich um eine Select-Element mit Attribute multiple.
                // Es werden nur die referenzierten Schlüssel übergeben. Etwaige Änderungen
                // an den im Select-Element gelisteten Datensätzen oder das Hinzufügen neuer
                // Auswahlmöglichketen werden in einem eigenen Workflow-Formular durchgeführt.
                // Somit sind Änderungen/Erweiterungen der Auswahlelemente hier nicht möglich
                // und werden auch nicht übernommen.

                if (is_null($input)):
                    // No input data provided, so no deeper checks
                    // If data required this is checked by global column rules

                else:

                    // Die refernzierten Modelle liegen als Liste von Schlüsseln vor.
                    // Es bleibt daher zu prüfen ob die Modelle alle Existieren.
                    // Die im Konstruktor definierte Schlüssel-Prüfung greift hier nicht.

                    $this->appendRule($this->getColumnName($column), 'array');

                    $left_column = $cache->where('ID', $column->_left_key)->first();
                    $database = $left_column->_repository->_database;
                    $left = $left_column->_repository;

                    if (is_iterable($input)) foreach ($input AS $index => $key) {
                        $this->appendRule($this->getColumnName($column) . '.' . $index, 'exists:' . $database->getKey() . '.' . $left->getTable() . ',', $left->make()->getKeyName());
                    }

                endif;

            }

            else {


                $this->appendRule($this->getColumnName($column), 'array');

                $left_column = $cache->where('ID', $column->_left_key)->first();
                $left = $left_column->_repository->make();
                $this->formrequest->stack->push($column->signifier);

                if (is_iterable($input)) foreach ($input AS $index => $row) {

                    if (isset($row[$left->getKeyName()])) {

                        $m = $left->with('_meta')->find($row[$right->getKeyName()]);
                        #$m = $this->model->{$column->signifier}()->with('_meta')->where($left->getTable() . '.ID', $row[$left->getKeyName()])->first();

                        #$this->formrequest->stack->push($index);
                        #$rules = new InputValidation($this->formrequest, $m);
                        #$this->formrequest->disposed->push($m);
                        #$rules->makeRules();
                        #$this->rules = array_merge($this->rules, $rules->all());
                    } else {
                        $m = $left->newInstance();#->using(new \App\Models\Autoflow\PHPXPress\Joins())->withPivot();
                        #$this->formrequest->stack->push($index);
                        #$rules = new InputValidation($this->formrequest, $m);
                        #$this->formrequest->disposed->push($m);
                        #$rules->makeRules();
                        #$this->rules = array_merge($this->rules, $rules->all());
                    }

                    $m->constraint = $column;
                    $m->parent = $this->model;

                    $this->formrequest->stack->push($index);
                    $rules = new InputValidation($this->formrequest, $m);
                    $this->formrequest->disposed->push($m);
                    $rules->makeRules();
                    $this->rules = array_merge($this->rules, $rules->all());

                    $this->formrequest->stack->pop();
                }

                $this->formrequest->stack->pop();
                #$repository->resetModel();

            }


        }

    }

    public function exists() {
        $key = $this->model->getConfig()->getKeyName();
        $col = $this->model->form->_elements->where('signifier', $key)->first();
        $con = $this->model->getConnectionName();
        $this->appendRule($this->getColumnName($col), 'exists:' .$con . '.' . $this->model->getTable() . ',' . $key);
    }

    protected function appendRule($column, $rule)
        {
        if(!is_string($rule)) return;
        if(!is_string($column)) throw new \Exception('invalid data');
        if(!array_key_exists($column, $this->rules)) $this->rules[$column] = $rule;
        elseif('' != $this->rules[$column]) $this->rules[$column] = $this->rules[$column] .'|' .$rule;
        else $this->rules[$column] = $rule;

        }

    protected function prependRule($column, $rule)
        {
        if(!is_string($rule)) return;
        if(!is_string($column)) throw new \Exception('invalid data');
        if(!array_key_exists($column, $this->rules)) $this->rules[$column] = $rule;
        elseif('' != $this->rules[$column]) $this->rules[$column] = $rule . '|' . $this->rules[$column];
        else $this->rules[$column] = $rule;
        }

    public function merge(array $rules) {
        $this->rules = array_merge($this->rules, $rules);
        }

    }
