<?php

namespace Croogo\Nodes\Model\Table;

use Cake\Database\Schema\TableSchema;
use Cake\Event\Event;
use Cake\I18n\I18n;
use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\TableRegistry;
use Cake\Routing\Router;
use Cake\Validation\Validator;
use Croogo\Core\Croogo;
use Croogo\Core\Model\Table\CroogoTable;
use Croogo\Nodes\Model\Entity\Node;

class NodesTable extends CroogoTable
{
    public function initialize(array $config)
    {
        $this->addBehavior('Tree');
        $this->addBehavior('Croogo/Core.BulkProcess', [
            'actionsMap' => [
                'promote' => 'bulkPromote',
                'unpromote' => 'bulkUnpromote',
            ],
        ]);
        $this->addBehavior('Croogo/Core.Publishable');
        $this->addBehavior('Croogo/Core.Url', [
            'url' => [
                'plugin' => 'Croogo/Nodes',
                'controller' => 'Nodes',
                'action' => 'view',
            ],
            'fields' => [
                'type',
                'slug',
            ],
        ]);
        $this->addBehavior('Croogo/Core.Trackable');
        $this->addBehavior('Croogo/Core.Visibility');
        $this->addBehavior('Croogo/Core.Cached', [
            'groups' => ['nodes']
        ]);
        $this->addBehavior('Search.Search');
        $this->addBehavior('Timestamp');
        $this->addBehavior('Croogo/Core.Copyable', [
            'stripFields' => [
                'model_taxonomies',
            ],
        ]);

        $this->belongsTo('Croogo/Users.Users');
        $this->belongsTo('Parent', [
            'className' => 'Croogo/Nodes.Nodes',
            'foreignKey' => 'parent_id',
        ]);

        $this->searchManager()
            ->add('q', 'Search.Finder', [
                'finder' => 'filterPublishedNodes'
            ])
            ->add('filter', 'Search.Finder', [
                'finder' => 'filterNodes'
            ])
            ->add('title', 'Search.Like', [
                'field' => $this->aliasField('title'),
                'before' => true,
                'after' => true
            ])
            ->add('slug', 'Search.Like', [
                'field' => $this->aliasField('slug'),
                'before' => true,
                'after' => true
            ])
            ->add('type', 'Search.Value', [
                'field' => $this->aliasField('type')
            ])
            ->add('status', 'Search.Value', [
                'field' => $this->aliasField('status')
            ])
            ->add('promote', 'Search.Value', [
                'field' => $this->aliasField('promote')
            ]);
    }

    protected function _initializeSchema(TableSchema $table)
    {
        $table->setColumnType('visibility_roles', 'encoded');

        return parent::_initializeSchema($table);
    }

    /**
     * @param \Cake\ORM\Query $query
     * @param array $options
     * @return \Cake\ORM\Query
     */
    public function findFilterNodes(Query $query, array $options = [])
    {
        if (!empty($options['filter'])) {
            $filter = '%' . $options['filter'] . '%';
            $query->andWhere([
                'or' => [
                    $this->aliasField('title') . ' like' => $filter,
                    $this->aliasField('excerpt') . ' like' => $filter,
                    $this->aliasField('body') . ' like' => $filter,
                    $this->aliasField('terms') . ' like' => $filter,
                ],
            ]);
        }

        return $query;
    }

    /**
     * @param \Cake\ORM\Query $query
     * @param array $options
     * @return \Cake\ORM\Query
     */
    public function findFilterPublishedNodes(Query $query, array $options = [])
    {
        return $query
            ->find('filterNodes', ['filter' => $options['q']])
            ->find('published');
    }

    /**
     * @param \Cake\Validation\Validator $validator Validator
     * @return \Cake\Validation\Validator
     */
    public function validationDefault(Validator $validator)
    {
        $validator->notBlank('title', __d('croogo', 'Please supply a title.'));

        $validator->minLength('slug', 1, __d('croogo', 'Slug cannot be empty.'));

        return $validator;
    }

    public function buildRules(RulesChecker $rules)
    {
        $rules->isUnique(['slug', 'type'], __d('croogo', 'The slug has already been taken.'));
        $rules->add(function (Node $node) {
            if (($node->type === '') || ($node->type === null)) {
                $node->type = 'post';
            }
            if ($node->type === 'post') {
                return true;
            }

            return (bool)TableRegistry::get('Croogo/Taxonomy.Types')
                ->findByAlias($node->type)
                ->count();
        }, 'validType', [
            'errorField' => 'type',
            'message' => 'Invalid type'
        ]);

        return parent::buildRules($rules);
    }

    /**
     * Create/update a Node record
     *
     * @param $node array Node data
     * @param $typeAlias string Node type alias
     * @return mixed see Model::saveAll()
     */
    public function saveNode(Node $node, $typeAlias = self::DEFAULT_TYPE)
    {
        // $node = $this->formatNode($node, $typeAlias);
        $event = Croogo::dispatchEvent('Model.Nodes.beforeSaveNode', $this, compact('node', 'typeAlias'));
        if ($event->isStopped()) {
            return $event->result;
        }

        $result = $this->save($node);
        Croogo::dispatchEvent('Model.Nodes.afterSaveNode', $this, $event->data);

        return $result;
    }

    /**
     * Format data for saving
     *
     * @param array $data Node and related data, eg Taxonomy and Role
     * @param string $typeAlias string Node type alias
     * @return array formatted data
     * @throws InvalidArgumentException
     */
    public function formatNode($data, $typeAlias = self::DEFAULT_TYPE)
    {
        $roles = $type = [];

        if (!array_key_exists($this->alias, $data)) {
            $data = [$this->alias => $data];
        } else {
            $data = $data;
        }

        if (empty($data[$this->alias]['path'])) {
            $data[$this->alias]['path'] = $this->_getNodeRelativePath($data);
        }

        if (!array_key_exists('Role', $data) || empty($data['Role']['Role'])) {
            $roles = '';
        } else {
            $roles = $data['Role']['Role'];
        }

        $data[$this->alias]['visibility_roles'] = $this->encodeData($roles);

        return $data;
    }

    public function findView(Query $query, array $options = [])
    {
        if (isset($options['roleId'])) {
            $query->find('byAccess', [
                'roleId' => $options['roleId'],
            ]);
        }
        $query
            ->find('published', $options);

        return $query;
    }

    /**
     * Finds a slug/type pair
     *
     * @param \Cake\ORM\Query $query Query object
     * @param array $options Options
     * @return \Cake\ORM\Query
     */
    public function findBySlug(Query $query, array $options = [])
    {
        $defaults = ['slug' => null, 'type' => null];
        $options += $defaults;

        return $query->where([
            $this->aliasField('slug') => $options['slug'],
            $this->aliasField('type') => $options['type'],
        ]);
    }

    /**
     * Find a single node by slug
     */
    public function findViewBySlug(Query $query, array $options = [])
    {
        $defaults = ['slug' => null, 'type' => null, 'roleId' => null];
        $options += $defaults;
        $cacheKeys = [
            'node',
            I18n::getLocale(),
            $options['roleId'],
            $options['type'],
            $options['slug'],
        ];
        $cacheKey = implode('_', $cacheKeys);

        $query->find('view', $options)
            ->find('bySlug', $options)
            ->cache($cacheKey, 'nodes_view');

        return $query;
    }

    /**
     * @param \Cake\ORM\Query $query The query object
     * @param array $options Finder options
     * @return \Cake\ORM\Query
     */
    public function findViewById(Query $query, array $options = [])
    {
        $defaults = ['id' => null, 'roleId' => null];
        $options += $defaults;
        $cacheKeys = [
            'node',
            I18n::getLocale(),
            $options['roleId'],
            $options['id'],
        ];
        $cacheKey = implode('_', $cacheKeys);
        $query->find('view', $options)
            ->where([
                $this->aliasField('id') => $options['id'],
            ])
            ->cache($cacheKey, 'nodes_view');

        return $query;
    }

    /**
     * @param \Cake\ORM\Query $query
     * @param array $options
     * @return \Cake\ORM\Query
     * @todo Extract this into a behaviour
     */
    public function findPublished(Query $query, array $options = [])
    {
        $options += ['roleId' => null];

        return $query
            ->andWhere([
                $this->aliasField('status') . ' IN' => $this->status($options['roleId']),
            ])
            ->contain(['Users']);
    }

    public function findPromoted(Query $query)
    {
        return $query->andWhere([
            $this->aliasField('promote') => true,
        ]);
    }

    public function beforeSave(Event $event)
    {
        $node = $event->getData()['entity'];

        if ($node->isDirty('type') || $node->isDirty('slug')) {
            $node->path = Router::url([
                'prefix' => false,
                'plugin' => 'Croogo/Nodes',
                'controller' => 'Nodes',
                'action' => 'view',
                'type' => $node->type,
                'slug' => $node->slug
            ]);
        }

        $event = Croogo::dispatchEvent('Model.Nodes.beforeSaveNode', $this, [
            'node' => $node,
            'typeAlias' => $node->type
        ]);
        if ($event->isStopped()) {
            return $event->result;
        }
    }

    public function afterSave(Event $event)
    {
        $node = $event->getData()['entity'];
        $event = Croogo::dispatchEvent('Model.Nodes.afterSaveNode', $this, [
            'node' => $node,
            'typeAlias' => $node->type
        ]);
        if ($event->isStopped()) {
            return $event->result;
        }
    }
}
