<?php

namespace App\Http\Controllers;

use App\Entity;
use App\EntityClass;
use App\Http\ViewHelpers\ViewFeeder;
use App\Http\Requests\EntityRequest;
use App\Permission;
use Carbon\Carbon;
use DB;
use Illuminate\Database\QueryException;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
use Input;
use Lang;
use Redirect;
use Session;

/**
 * Class EntityController
 * @package App\Http\Controllers
 */
class EntityController extends Controller implements ViewFeederAwareController
{

    /**
     * Create a new controller instance.
     */
    public function __construct()
    {
        $this->middleware('auth');
    }

    /**
     * @return \Illuminate\Http\RedirectResponse
     */
    public function indexDistinguish()
    {
        $firstEntity = DB::table('entities as d')
            ->join('entity_classes as dc', 'd.entity_class_id', '=', 'dc.id')
            ->select('dc.id as id')
            ->where([
                ['dc.is_type', '=', 0]
            ])
            ->orderBy('dc.id', 'asc')
            ->first();

        if ($firstEntity)
            return Redirect::route(self::getRouterName() . '.index', $firstEntity->id);
        else {
            Session::flash('error', "No entity type was not found, you can create new one here.");
            return Redirect::route(self::getRouterName() . '.createDistinguish');
        }
    }

    /**
     * Show the screen for choosing the entity to be created.
     *
     * @return \Illuminate\Http\Response
     */
    public function createDistinguish()
    {
        $viewFeeder = new ViewFeeder($this, ViewFeeder::CREATE);

        $entityClasses = DB::table('entity_classes as dc')
            ->select([
                'dc.id as id',
                'dc.name as name',
            ])
            ->where([
                ['dc.is_type', '=', 0]
            ])
            ->distinct()
            ->orderBy('name', 'asc')
            ->get();

        return view('entity.createDistinguish', $viewFeeder
            ->size(4)
            ->results($entityClasses)
            ->feed());
    }

    /**
     * Show the form for creating a new resource.
     *
     * @param $classId
     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response
     */
    public function create($classId)
    {
        $viewFeeder = new ViewFeeder($this, ViewFeeder::CREATE);

        $entityClass = EntityClass::get($classId, 0, true, true);

        if (!$entityClass) {
            Session::flash('error', "Entity with class id '$classId' not found.");
            return Redirect::route(self::getRouterName() . '.createDistinguish');
        }

        if ($entityClass instanceof RedirectResponse) {
            return $entityClass;
        }

        return view('entity.create', $viewFeeder
            ->size(6)
            ->result($entityClass)
            ->singular($entityClass->name)
            ->classId($classId)
            ->autoComplete([
                'location' => $entityClass->has_location,
                'manufacturer' => $entityClass->has_manufacturer,
                'supplier' => $entityClass->has_supplier
            ])
            ->feed());
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param Request $request
     * @param $classId
     * @return RedirectResponse|\Illuminate\Http\Response|\StdClass
     */
    public function store(Request $request, $classId)
    {
        $entityClass = EntityClass::get($classId, 0, true, true);

        $redirectBack = Entity::validateEntityChange($entityClass, $request, null, 0);

        if ($redirectBack) {
            Input::flash();
            return Redirect::back();
        }
        $entityId = null;

        DB::transaction(function () use ($request, $entityClass, $classId, &$entityId) {

            $entityId = DB::table('entities')
                ->insertGetId([
                    'entity_class_id' => $classId,
                    'location_id' => $entityClass->new_location_id, // Got already from validation
                    'manufacturer_id' => $entityClass->new_manufacturer_id, // Got already from validation
                    'supplier_id' => $entityClass->new_supplier_id, // Got already from validation
                    'parent_entity_id' => $request->get('parent_entity_id', null),
                    'entity_type_id' => $request->get('type_id', null),
                    'created_at' => Carbon::now(),
                    'updated_at' => Carbon::now(),
                ]);

            $parametersTable = DB::table('parameters');

            foreach ($entityClass->parameters as $parameter) {
                $desiredValue = $request->get('param_' . $parameter['name'], false);
                if ($desiredValue !== false)
                    $parametersTable
                        ->insert([
                            'entity_class_parameter_id' => $parameter['id'],
                            'entity_id' => $entityId,
                            $parameter['type'] => $desiredValue
                        ]);
            }
        });

        if ($entityId === null) {
            Session::flash('error', "Failed creating entity. Please see logs for more details");
            Input::flash();
            return Redirect::back();
        }

        return Redirect::route('entities.show', [
            'classId' => $classId,
            'id' => $entityId
        ]);
    }

    /**
     * Display a listing of the resource.
     *
     * @param Request $request
     * @param $entityClassId
     * @return \Illuminate\Http\RedirectResponse
     */
    public function index(Request $request, $entityClassId)
    {
        $this->validate($request, [
            'ordering' => 'in:asc,desc',
            'count' => 'integer|min:5|max:100'
        ]);

        $entityClass = EntityClass::get($entityClassId, 0);

        if (!$entityClass) {
            Session::flash('error', "Entity class Id $entityClassId was not found.");
            return Redirect::route(self::getRouterName() . '.createDistinguish');
        }

        $entityClassType = null;

        if ($entityClass->type_id !== null) {
            $entityClassType = EntityClass::get($entityClass->type_id, 1);
        }

        $this->validate($request, [
            'sort' => Rule::in(EntityClass::availableSorting($entityClass, $entityClassType))
        ]);

        $sort = $request->get('sort', 'id');
        $ordering = $request->get('ordering', 'desc');

        list($columns, $entities) = Entity::getEntitiesWithEntityClass($entityClass, $entityClassType, $sort, $ordering, $request);

        if ($request->get('type_id', null)) {
            $columns[] = 'type_id';
        }
        $viewFeeder = new ViewFeeder($this, ViewFeeder::LIST);

        if (count($columns) > 8)
            $viewFeeder->fluid();

        return view('results.list', $viewFeeder
            ->results($entities)
            ->sorting($sort, $ordering)
            ->size(12)
            ->columns($columns)
            ->permissions([
                'canEdit' => Permission::EDIT_DEVICE,
                'canDelete' => Permission::DELETE_DEVICE,
            ])
            ->classId($entityClassId)
            ->heading('List of entities of class')
            ->plural($entityClass->name)
            ->feed()
        );
    }

    /**
     * Display the specified resource.
     *
     * @param  int $entityClassId
     * @param  int $id
     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response
     */
    public function show($entityClassId, $id)
    {
        $entity = null;
        $columns = null;
        $entityClass = null;

        // Prevent anything to change between our selects
        DB::statement('set autocommit = 0');
        DB::statement('set transaction isolation level serializable;');
        DB::transaction(function () use ($entityClassId, $id, &$entity, &$columns, &$entityClass) {

            $entityClass = EntityClass::get($entityClassId, 0);

            if (!$entityClass) {
                Session::flash('error', "Entity with class Id $entityClassId was not found, please use one of the following links to create a new entity.");
                return Redirect::route(self::getRouterName() . '.createDistinguish');
            }

            $entityClassType = null;

            if ($entityClass->type_id !== null) {
                $entityClassType = EntityClass::get($entityClass->type_id, 1);
            }


            list($columns, $entity) = Entity::getEntityWithEntityClass($entityClass, $entityClassType, $id);

            if (!$entity) {
                Session::flash('error', "Entity with id '$id' was not found");
                return Redirect::route(self::getRouterName() . '.index', [
                    'classId' => $entityClassId
                ]);
            }
        });

        $viewFeeder = new ViewFeeder($this, ViewFeeder::SHOW);

        return view('entity.show', $viewFeeder
            ->result($entity)
            ->columns($columns)
            ->classId($entityClassId)
            ->permissions([
                'canEdit' => Permission::EDIT_DEVICE,
                'canDelete' => Permission::DELETE_DEVICE
            ])
            ->size(6)
            ->singular($entityClass->name)
            ->feed());
    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param  int $classId
     * @param $entityId
     * @return RedirectResponse|\Illuminate\Http\Response
     */
    public function edit($classId, $entityId)
    {
        $viewFeeder = new ViewFeeder($this, ViewFeeder::EDIT);

        $entityClass = EntityClass::get($classId, 0, true, true);

        if (!$entityClass) {
            Session::flash('error', "Entity with class id '$classId' not found.");
            return Redirect::route(self::getRouterName() . '.createDistinguish');
        }

        if ($entityClass instanceof RedirectResponse) {
            return $entityClass;
        }

        list($columns, $entity) = Entity::getEntityWithEntityClass($entityClass, null, $entityId);

        if (!$entity) {
            Session::flash('error', "Entity with id '$classId' not found.");
            return Redirect::route(self::getRouterName() . '.index', ['classId' => $classId]);
        }

        if ($entity instanceof RedirectResponse) {
            return $entity;
        }

        $entity->class = $entityClass;

        return view('entity.edit', $viewFeeder
            ->size(6)
            ->result($entity)
            ->singular($entityClass->name)
            ->columns($columns)
            ->classId($classId)
            ->autoComplete([
                'location' => $entityClass->has_location,
                'manufacturer' => $entityClass->has_manufacturer,
                'supplier' => $entityClass->has_supplier
            ])
            ->feed());
    }

    /**
     * Update the specified resource in storage.
     *
     * @param Request $request
     * @param $classId
     * @param $entityId
     * @return RedirectResponse|\Illuminate\Http\Response
     */
    public function update(Request $request, $classId, $entityId)
    {

        $entityClass = EntityClass::get($classId, 0, true, true);

        $redirectBack = Entity::validateEntityChange($entityClass, $request, $entityId, 0);

        if ($redirectBack) {
            Input::flash();
            return Redirect::back();
        }

        DB::transaction(function () use ($request, $entityClass, $classId, $entityId) {

            $toUpdate = [
                'entity_class_id' => $classId,
                'location_id' => $entityClass->new_location_id, // Got already from validation
                'manufacturer_id' => $entityClass->new_manufacturer_id, // Got already from validation
                'supplier_id' => $entityClass->new_supplier_id, // Got already from validation
                'parent_entity_id' => $request->get('parent_entity_id', null),
                'entity_type_id' => $request->get('type_id', null),
                'updated_at' => Carbon::now(),
            ];

            $finalState = $request->get('state_id', false);

            if ($finalState !== false)
                $toUpdate['state_id'] = $finalState;

            DB::table('entities')
                ->where('id', '=', $entityId)
                ->update($toUpdate);

            foreach ($entityClass->parameters as $parameter) {

                $desiredValue = $request->get('param_' . $parameter['name'], null);
                DB::table('parameters')
                    ->updateOrInsert(
                        [
                            'entity_class_parameter_id' => intval($parameter['id']),
                            'entity_id' => intval($entityId),
                        ],
                        [
                            $parameter['type'] => $desiredValue
                        ]);
            }
        });

        if ($entityId === null) {
            Session::flash('error', "Failed updating entity. Please see logs for more details");
            Input::flash();
            return Redirect::back();
        }

        return Redirect::route(self::getRouterName() . '.show', [
            'classId' => $classId,
            'id' => $entityId
        ]);
    }


    /**
     * Remove the specified resource from storage.
     *
     * @param $classId
     * @param $entityId
     * @return \Illuminate\Http\RedirectResponse
     */
    public function destroy($classId, $entityId)
    {
        $hasChildren = DB::table('entities as d')
            ->join('entities as dp', 'd.parent_entity_id', '=', 'dp.id')
            ->where([
                ['dp.id', '=', $entityId]
            ])
            ->count();

        if ($hasChildren) {
            Session::flash('error', "Cannot remove this entity type, because it has $hasChildren children. Remove it/them first.");
            return Redirect::back();
        }

        try {
            $deleted = DB::table('entities')
                ->delete($entityId);

        } catch (QueryException $e) {
            Session::flash('error', $e->getMessage());
            return Redirect::back();
        }

        if ($deleted)
            Session::flash('success', Lang::trans('entity.delete_success', ['id' => $entityId]));
        else
            Session::flash('error', Lang::trans('entity.delete_failed', ['id' => $entityId]));

        return Redirect::back();
    }

    /**
     * Gets the name of the router to be used from the view.
     *
     * @return string
     */
    public static function getRouterName(): string
    {
        return 'entities';
    }

    /**
     * Returns singular of object controller is working with.
     *
     * @return string
     */
    public function getSingular(): string
    {
        return 'Entity';
    }

    /**
     * Returns plural of object controller is working with.
     *
     * @return string
     */
    public function getPlural(): string
    {
        return 'Entities';
    }


    /**
     * Returns array of columns to be shown when listing objects controller is working with.
     * @return array
     */
    public function getColumns(): array
    {
        return [];
    }
}
