<?php
/*
 * Xibo - Digital Signage - http://www.xibo.org.uk
 * Copyright (C) 2015 Spring Signage Ltd
 *
 * This file (ModuleFactory.php) is part of Xibo.
 *
 * Xibo is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 *
 * Xibo is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with Xibo.  If not, see <http://www.gnu.org/licenses/>.
 */


namespace Xibo\Factory;


use Xibo\Entity\Media;
use Xibo\Entity\Module;
use Xibo\Entity\User;
use Xibo\Entity\Widget;
use Xibo\Exception\NotFoundException;
use Xibo\Service\LogServiceInterface;
use Xibo\Service\ModuleServiceInterface;
use Xibo\Service\SanitizerServiceInterface;
use Xibo\Storage\StorageServiceInterface;

/**
 * Class ModuleFactory
 * @package Xibo\Factory
 */
class ModuleFactory extends BaseFactory
{
    /**
     * @var ModuleServiceInterface
     */
    private $moduleService;

    /**
     * @var WidgetFactory
     */
    private $widgetFactory;

    /**
     * @var RegionFactory
     */
    private $regionFactory;

    /**
     * @var PlaylistFactory
     */
    private $playlistFactory;

    /**
     * @var MediaFactory
     */
    protected $mediaFactory;

    /**
     * @var DataSetFactory
     */
    protected $dataSetFactory;

    /**
     * @var DataSetColumnFactory
     */
    protected $dataSetColumnFactory;

    /**
     * @var TransitionFactory
     */
    protected $transitionFactory;

    /**
     * @var DisplayFactory
     */
    protected $displayFactory;

    /**
     * @var CommandFactory
     */
    protected $commandFactory;

    /** @var  ScheduleFactory */
    protected $scheduleFactory;

    /** @var  PermissionFactory */
    protected $permissionFactory;

    /** @var  UserGroupFactory */
    protected $userGroupFactory;

    /**
     * Construct a factory
     * @param StorageServiceInterface $store
     * @param LogServiceInterface $log
     * @param SanitizerServiceInterface $sanitizerService
     * @param User $user
     * @param UserFactory $userFactory
     * @param ModuleServiceInterface $moduleService
     * @param WidgetFactory $widgetFactory
     * @param RegionFactory $regionFactory
     * @param PlaylistFactory $playlistFactory
     * @param MediaFactory $mediaFactory
     * @param DataSetFactory $dataSetFactory
     * @param DataSetColumnFactory $dataSetColumnFactory
     * @param TransitionFactory $transitionFactory
     * @param DisplayFactory $displayFactory
     * @param CommandFactory $commandFactory
     * @param ScheduleFactory $scheduleFactory
     * @param PermissionFactory $permissionFactory
     * @param UserGroupFactory $userGroupFactory
     */
    public function __construct($store, $log, $sanitizerService, $user, $userFactory, $moduleService, $widgetFactory, $regionFactory, $playlistFactory, $mediaFactory, $dataSetFactory, $dataSetColumnFactory, $transitionFactory, $displayFactory, $commandFactory, $scheduleFactory, $permissionFactory, $userGroupFactory)
    {
        $this->setCommonDependencies($store, $log, $sanitizerService);
        $this->setAclDependencies($user, $userFactory);

        $this->moduleService = $moduleService;
        $this->widgetFactory = $widgetFactory;
        $this->regionFactory = $regionFactory;
        $this->playlistFactory = $playlistFactory;
        $this->mediaFactory = $mediaFactory;
        $this->dataSetFactory = $dataSetFactory;
        $this->dataSetColumnFactory = $dataSetColumnFactory;
        $this->transitionFactory = $transitionFactory;
        $this->displayFactory = $displayFactory;
        $this->commandFactory = $commandFactory;
        $this->scheduleFactory = $scheduleFactory;
        $this->permissionFactory = $permissionFactory;
        $this->userGroupFactory = $userGroupFactory;
    }

    /**
     * @return Module
     */
    public function createEmpty()
    {
        return new Module($this->getStore(), $this->getLog());
    }

    /**
     * Create a Module
     * @param string $type
     * @return \Xibo\Widget\ModuleWidget
     * @throws NotFoundException
     */
    public function create($type)
    {
        $modules = $this->query(['enabled DESC'], array('type' => $type));

        $this->getLog()->debug('Creating %s out of possible %s', $type, json_encode(array_map(function($element) { return $element->class; }, $modules)));

        if (count($modules) <= 0)
            throw new NotFoundException(sprintf(__('Unknown type %s'), $type));

        // Create a module
        return $this->moduleService->get(
            $modules[0],
            $this,
            $this->mediaFactory,
            $this->dataSetFactory,
            $this->dataSetColumnFactory,
            $this->transitionFactory,
            $this->displayFactory,
            $this->commandFactory,
            $this->scheduleFactory,
            $this->permissionFactory,
            $this->userGroupFactory
        );
    }

    /**
     * Create a Module
     * @param string $class
     * @return \Xibo\Widget\ModuleWidget
     * @throws NotFoundException
     */
    public function createByClass($class)
    {
        $modules = $this->query(['enabled DESC'], array('class' => $class));

        $this->getLog()->debug('Creating %s out of possible %s', $class, json_encode(array_map(function($element) { return $element->class; }, $modules)));

        if (count($modules) <= 0)
            throw new NotFoundException(sprintf(__('Unknown class %s'), $class));

        // Create a module
        return $this->moduleService->get(
            $modules[0],
            $this,
            $this->mediaFactory,
            $this->dataSetFactory,
            $this->dataSetColumnFactory,
            $this->transitionFactory,
            $this->displayFactory,
            $this->commandFactory,
            $this->scheduleFactory,
            $this->permissionFactory,
            $this->userGroupFactory
        );
    }

    /**
     * Create a Module
     * @param string $className
     * @return \Xibo\Widget\ModuleWidget
     * @throws NotFoundException
     */
    public function createForInstall($className)
    {
        // Create a module
        return $this->moduleService->getByClass(
            $className,
            $this,
            $this->mediaFactory,
            $this->dataSetFactory,
            $this->dataSetColumnFactory,
            $this->transitionFactory,
            $this->displayFactory,
            $this->commandFactory,
            $this->scheduleFactory,
            $this->permissionFactory,
            $this->userGroupFactory
        );
    }

    /**
     * Create a Module
     * @param string $moduleId
     * @return \Xibo\Widget\ModuleWidget
     * @throws NotFoundException
     */
    public function createById($moduleId)
    {
        return $this->moduleService->get(
            $this->getById($moduleId),
            $this,
            $this->mediaFactory,
            $this->dataSetFactory,
            $this->dataSetColumnFactory,
            $this->transitionFactory,
            $this->displayFactory,
            $this->commandFactory,
            $this->scheduleFactory,
            $this->permissionFactory,
            $this->userGroupFactory
        );
    }

    /**
     * Create a Module with a Media Record
     * @param Media $media
     * @return \Xibo\Widget\ModuleWidget
     * @throws NotFoundException
     */
    public function createWithMedia($media)
    {
        $modules = $this->query(null, array('type' => $media->mediaType));

        if (count($modules) <= 0)
            throw new NotFoundException(sprintf(__('Unknown type %s'), $media->mediaType));

        // Create a widget
        $widget = $this->widgetFactory->createEmpty();
        $widget->assignMedia($media->mediaId);

        // Create a module
        /* @var \Xibo\Widget\ModuleWidget $object */
        $module = $modules[0];
        $object = $this->moduleService->get(
            $module,
            $this,
            $this->mediaFactory,
            $this->dataSetFactory,
            $this->dataSetColumnFactory,
            $this->transitionFactory,
            $this->displayFactory,
            $this->commandFactory,
            $this->scheduleFactory,
            $this->permissionFactory,
            $this->userGroupFactory
        );
        $object->setWidget($widget);

        return $object;
    }

    /**
     * Create a Module for a Widget and optionally a playlist/region
     * @param string $type
     * @param int $widgetId
     * @param int $ownerId
     * @param int $playlistId
     * @param int $regionId
     * @return \Xibo\Widget\ModuleWidget
     * @throws NotFoundException
     */
    public function createForWidget($type, $widgetId = 0, $ownerId = 0, $playlistId = 0, $regionId = 0)
    {
        $module = $this->create($type);

        // Do we have a regionId
        if ($regionId != 0) {
            // Load the region and set
            $region = $this->regionFactory->getById($regionId);
            $module->setRegion($region);
        }

        // Do we have a widgetId
        if ($widgetId == 0) {
            // If we don't have a widget we must have a playlist
            if ($playlistId == 0) {
                throw new \InvalidArgumentException(__('Neither Playlist or Widget provided'));
            }

            $playlist = $this->playlistFactory->getById($playlistId);
            $playlist->setChildObjectDependencies($this->regionFactory);
            $playlist->load(['playlistIncludeRegionAssignments' => false]);

            // Create a new widget to use
            $widget = $this->widgetFactory->create($ownerId, $playlistId, $module->getModuleType(), null);
            $module->setWidget($widget);

            $playlist->assignWidget($widget);
        }
        else {
            // Load the widget
            $module->setWidget($this->widgetFactory->loadByWidgetId($widgetId));
        }

        return $module;
    }

    /**
     * Create a Module using a Widget
     * @param Widget $widget
     * @param Region[optional] $region
     * @return \Xibo\Widget\ModuleWidget
     */
    public function createWithWidget($widget, $region = null)
    {
        $module = $this->create($widget->type);
        $module->setWidget($widget);

        if ($region != null)
            $module->setRegion($region);

        return $module;
    }

    public function get($key = 'type')
    {
        $modules = $this->query();

        if ($key != null && $key != '') {

            $keyed = array();
            foreach ($modules as $module) {
                /* @var Module $module */
                $keyed[$module->type] = $module;
            }

            return $keyed;
        }

        return $modules;
    }

    public function getAssignableModules()
    {
        return $this->query(null, array('assignable' => 1, 'enabled' => 1));
    }

    /**
     * Get module by Id
     * @param int $moduleId
     * @return Module
     * @throws NotFoundException
     */
    public function getById($moduleId)
    {
        $modules = $this->query(null, array('moduleId' => $moduleId));

        if (count($modules) <= 0)
            throw new NotFoundException();

        return $modules[0];
    }

    /**
     * Get module by InstallName
     * @param string $installName
     * @return Module
     * @throws NotFoundException
     */
    public function getByInstallName($installName)
    {
        $modules = $this->query(null, ['installName' => $installName]);

        if (count($modules) <= 0)
            throw new NotFoundException();

        return $modules[0];
    }

    /**
     * Get Enabled
     * @return Module[]
     */
    public function getEnabled()
    {
        return $this->query(null, ['enabled' => 1]);
    }

    /**
     * Get module by extension
     * @param string $extension
     * @return Module
     * @throws NotFoundException
     */
    public function getByExtension($extension)
    {
        $modules = $this->query(['enabled DESC'], array('extension' => $extension));

        if (count($modules) <= 0)
            throw new NotFoundException(sprintf(__('Extension %s does not match any enabled Module'), $extension));

        return $modules[0];
    }

    /**
     * Get Valid Extensions
     * @param array[Optional] $filterBy
     * @return array[string]
     */
    public function getValidExtensions($filterBy = [])
    {
        $modules = $this->query(null, $filterBy);
        $extensions = array();

        foreach($modules as $module) {
            /* @var Module $module */
            if ($module->validExtensions != '') {
                foreach (explode(',', $module->validExtensions) as $extension) {
                    $extensions[] = $extension;
                }
            }
        }

        return $extensions;
    }

    /**
     * Get View Paths
     * @return array[string]
     */
    public function getViewPaths()
    {
        $modules = $this->query();
        $paths = array_map(function ($module) {
            /* @var Module $module */
            return str_replace_first('..', PROJECT_ROOT, $module->viewPath);
        }, $modules);

        $paths = array_unique($paths);

        return $paths;
    }

    public function query($sortOrder = null, $filterBy = array())
    {
        if ($sortOrder == null)
            $sortOrder = array('Module');

        $entries = array();

        try {
            $dbh = $this->getStore()->getConnection();

            $params = array();

            $select = '
                SELECT ModuleID,
                   Module,
                   Name,
                   Enabled,
                   Description,
                   render_as,
                   settings,
                   RegionSpecific,
                   ValidExtensions,
                   ImageUri,
                   PreviewEnabled,
                   assignable,
                   SchemaVersion
                ';

            if (DBVERSION >= 120) {
                $select .= '
                    ,
                    viewPath,
                   `class`
                ';
            }

            if (DBVERSION >= 122) {
                $select .= '
                    ,
                    `defaultDuration`
                ';
            }

            if (DBVERSION >= 125) {
                $select .= '
                    ,
                    IFNULL(`installName`, `module`) AS installName
                ';
            }

            $body = '
                  FROM `module`
                 WHERE 1 = 1
            ';

            if ($this->getSanitizer()->getInt('moduleId', $filterBy) !== null) {
                $params['moduleId'] = $this->getSanitizer()->getInt('moduleId', $filterBy);
                $body .= ' AND ModuleID = :moduleId ';
            }

            if ($this->getSanitizer()->getString('name', $filterBy) != '') {
                $params['name'] = $this->getSanitizer()->getString('name', $filterBy);
                $body .= ' AND name = :name ';
            }

            if ($this->getSanitizer()->getString('installName', $filterBy) != null) {
                $params['installName'] = $this->getSanitizer()->getString('installName', $filterBy);
                $body .= ' AND installName = :installName ';
            }

            if ($this->getSanitizer()->getString('type', $filterBy) != '') {
                $params['type'] = $this->getSanitizer()->getString('type', $filterBy);
                $body .= ' AND module = :type ';
            }

            if ($this->getSanitizer()->getString('class', $filterBy) != '') {
                $params['class'] = $this->getSanitizer()->getString('class', $filterBy);
                $body .= ' AND `class` = :class ';
            }

            if ($this->getSanitizer()->getString('extension', $filterBy) != '') {
                $params['extension'] = '%' . $this->getSanitizer()->getString('extension', $filterBy) . '%';
                $body .= ' AND ValidExtensions LIKE :extension ';
            }

            if ($this->getSanitizer()->getInt('assignable', -1, $filterBy) != -1) {
                $body .= " AND assignable = :assignable ";
                $params['assignable'] = $this->getSanitizer()->getInt('assignable', $filterBy);
            }

            if ($this->getSanitizer()->getInt('enabled', -1, $filterBy) != -1) {
                $body .= " AND enabled = :enabled ";
                $params['enabled'] = $this->getSanitizer()->getInt('enabled', $filterBy);
            }

            if ($this->getSanitizer()->getInt('regionSpecific', -1, $filterBy) != -1) {
                $body .= " AND regionSpecific = :regionSpecific ";
                $params['regionSpecific'] = $this->getSanitizer()->getInt('regionSpecific', $filterBy);
            }

            // Sorting?
            $order = '';
            if (is_array($sortOrder))
                $order .= 'ORDER BY ' . implode(',', $sortOrder);

            $limit = '';
            // Paging
            if ($filterBy !== null && $this->getSanitizer()->getInt('start', $filterBy) !== null && $this->getSanitizer()->getInt('length', $filterBy) !== null) {
                $limit = ' LIMIT ' . intval($this->getSanitizer()->getInt('start', $filterBy), 0) . ', ' . $this->getSanitizer()->getInt('length', 10, $filterBy);
            }

            $sql = $select . $body . $order . $limit;

            //

            $sth = $dbh->prepare($sql);
            $sth->execute($params);

            foreach ($sth->fetchAll(\PDO::FETCH_ASSOC) as $row) {
                $module = $this->createEmpty();
                $module->moduleId = $this->getSanitizer()->int($row['ModuleID']);
                $module->name = $this->getSanitizer()->string($row['Name']);
                $module->description = $this->getSanitizer()->string($row['Description']);
                $module->validExtensions = $this->getSanitizer()->string($row['ValidExtensions']);
                $module->imageUri = $this->getSanitizer()->string($row['ImageUri']);
                $module->renderAs = $this->getSanitizer()->string($row['render_as']);
                $module->enabled = $this->getSanitizer()->int($row['Enabled']);
                $module->regionSpecific = $this->getSanitizer()->int($row['RegionSpecific']);
                $module->previewEnabled = $this->getSanitizer()->int($row['PreviewEnabled']);
                $module->assignable = $this->getSanitizer()->int($row['assignable']);
                $module->schemaVersion = $this->getSanitizer()->int($row['SchemaVersion']);

                // Identification
                $module->type = strtolower($this->getSanitizer()->string($row['Module']));

                if (DBVERSION >= 120) {
                    $module->class = $this->getSanitizer()->string($row['class']);
                    $module->viewPath = $this->getSanitizer()->string($row['viewPath']);
                }

                if (DBVERSION >= 122) {
                    $module->defaultDuration = $this->getSanitizer()->int($row['defaultDuration']);
                }

                if (DBVERSION >= 125) {
                    $module->installName = $this->getSanitizer()->string($row['installName']);
                }

                $settings = $row['settings'];
                $module->settings = ($settings == '') ? array() : json_decode($settings, true);

                $entries[] = $module;
            }

            // Paging
            if ($limit != '' && count($entries) > 0) {
                $results = $this->getStore()->select('SELECT COUNT(*) AS total ' . $body, $params);
                $this->_countLast = intval($results[0]['total']);
            }

            return $entries;
        }
        catch (\Exception $e) {

            $this->getLog()->error($e->getMessage());

            return array();
        }
    }
}