<?php

/*
 * This file is part of Phraseanet
 *
 * (c) 2005-2016 Alchemy
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

use Alchemy\Phrasea\Application;
use Alchemy\Phrasea\Collection\Collection as CollectionVO;
use Alchemy\Phrasea\Collection\CollectionRepository;
use Alchemy\Phrasea\Collection\CollectionRepositoryRegistry;
use Alchemy\Phrasea\Collection\CollectionService;
use Alchemy\Phrasea\Collection\Reference\CollectionReference;
use Alchemy\Phrasea\Collection\Reference\CollectionReferenceRepository;
use Alchemy\Phrasea\Core\Thumbnail\ThumbnailedElement;
use Alchemy\Phrasea\Core\Thumbnail\ThumbnailManager;
use Alchemy\Phrasea\Model\Entities\User;
use Symfony\Component\HttpFoundation\File\File;

use Alchemy\Phrasea\Core\Event\Collection\CollectionEvent;
use Alchemy\Phrasea\Core\Event\Collection\CollectionEvents;
use Alchemy\Phrasea\Core\Event\Collection\CreatedEvent;
use Alchemy\Phrasea\Core\Event\Collection\NameChangedEvent;
use Alchemy\Phrasea\Core\Event\Collection\EmptiedEvent;
use Alchemy\Phrasea\Core\Event\Collection\EnabledEvent;
use Alchemy\Phrasea\Core\Event\Collection\DisabledEvent;
use Alchemy\Phrasea\Core\Event\Collection\MountedEvent;
use Alchemy\Phrasea\Core\Event\Collection\UnmountedEvent;
use Alchemy\Phrasea\Core\Event\Collection\SettingsChangedEvent;
use Alchemy\Phrasea\Core\Event\Collection\LabelChangedEvent;

class collection implements ThumbnailedElement, cache_cacheableInterface
{

    const PIC_LOGO = 'minilogos';
    const PIC_WM = 'wm';
    const PIC_STAMP = 'stamp';
    const PIC_PRESENTATION = 'presentation';

    private static $_logos = [];
    private static $_stamps = [];
    private static $_watermarks = [];
    private static $_presentations = [];

    /**
     * @param Application $app
     * @param $databoxId
     * @return CollectionRepository
     */
    private static function getRepository(Application $app, $databoxId)
    {
        /** @var CollectionRepositoryRegistry $registry */
        $registry = $app['repo.collections-registry'];

        return $registry->getRepositoryByDatabox($databoxId);
    }

    public static function create(Application $app, databox $databox, appbox $appbox, $name, User $user = null)
    {
        $databoxId = $databox->get_sbas_id();

        $repository = self::getRepository($app, $databoxId);
        $collection = new CollectionVO($databoxId, 0, $name);

        $dom = new \DOMDocument();
        try {
            if(!@$dom->load($app['root.path'] . '/lib/conf.d/collection_settings.xml')) {
                $dom = null;
            }
        }
        catch (Exception $e) {
            $dom = null;
        }
        if($dom) {
            $collection->setPreferences($dom->saveXML());
        }

        $repository->save($collection);

        $repository = $app['repo.collection-references'];
        $collectionReference = new CollectionReference(0, $databoxId, $collection->getCollectionId(), 0, true, '');

        $repository->save($collectionReference);

        $app['repo.collections-registry']->purgeRegistry();

        $collection = new self($app, $collection, $collectionReference);

        if (null !== $user) {
            $collection->collectionService->grantAdminRights($collectionReference, $user);
        }

        $app['dispatcher']->dispatch(
            CollectionEvents::CREATED,
            new CreatedEvent(
                $collection
            )
        );

        return $collection;
    }

    public static function mount_collection(Application $app, databox $databox, $coll_id, User $user)
    {
        $reference = new CollectionReference(0, $databox->get_sbas_id(), $coll_id, 0, true, '');

        $app['repo.collection-references']->save($reference);
        $app['repo.collections-registry']->purgeRegistry();

        $collection = self::getByBaseId($app, $reference->getBaseId());
        $collection->collectionService->grantAdminRights($collection->reference, $user);

        $app['dispatcher']->dispatch(
            CollectionEvents::MOUNTED,
            new MountedEvent(
                $collection
            )
        );

        return $reference->getBaseId();
    }

    public static function getLogo($base_id, Application $app, $printname = false)
    {
        $base_id_key = $base_id . '_' . ($printname ? '1' : '0');

        if (!isset(self::$_logos[$base_id_key])) {

            if (is_file($app['root.path'] . '/config/minilogos/' . $base_id)) {
                $name = phrasea::bas_labels($base_id, $app);
                self::$_logos[$base_id_key] = '<img title="' . $name
                    . '" src="/custom/minilogos/' . $base_id . '" />';
            } elseif ($printname) {
                self::$_logos[$base_id_key] = phrasea::bas_labels($base_id, $app);
            }
        }

        return isset(self::$_logos[$base_id_key]) ? self::$_logos[$base_id_key] : '';
    }

    public static function getWatermark($base_id)
    {
        if (!isset(self::$_watermarks['base_id'])) {

            if (is_file(__DIR__ . '/../../config/wm/' . $base_id)) {
                self::$_watermarks['base_id'] = '<img src="/custom/wm/' . $base_id . '" />';
            }
        }

        return isset(self::$_watermarks['base_id']) ? self::$_watermarks['base_id'] : '';
    }

    public static function getPresentation($base_id)
    {
        if (!isset(self::$_presentations['base_id'])) {

            if (is_file(__DIR__ . '/../../config/presentation/' . $base_id)) {
                self::$_presentations['base_id'] = '<img src="/custom/presentation/' . $base_id . '" />';
            }
        }

        return isset(self::$_presentations['base_id']) ? self::$_presentations['base_id'] : '';
    }

    public static function getStamp($base_id)
    {
        if (!isset(self::$_stamps['base_id'])) {

            if (is_file(__DIR__ . '/../../config/stamp/' . $base_id)) {
                self::$_stamps['base_id'] = '<img src="/custom/stamp/' . $base_id . '" />';
            }
        }

        return isset(self::$_stamps['base_id']) ? self::$_stamps['base_id'] : '';
    }

    public static function purge()
    {
        // BC only
    }

    /**
     * @param  Application $app
     * @param  int $base_id
     * @return collection
     */
    public static function getByBaseId(Application $app, $base_id)
    {
        /** @var CollectionReferenceRepository $referenceRepository */
        $referenceRepository = $app['repo.collection-references'];
        $reference = $referenceRepository->find($base_id);

        if (!$reference) {
            throw new Exception_Databox_CollectionNotFound(sprintf(
                "Collection with base_id %s could not be found",
                $base_id
            ));
        }

        return self::getAvailableCollection($app, $reference->getDataboxId(), $reference->getCollectionId());
    }

    /**
     * @param Application $app
     * @param databox|int $databox
     * @param int $collectionId
     * @return collection
     */
    public static function getByCollectionId(Application $app, $databox, $collectionId)
    {
        assert(is_int($collectionId));
        $databoxId = $databox instanceof databox ? $databox->get_sbas_id() : (int)$databox;

        return self::getAvailableCollection($app, $databoxId, $collectionId);
    }

    /**
     * @param Application $app
     * @return \Alchemy\Phrasea\Core\Configuration\AccessRestriction
     */
    private static function getAccessRestriction(Application $app)
    {
        return $app['conf.restrictions'];
    }

    private static function assertCollectionIsAvailable(Application $app, collection $collection)
    {
        if (!self::getAccessRestriction($app)->isCollectionAvailable($collection)) {
            throw new Exception_Databox_CollectionNotFound(sprintf(
                'Collection `%d` is not available here.',
                $collection->get_base_id()
            ));
        }
    }

    /**
     * @param Application $app
     * @param int $databoxId
     * @param int $collectionId
     * @return collection
     */
    private static function getByDataboxIdAndCollectionId(Application $app, $databoxId, $collectionId)
    {
        $repository = self::getRepository($app, $databoxId);
        $collection = $repository->find($collectionId);

        if (!$collection) {
            throw new Exception_Databox_CollectionNotFound(sprintf(
                "Collection '%d' on databox '%d' could not be found",
                $collectionId,
                $databoxId
            ));
        }

        return $collection;
    }

    /**
     * @param Application $app
     * @param int $databoxId
     * @param int $collectionId
     * @return collection
     */
    private static function getAvailableCollection(Application $app, $databoxId, $collectionId)
    {
        $collection = self::getByDataboxIdAndCollectionId($app, $databoxId, $collectionId);
        self::assertCollectionIsAvailable($app, $collection);

        return $collection;
    }

    /**
     * @var Application
     */
    protected $app;

    /**
     * @var CollectionService
     */
    protected $collectionService;

    /**
     * @var databox
     */
    protected $databox;

    /**
     * @var CollectionVO
     */
    protected $collectionVO;

    /**
     * @var CollectionRepositoryRegistry
     */
    protected $collectionRepositoryRegistry;

    /**
     * @var CollectionReference
     */
    protected $reference;


    /**
     * @param Application $app
     * @param CollectionVO $collection
     * @param CollectionReference $reference
     * @internal param $baseId
     * @internal param array $row
     */
    public function __construct(Application $app, CollectionVO $collection, CollectionReference $reference)
    {
        $this->collectionVO = $collection;
        $this->reference = $reference;

        $this->fetchInternalServices($app);
    }

    /**
     * @param $eventName
     * @param CollectionEvent $event
     */
    private function dispatch($eventName, CollectionEvent $event)
    {
        $this->app['dispatcher']->dispatch($eventName, $event);
    }

    /**
     * @return CollectionRepository
     */
    private function getCollectionRepository()
    {
        return self::getRepository($this->app, $this->reference->getDataboxId());
    }

    /**
     * @return CollectionReferenceRepository
     */
    private function getReferenceRepository()
    {
        return $this->app['repo.collection-references'];
    }

    public function hydrate(Application $app)
    {
        $this->fetchInternalServices($app);
    }

    public function __sleep()
    {
        return [
            'collectionVO',
            'reference'
        ];
    }

    public function __debugInfo()
    {
        return [
            'reference' => $this->reference,
            'databox' => $this->databox,
            'collectionVO' => $this->collectionVO
        ];
    }

    /**
     * @return CollectionVO
     */
    public function getCollection()
    {
        return $this->collectionVO;
    }

    /**
     * @return CollectionReference
     */
    public function getReference()
    {
        return $this->reference;
    }

    /**
     * @return bool
     */
    public function is_active()
    {
        return $this->reference->isActive();
    }

    /**
     *
     * @return databox
     */
    public function get_databox()
    {
        return $this->databox;
    }

    /**
     * @return \Doctrine\DBAL\Connection
     */
    public function get_connection()
    {
        return $this->databox->get_connection();
    }

    /**
     * @param $publi
     * @return $this
     */
    public function set_public_presentation($publi)
    {
        $this->collectionVO->setPublicWatermark($publi);
        $this->getCollectionRepository()->save($this->collectionVO);
        $this->app['repo.collections-registry']->purgeRegistry();

        return $this;
    }

    /**
     * @param $name
     * @return $this
     * @throws Exception_InvalidArgument
     */
    public function set_name($name)
    {
        $old_name = $this->get_name();

        try {
            $this->collectionVO->setName($name);
        } catch (\InvalidArgumentException $e) {
            throw new Exception_InvalidArgument();
        }

        $this->getCollectionRepository()->save($this->collectionVO);
        $this->app['repo.collections-registry']->purgeRegistry();

        $this->dispatch(CollectionEvents::NAME_CHANGED,
            new NameChangedEvent(
                $this,
                array("name_before"=>$old_name)
            )
        );

        return $this;
    }

    /**
     * @param $code
     * @param $label
     * @return $this
     */
    public function set_label($code, $label)
    {
        $old_label = $this->collectionVO->getLabel($code);

        $this->collectionVO->setLabel($code, $label);

        $this->getCollectionRepository()->save($this->collectionVO);
        $this->app['repo.collections-registry']->purgeRegistry();

        $this->dispatch(CollectionEvents::LABEL_CHANGED, new LabelChangedEvent($this, array(
            "lng"=>$code,
            "label_before"=>$old_label,
        )));

        return $this;
    }

    /**
     * @param $code
     * @param bool $substitute
     * @return string
     */
    public function get_label($code, $substitute = true)
    {
        return $this->collectionVO->getLabel($code, $substitute);
    }

    /**
     * @return int
     */
    public function get_ord()
    {
        return $this->reference->getDisplayIndex();
    }

    /**
     * @param $ord
     * @return $this
     */
    public function set_ord($ord)
    {
        $this->reference->setDisplayIndex($ord);

        $this->getReferenceRepository()->save($this->reference);
        $this->app['repo.collections-registry']->purgeRegistry();

        return $this;
    }

    /**
     * @return int[]|null|string
     */
    public function get_binary_minilogos()
    {
        return $this->collectionVO->getLogo();
    }

    /**
     * @return int
     */
    public function get_base_id()
    {
        return (int) $this->reference->getBaseId();
    }

    /**
     * @return int
     */
    public function get_sbas_id()
    {
        return (int) $this->reference->getDataboxId();
    }

    /**
     * @return int
     */
    public function get_coll_id()
    {
        return (int) $this->reference->getCollectionId();
    }

    /**
     * @return string
     */
    public function get_prefs()
    {
        return $this->collectionVO->getPreferences();
    }

    /**
     * @param DOMDocument $dom
     * @return string
     */
    public function set_prefs(DOMDocument $dom)
    {
        $oldPreferences = $this->collectionVO->getPreferences();

        $this->collectionVO->setPreferences($dom->saveXML());
        $this->getCollectionRepository()->save($this->collectionVO);

        $this->app['repo.collections-registry']->purgeRegistry();

        $this->dispatch(
            CollectionEvents::SETTINGS_CHANGED,
            new SettingsChangedEvent(
                $this,
                array(
                    'settings_before' => $oldPreferences
                )
            )
        );

        return $this->collectionVO->getPreferences();
    }

    /**
     * @return string
     */
    public function get_name()
    {
        return $this->collectionVO->getName();
    }

    /**
     * @return string
     */
    public function get_pub_wm()
    {
        return $this->collectionVO->getPublicWatermark();
    }

    /**
     * @return bool
     */
    public function is_available()
    {
        return true;
    }

    /**
     * @return int
     */
    public function getRootIdentifier()
    {
        return $this->reference->getBaseId();
    }

    /**
     * @return $this
     */
    public function disable()
    {
        $this->reference->disable();

        $this->getReferenceRepository()->save($this->reference);
        $this->collectionRepositoryRegistry->purgeRegistry();

        // clear cached collection
        $this->getCollectionRepository()->clearCache();

        // clear the trivial cache of databox->get_collections()
        $this->get_databox()->clearCache(databox::CACHE_COLLECTIONS);

        cache_databox::update($this->app, $this->databox->get_sbas_id(), 'structure');
        
	    $this->dispatch(CollectionEvents::DISABLED, new DisabledEvent($this));
	
        return $this;
    }

    /**
     * @return $this
     */
    public function enable()
    {
        $this->reference->enable();

        $this->getReferenceRepository()->save($this->reference);
        $this->collectionRepositoryRegistry->purgeRegistry();

        // clear cached collection
        $this->getCollectionRepository()->clearCache();

        // clear the trivial cache of databox->get_collections()
        $this->get_databox()->clearCache(databox::CACHE_COLLECTIONS);

        cache_databox::update($this->app, $this->databox->get_sbas_id(), 'structure');
        
	    $this->dispatch(CollectionEvents::ENABLED, new EnabledEvent($this));
	
        return $this;
    }

    /**
     * @param int $pass_quantity
     * @return $this
     * @throws \Doctrine\DBAL\DBALException
     */
    public function empty_collection($pass_quantity = 100)
    {
        $this->collectionService->emptyCollection($this->databox, $this->collectionVO, $pass_quantity);
        $this->dispatch(CollectionEvents::EMPTIED, new EmptiedEvent($this));
        return $this;
    }

    public function getCollectionRecordIdList()
    {
        return $this->collectionService->getCollectionRecordIdList($this->collectionVO);
    }

    /**
     * @param string $thumbnailType
     * @param File $file
     */
    public function updateThumbnail($thumbnailType, File $file = null)
    {
        switch ($thumbnailType) {
            case ThumbnailManager::TYPE_WM;
                $this->reset_watermark();
                break;
            case ThumbnailManager::TYPE_LOGO:
                $this->update_logo($file);
                break;
            case ThumbnailManager::TYPE_PRESENTATION:
                break;
            case ThumbnailManager::TYPE_STAMP:
                $this->reset_stamp();
                break;
            default:
                throw new \InvalidArgumentException('Unsupported thumbnail type.');
        }
    }

    /**
     * @return int|null
     * @throws \Doctrine\DBAL\DBALException
     */
    public function get_record_amount()
    {
        return $this->collectionService->getRecordCount($this->collectionVO);
    }

    /**
     * @return array
     * @throws \Doctrine\DBAL\DBALException
     */
    public function get_record_details()
    {
        return $this->collectionService->getRecordDetails($this->collectionVO);
    }

    /**
     * @param SplFileInfo $pathfile
     * @return $this
     */
    public function update_logo(\SplFileInfo $pathfile = null)
    {
        $fileContents = null;

        if (!is_null($pathfile)) {
            $fileContents = file_get_contents($pathfile->getPathname());
        }

        $this->collectionVO->setLogo($fileContents);

        $this->getCollectionRepository()->save($this->collectionVO);
        $this->collectionRepositoryRegistry->purgeRegistry();

        return $this;
    }

    /**
     * @return $this
     * @throws \Doctrine\DBAL\DBALException
     */
    public function reset_watermark()
    {
        $this->collectionService->resetWatermark($this->collectionVO);

        $this->getCollectionRepository()->save($this->collectionVO);
        $this->collectionRepositoryRegistry->purgeRegistry();

        return $this;
    }

    /**
     * @param null $record_id
     * @return $this
     * @throws \Doctrine\DBAL\DBALException
     */
    public function reset_stamp($record_id = null)
    {
        $this->collectionService->resetStamp($this->collectionVO, $record_id);

        $this->getCollectionRepository()->save($this->collectionVO);
        $this->collectionRepositoryRegistry->purgeRegistry();

        return $this;
    }

    /**
     * @throws \Doctrine\DBAL\DBALException
     */
    public function delete()
    {
        $this->collectionService->delete($this->databox, $this->collectionVO, $this->reference);

        $this->getCollectionRepository()->delete($this->collectionVO);

        $this->app['manipulator.registration']->deleteRegistrationsOnCollection($this);
        $this->collectionRepositoryRegistry->purgeRegistry();
    }

    /**
     * @return $this
     * @throws \Doctrine\DBAL\DBALException
     */
    public function unmount()
    {
        $old_coll_id = $this->get_coll_id();
        $old_name = $this->get_name();

        $this->collectionService->unmountCollection($this->reference);

        $this->getReferenceRepository()->delete($this->reference);

        $this->app['manipulator.registration']->deleteRegistrationsOnCollection($this);
        $this->collectionRepositoryRegistry->purgeRegistry();

        $this->dispatch(
            CollectionEvents::UNMOUNTED,
            new UnmountedEvent(
                null,                   // the coll is not available anymore
                array(
                    'coll_id' => $old_coll_id,
                    'coll_name' => $old_name
                )
            )
        );

        return $this;
    }

    /**
     * Tells whether registration is activated for provided collection or not.
     *
     * @return boolean
     */
    public function isRegistrationEnabled()
    {
        if (false === $xml = simplexml_load_string($this->get_prefs())) {
            return false;
        }

        $element = $xml->xpath('/baseprefs/caninscript');

        if (count($element) === 0) {
            return $this->databox->isRegistrationEnabled();
        }

        foreach ($element as $caninscript) {
            if (false !== (bool)(string)$caninscript) {
                return true;
            }
        }

        return false;
    }

    /**
     * matches a email against the auto-register whitelist
     *
     * @param string $email
     * @return null|string  the user-model to apply if the email matches
     */
    public function getAutoregisterModel($email)
    {
        // try to match against the collection whitelist
        if($this->isRegistrationEnabled()) {
            if (($xml = @simplexml_load_string($this->get_prefs())) !== false) {
                foreach ($xml->xpath('/baseprefs/registration/auto_register/email_whitelist/email') as $element) {
                    if (preg_match($element['pattern'], $email) === 1) {
                        return (string)$element['user_model'];
                    }
                }
            }
        }

        // no match ? try against the databox whitelist
        return $this->get_databox()->getAutoregisterModel($email);
    }

    public function get_cache_key($option = null)
    {
        return 'collection_' . $this->collectionVO->getCollectionId() . ($option ? '_' . $option : '');
    }

    public function get_data_from_cache($option = null)
    {
        return $this->databox->get_data_from_cache($this->get_cache_key($option));
    }

    public function set_data_to_cache($value, $option = null, $duration = 0)
    {
        return $this->databox->set_data_to_cache($value, $this->get_cache_key($option), $duration);
    }

    public function delete_data_from_cache($option = null)
    {
        $this->databox->delete_data_from_cache($this->get_cache_key($option));
    }

    /**
     * @param Application $app
     */
    private function fetchInternalServices(Application $app)
    {
        $this->app = $app;
        $this->databox = $app->getApplicationBox()->get_databox($this->reference->getDataboxId());
        $this->collectionService = $app->getApplicationBox()->getCollectionService();
        $this->collectionRepositoryRegistry = $app['repo.collections-registry'];
    }
}
