<?php
/**
 * Pi Engine (http://piengine.org)
 *
 * @link            http://code.piengine.org for the Pi Engine source repository
 * @copyright       Copyright (c) Pi Engine http://piengine.org
 * @license         http://piengine.org/license.txt BSD 3-Clause License
 */

namespace Pi\Application\Installer\Resource;

use Pi;
use Pi\Db\RowGateway\RowGateway;

/**
 * Block maintenance with configuration specs
 *
 * ```
 *  array(
 *      // Block with renderer and structured options
 *      'blockA' => array(
 *          // Required, translated
 *          'title'         => __('Block Title'),
 *          // Optional
 *          //'link'          => '/link/to/a/URL',
 *          // Optional, specified stylesheet class for display
 *          //'class'         => 'css-class',
 *          // Optional, translated
 *          'description'   => __('Describing the block'),
 *           // Required
 *          'render'        => array('class', 'method'),
 *          // in module/template/block/, no suffix
 *          'template'      => 'template',
 *          // Cache granularity, optional: role, locale, user, guest
 *          'cache_level'   => 'role',
 *          'config'        => array(
 *              'a' => array(
 *                  'title'         => 'Config A',
 *                  'description'   => 'Config A hint',
 *                  'edit'          => 'select',
 *                  'value'         => 'option_a',
 *                  'edit'          => array(
 *                      'attributes'    => array(
 *                          'type'      => 'select'
 *                          'options'   => array(
 *                              1   => 'Option 1',
 *                              2   => 'Option 2',
 *                              3   => 'Option 3',
 *                          ),
 *                      ),
 *                  ),
 *                  'filter'        => 'num_int',
 *              ),
 *              'b'  => array(
 *                  'title'         => 'Config B',
 *                  'description'   => 'Config B hint',
 *                  'edit'          => 'SpecifiedEditElement',
 *                  'filter'        => 'string',
 *                  'value'         => 'good',
 *              ),
 *              'c'  => array(
 *                  'title'         => 'Config C',
 *                  'description'   => 'Config C hint',
 *                  'edit'          => 'input', // optional
 *                  'filter'        => 'string',
 *                  'value'         => __('sample text'),
 *              ),
 *          ),
 *      ),
 *      ...
 *  );
 * ```
 *
 * @author Taiwen Jiang <taiwenjiang@tsinghua.org.cn>
 */
class Block extends AbstractResource
{
    /** @var string */
    //protected $moduleSeperator = '-';

    /**
     * Canonize block data for creation
     *
     * @param array $block
     *
     * @return array
     */
    protected function canonizeAdd($block)
    {
        $module      = $this->event->getParam('module');
        $directory   = $this->event->getParam('directory');
        $classPrefix = sprintf('Module\\%s\Block', ucfirst($directory));
        if (is_array($block['render'])) {
            $block['render'] = implode('::', $block['render']);
        }
        $block['render'] = $classPrefix . '\\' . ucfirst($block['render']);

        $data = [
            'name'        => $block['name'],
            'title'       => $block['title'],
            'description' => isset($block['description']) ? $block['description'] : '',
            'module'      => $module,
            'render'      => $block['render'],
            'template'    => isset($block['template']) ? $block['template'] : '',
            'config'      => isset($block['config']) ? $block['config'] : [],
            'cache_level' => isset($block['cache_level']) ? $block['cache_level'] : '',
        ];

        return $data;
    }

    /**
     * Canonize block data for update
     *
     * @param array $block
     *
     * @return array
     */
    protected function canonizeUpdate($block)
    {
        $module      = $this->event->getParam('module');
        $directory   = $this->event->getParam('directory');
        $classPrefix = sprintf('Module\\%s\Block', ucfirst($directory));
        if (is_array($block['render'])) {
            $block['render'] = implode('::', $block['render']);
        }
        $block['render'] = $classPrefix . '\\' . ucfirst($block['render']);

        $data = [
            'title'       => $block['title'],
            'description' => isset($block['description']) ? $block['description'] : '',
            'render'      => $block['render'],
            'template'    => isset($block['template']) ? $block['template'] : '',
            'config'      => isset($block['config']) ? $block['config'] : [],
            'cache_level' => isset($block['cache_level']) ? $block['cache_level'] : '',
        ];

        return $data;
    }

    /**
     * {@inheritDoc}
     */
    public function installAction()
    {
        if (empty($this->config)) {
            return;
        }
        $module = $this->event->getParam('module');
        $blocks = $this->config;
        foreach ($blocks as $key => $block) {
            // break the loop if missing block config
            if (empty($block['render'])) {
                continue;
            }
            $block['name']   = !empty($block['name']) ? $block['name'] : $key;
            $block['module'] = $module;
            $data            = $this->canonizeAdd($block);
            $message         = [];
            $status          = $this->addBlock($data, $message);
            if (!$status) {
                $message[] = sprintf('Block "%s" is not created.', $key);
                return [
                    'status'  => false,
                    'message' => $message,
                ];
            }
        }

        Pi::registry('block')->clear($module);

        return true;
    }

    /**
     * {@inheritDoc}
     */
    public function updateAction()
    {
        $module = $this->event->getParam('module');
        Pi::registry('block')->clear($module);

        if ($this->skipUpgrade()) {
            return;
        }

        $blocks = $this->config ?: [];

        $model = Pi::model('block_root');
        foreach ($blocks as $key => $block) {
            // break the loop if missing block config
            if (empty($block['render'])) {
                continue;
            }
            $block['name']   = !empty($block['name']) ? $block['name'] : $key;
            $block['module'] = $module;
            $rowset          = $model->select(
                [
                    'name'   => $block['name'],
                    'module' => $module,
                ]
            );
            // Add new block
            if (!$rowset->count()) {
                $data    = $this->canonizeAdd($block);
                $message = [];
                $status  = $this->addBlock($data, $message);
                if (!$status) {
                    $message[] = sprintf('Block "%s" is not created.', $key);
                    return [
                        'status'  => false,
                        'message' => $message,
                    ];
                }
                // Update existent block
            } else {
                $row     = $rowset->current();
                $data    = $this->canonizeUpdate($block);
                $message = [];
                $status  = $this->updateBlock($row, $data, $message);
                if (!$status) {
                    $message[] = sprintf('Block "%s" is not updated.', $key);
                    return [
                        'status'  => false,
                        'message' => $message,
                    ];
                }
            }
        }

        // Remove deprecated blocks
        $rowset = $model->select(['module' => $module]);
        foreach ($rowset as $row) {
            if (!isset($blocks[$row->name])) {
                $message = [];
                $status  = $this->deleteBlock($row, $message);
                if (!$status) {
                    $message[] = sprintf(
                        'Deprecated block "%s" is not updated.',
                        $row->key
                    );
                    return [
                        'status'  => false,
                        'message' => $message,
                    ];
                }
            }
        }

        return true;
    }

    /**
     * {@inheritDoc}
     */
    public function uninstallAction()
    {
        $module = $this->event->getParam('module');

        Pi::model('block')->delete(['module' => $module]);
        Pi::model('block_root')->delete(['module' => $module]);
        Pi::registry('block')->clear($module);

        return true;
    }

    /**
     * {@inheritDoc}
     */
    public function activateAction()
    {
        $module = $this->event->getParam('module');
        Pi::model('block')->update(
            ['active' => 1],
            ['module' => $module]
        );

        Pi::registry('block')->clear($module);

        return true;
    }

    /**
     * {@inheritDoc}
     */
    public function deactivateAction()
    {
        $module = $this->event->getParam('module');
        Pi::model('block')->update(
            ['active' => 0],
            ['module' => $module]
        );

        Pi::registry('block')->clear($module);

        return true;
    }

    /**
     * Adds a block and its relevant options, ACL rules
     *
     * @param array  $block
     * @param string $message
     *
     * @return bool
     */
    protected function addBlock($block, &$message)
    {
        $result = Pi::api('block', 'system')->add($block);

        return $result['status'];
    }

    /**
     * Updates a block and its relevant options
     *
     * @param RowGateway $rootRow
     * @param array      $block
     * @param array      $message
     *
     * @return bool
     */
    protected function updateBlock(RowGateway $rootRow, $block, &$message)
    {
        $result = Pi::api('block', 'system')->update($rootRow, $block);

        return $result['status'];
    }

    /**
     * Deletes a block root and its relevant views, ACL rules
     *
     * @param RowGateway $rootRow
     * @param array      $message
     *
     * @return bool
     */
    protected function deleteBlock(RowGateway $rootRow, &$message)
    {
        $result = Pi::api('block', 'system')->delete($rootRow, true);

        return $result['status'];
    }
}
