<?php
namespace Backend\BaseBundle\Controller;

use Backend\BaseBundle\Form\Type;
use Backend\BaseBundle\Form\Type\SubmitBackType;
use Backend\BaseBundle\Guesser\ControllerNameGuesser;
use Backend\BaseBundle\Model;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;

abstract class BaseController extends Controller
{
    const INTERNAL_ACTION = array('index', 'edit', 'sort', 'new', 'search', 'delete', 'view');

    protected $config;

    /** @var  Model\Site */
    protected $site;

    /** @var  ControllerNameGuesser */
    protected $controllerNameGuesser;

    protected function getSite()
    {
        if($this->site === null){
            $this->site = $this->get('session')->get('Site');
        }
        return $this->site;
    }

    protected function postUpdate($object)
    {

    }

    public function __construct()
    {
        $this->controllerNameGuesser = new ControllerNameGuesser(static::class);
    }
    
    public function getNormalizeConfig()
    {
        if ($this->config === null) {
            $this->config = $this->normalizeConfig($this->getControllerConfig());
        }
        return $this->config;
    }

    protected function getControllerConfig()
    {
        return $this->get('backend_base_config')->getControllerConfig($this->controllerNameGuesser->getControllerName());
    }

    protected function normalizeConfig($config)
    {
        if (isset($config['route'])) {
            foreach ($config['route'] as $action => $route) {
                if ($route === true) {
                    $config['route'][$action] = $this->controllerNameGuesser->getActionRouteName($action);
                    continue;
                }
                if ($route === false) {
                    unset($config['route'][$action]);
                }
            }
        }
        return $config;
    }

    public function generateUrlWithSlug($route, $parameters = array(), $referenceType = UrlGeneratorInterface::ABSOLUTE_PATH)
    {
        $parameters = array_merge($parameters, array('slug' => $this->getSite()->getSlug()));
        return $this->generateUrl($route, $parameters, $referenceType);
    }

    protected function redirectToRouteWithSlug($route, array $parameters = array(), $status = 302)
    {
        return $this->redirect($this->generateUrlWithSlug($route, $parameters), $status);
    }

    /**
     * @Route("/")
     */
    public function indexAction(Request $request)
    {
        $config = $this->getNormalizeConfig();
        try {
            $pager = $this->createQuery($this->getSite())
                ->paginate($request->get('p', 1));

            $searchForm = $this->getSearchForm($request);

            return $this->tryRenderTemplate('index', array(
                'sort' => isset($this->getFilters()['sort']) ? $this->getFilters()['sort'] : null,
                'pager' => $pager,
                '_config' => array(
                    'searchForm' => $searchForm ? $searchForm->createView() : null,
                    'security' => isset($config['security']) ? $config['security'] : array(),
                    'route' => isset($config['route']) ? $config['route'] : array(),
                    'index' => isset($config['index']) ? $config['index'] : array(),
                    'batch' => isset($config['batch']) ? $config['batch'] : array(),
                    'action' => isset($config['action']) ? $config['action'] : array(),
                    'columnMap' => $this->getColumnMap($this->guessModelName()),
                ),
            ));
        } catch (\PropelException $e) {
            throw $e;
            $this->resetFilters();
            return $this->redirectToRouteWithSlug($config['route']['index']);
        }
    }

    protected function getColumnMap($modelName)
    {
        $peerName = $modelName.'Peer';
        $tableMap = $peerName::getTableMap();
        $columnMap = array();

        foreach ($tableMap->getColumns() as $column) {
            $columnMap[$column->getPhpName()] = $column->getType();
        }
        return $columnMap;
    }

    protected function getSearchForm(Request $request)
    {
        $config = $this->getNormalizeConfig();
        if (!isset($config['search']) || !isset($config['route']['search'])) {
            return null;
        }
        return $this->getForm(
            "{$this->controllerNameGuesser->getShortName()}_search",
            $config['search'],
            $this->getSearchCondition(),
            array(
                'action' => $this->generateUrlWithSlug($config['route']['search'], $request->attributes->get('_route_params')),
            )
        );
    }

    protected function getSearchCondition()
    {
        $filters = $this->getFilters();
        return isset($filters['filter']) ? $filters['filter'] : null;
    }

    protected function createBaseQuery(Model\Site $site)
    {
        $queryClass = "{$this->guessModelName()}Query";
        $query = $queryClass::create()
            ->filterBySiteId($site->getId());
        return $query;
    }

    /**
     * @param Model\Site $site
     * @return \ModelCriteria
     */
    protected function createQuery(Model\Site $site)
    {
        $query = $this->createBaseQuery($site);
        $this->buildSortQuery($query);
        $this->buildFilterQuery($query);
        return $query;
    }

    protected function buildSortQuery(\ModelCriteria $query)
    {
        $filters = $this->getFilters();

        if (!isset($filters['sort']) || !$filters['sort']) {
            $query->orderBy('id', \Criteria::DESC);
            return;
        }

        $order = $filters['sort'][1] ? \Criteria::ASC : \Criteria::DESC;

        $useJoin = explode(':', $filters['sort'][0]);
        if (count($useJoin) > 1) {
            $column = array_pop($useJoin);
            foreach ($useJoin as $join) {
                $method = "use{$join}Query";
                $query = $query->$method();
            }
            $method = "orderBy{$column}";
            $query->$method($order);
            foreach ($useJoin as $join) {
                $method = "endUse";
                $query = $query->$method();
            }
            return;
        }

        $query->orderBy($filters['sort'][0], $order);
    }

    protected function buildFilterQuery($query)
    {
        $filters = $this->getFilters();
        if(isset($filters['filter'])) {
            $filters['filter'] = $this->convertFilter($filters['filter']);
        }
        $peerClass = $query->getModelPeerName();
        if (isset($filters['filter']) && $filters['filter']) {
            foreach ($filters['filter'] as $column => $filter) {
                if ($column{0} == '_' || $filter === null) {
                    continue;
                }
                if (is_scalar($filter)) {
                    $filter = "%$filter%";
                    $like = \Criteria::LIKE;
                } else {
                    $like = null;
                }
                if (count($columns = explode('::', $column)) > 1) {
                    list($column, $startEnd) = $columns;
                    $query = $query->_and();
                    switch ($startEnd) {
                        case 'startGT':
                            $like = \Criteria::GREATER_THAN;
                            break;
                        case 'startGTE':
                            $like = \Criteria::GREATER_EQUAL;
                            break;
                        case 'endLT':
                            $like = \Criteria::LESS_THAN;
                            break;
                        case 'endLTE':
                            $like = \Criteria::LESS_EQUAL;
                            break;
                    }
                }
                $useJoin = explode(':', $column);
                if (count($useJoin) > 1) {
                    $column = array_pop($useJoin);
                    foreach ($useJoin as $join) {
                        $method = "use{$join}Query";
                        $query = $query->$method();
                    }
                    $method = "filterBy{$column}";
                    $query->$method($filter, $like);
                    foreach ($useJoin as $join) {
                        $method = "endUse";
                        $query = $query->$method();
                    }
                } else {
                    $phpColumnName = $peerClass::translateFieldName($column, \BasePeer::TYPE_PHPNAME, \BasePeer::TYPE_PHPNAME);
                    if (strpos('.', $phpColumnName) !== false) {
                        $query = $query->filterBy($phpColumnName, $filter, $like);
                    } else {
                        $method = "filterBy{$phpColumnName}";
                        $query = $query->$method($filter, $like);
                    }
                }
            }
        }
    }

    /**
     * @Route("/search")
     * @Method({"POST"})
     */
    public function searchAction(Model\Site $site, Request $request)
    {
        $config = $this->getNormalizeConfig();

        if ($request->request->get('reset')) {
            $this->resetFilters();
            return $this->redirectToRouteWithSlug($config['route']['index'], $request->attributes->get('_route_params'));
        }

        $form = $this->getSearchForm($request);
        $form->handleRequest($request);
        if ($form->isValid()) {
            $filters = $this->getFilters();
            $filters['filter'] = $form->getData();
            $this->updateFilters($filters);
        }

        return $this->redirectToRouteWithSlug($config['route']['index'], $request->attributes->get('_route_params'));
    }

    protected function convertFilter($filter)
    {
        foreach ($filter as $column => $condition){
            if(is_array($condition)){
                unset($filter[$column]);
                if(isset($condition['greater'])) {
                    $filter[$column . '::startGTE'] = $condition['greater'];
                }
                if(isset($condition['less'])) {
                    $filter[$column . '::endLTE'] = $condition['less'];
                }
            }
        }
        return $filter;
    }

    /**
     * @Route("/sort")
     */
    public function sortAction(Model\Site $site, Request $request)
    {
        $config = $this->getNormalizeConfig();
        $column = $request->get('sort');
        $filters = $this->getFilters();

        if (!isset($filters['sort'])) {
            $filters['sort'] = array($column, false);
        }
        $filters['sort'][0] = $column;
        $filters['sort'][1] = !$filters['sort'][1]; //false means desc
        $this->updateFilters($filters);
        return $this->redirectToRouteWithSlug($config['route']['index'], $request->attributes->get('_route_params'));
    }

    /**
     * @param $pk
     * @return \BaseObject
     */
    protected function findObjectByPk($pk)
    {
        $queryClass = "{$this->guessModelName()}Query";
        return $queryClass::create()->findPk($pk);
    }

    /**
     *
     * @return \BaseObject
     */
    protected function createObject($siteId)
    {
        $objectClass = $this->guessModelName();
        $object = new $objectClass();
        $object->setSiteId($siteId);
        return $object;
    }

    protected function logOperation(\BaseObject $object)
    {
        $modify = array();
        foreach($object->getModifiedColumns() as $column){
            list($tableName, $columnName) = explode('.', $column);
            $modify[$columnName] = $object->getByName($columnName, \BasePeer::TYPE_FIELDNAME);
        }
        $peer = $object->getPeer();
        $tableName = $peer::TABLE_NAME;
        $this->get('backend.base_bundle.operationlog')->log(
            $this->getUser(),
            $object->isNew()?Model\OperationLogPeer::MODIFY_TYPE_NEW:($object->isDeleted()?Model\OperationLogPeer::MODIFY_TYPE_DELETE:Model\OperationLogPeer::MODIFY_TYPE_UPDATE),
            $tableName,
            $object->isDeleted()?$object->toArray(\BasePeer::TYPE_FIELDNAME):$modify
        );
    }
    protected function updateData($form)
    {
        try {
            $object = $form->getData();
            $this->logOperation($object);
            $object->save();
            $this->doPostUpdate($form);
            $this->addFlash('success', 'message.data.success.save');
        }
        catch(\PropelException $e){
            throw $e;
            $this->addFlash('error', 'message.data.error.save');
        }
    }
    /**
     * @Route("/new")
     * @ParamConverter("site")
     */
    public function newAction(Model\Site $site, Request $request)
    {
        $config = $this->getNormalizeConfig();
        $object = $this->createObject($site->getId());
        $form = $this->getForm($this->controllerNameGuesser->getShortName(), $this->getNormalizeConfig()['form'], $object);

        $form->handleRequest($request);

        if ($form->isValid()) {
            $this->updateData($form);
            $editUrl = null;
            if(isset($config['route']['edit'])){
                $editUrl = $this->generateUrlWithSlug($config['route']['edit'], array('id' => $form->getData()->getId()));
            }
            $url = $request->get('return_url', $editUrl);
            return $this->redirect($url);
        }

        return $this->tryRenderTemplate('edit', array('edit_form' => $form->createView()));
    }

    protected function fetchObject($id)
    {
        $site = $this->getSite();

        if (!($object = $this->findObjectByPk($id)) || $object->getSiteId() != $site->getId()) {
            throw $this->createNotFoundException();
        }
        return $object;
    }

    /**
     * @Route("/{id}/edit")
     */
    public function editAction(Request $request, $id)
    {
        $config = $this->getNormalizeConfig();
        $form = $this->getForm($this->controllerNameGuesser->getShortName(), $this->getNormalizeConfig()['form'], $this->fetchObject($id));

        $form->handleRequest($request);

        if ($form->isValid()) {
            $this->updateData($form);
            $url = $request->get('return_url', $this->generateUrlWithSlug($config['route']['edit'], array('id' => $form->getData()->getId())));
            return $this->redirect($url);
        }

        return $this->tryRenderTemplate('edit', array('edit_form' => $form->createView()));
    }

    /**
     * @Route("/{id}/delete")
     */
    public function deleteAction(Request $request, $id)
    {
        $config = $this->getNormalizeConfig();
        $tokenManager = $this->get('security.csrf.token_manager');
        $token = $tokenManager->getToken('delete');

        if($token != $request->get('_token')){
            throw $this->createNotFoundException();
        }

        $object = $this->fetchObject($id);

        try{
            $object->delete();
            $this->logOperation($object);
            $this->addFlash('success', 'message.data.success.delete');
        }
        catch(\PropelException $e){
            $this->addFlash('error', 'message.data.error.delete');
        }

        $defaultReturnUrl = $this->generateUrlWithSlug($config['route']['index']);
        $referer = $request->headers->get('referer', $defaultReturnUrl);
        return $this->redirect($referer);
    }

    protected function form_walk_recursive(FormInterface $form, $callback)
    {
        foreach ($form as $child) {
            $callback($child);
            $this->form_walk_recursive($child, $callback);
        }
    }

    protected function doPostUpdate(FormInterface $form)
    {
        $this->form_walk_recursive($form, function (FormInterface $form) {
            if (is_callable($postUpdateCallback = $form->getConfig()->getOption('post_update'))) {
                call_user_func($postUpdateCallback, $form);
            }
        });
        $this->postUpdate($form->getData());
    }

    protected function getForm($formName, $formConfig, $object, $option = array())
    {
        $formType = new Type\BaseFormType(function (FormBuilderInterface $builder, array $options) use ($formConfig) {
            foreach ($formConfig as $field){
                list($child, $type, $option) = $field;
                if($type == SubmitBackType::class){
                    $builder->add($child.'2', $type, array_merge(array('translation_domain' => 'forms'), $option));
                    break;
                }
            }
            foreach ($formConfig as $field) {
                list($child, $type, $option) = $field;
                $builder->add($child, $type, array_merge(array('translation_domain' => 'forms'), $option));
            }
        }, $formName);

        return $this->createForm($formType, $object, $option);
    }

    protected function updateFilters($filters)
    {
        $session = $this->get('session');
        $session->set('backend_filter_' . static::class, $filters);
    }

    protected function getFilters()
    {
        $session = $this->get('session');
        return $session->get('backend_filter_' . static::class);
    }

    protected function resetFilters()
    {
        $this->updateFilters(array());
    }

    protected function guessModelName()
    {
        $config = $this->getNormalizeConfig();
        if (!isset($config['model'])) {
            $modelName = $this->controllerNameGuesser->getShortName();
        } else {
            $modelName = $config['model'];
        }
        if (strpos($modelName, '\\') === false) {
            $modelName = "\\{$this->controllerNameGuesser->getBundleNamespace()}\\Model\\{$modelName}";
        }
        return $modelName;
    }

    protected function tryRenderTemplate($action, $params)
    {
        $name = $this->controllerNameGuesser->getShortName();
        $bundleName = $this->controllerNameGuesser->getBundleName();
        $templateName = "$bundleName:Backend/$name:$action.html.twig";
        if (!$this->get('templating')->exists($templateName)) {
            $templateName = "BackendBaseBundle:Backend/Base:$action.html.twig";
        }
        return $this->render($templateName, $params);
    }

    /**
     * @Route("/{id}/view")
     */
    public function viewAction($id, Request $request)
    {
        $config = $this->getNormalizeConfig();
        return $this->tryRenderTemplate('view', array(
            '_config' =>  array(
                'view' => isset($config['view']) ? $config['view'] : array(),
                'columnMap' => $this->getColumnMap($this->guessModelName()),
            ),
            'object' => $this->fetchObject($id),
            'returnUrl' => $request->headers->get('referer'),
        ));
    }
}