<?php
/**
 * @package     DOCman
 * @copyright   Copyright (C) 2011 - 2014 Timble CVBA. (http://www.timble.net)
 * @license     GNU GPLv3 <http://www.gnu.org/licenses/gpl.html>
 * @link        http://www.joomlatools.com
 */

/**
 * Used by the document controller to make an image thumbnail out of the file if possible
 */
class ComDocmanControllerBehaviorThumbnailable extends KControllerBehaviorAbstract
{
    /**
     * If set to true in before.edit, after.edit will regenerate the thumbnail
     */
    protected $_regenerate;

    protected $_thumbnail_size;

    protected static $_extensions = array('jpg', 'jpeg', 'gif', 'png', 'bmp');

    /**
     * @var mixed The thumbnail container.
     */
    protected $_container;

    /**
     * @var string The folder (relative to container's root) where generated thumbnails will be stored.
     */
    protected $_folder;

    public function __construct(KObjectConfig $config)
    {
        parent::__construct($config);

        $this->setThumbnailSize(KObjectConfig::unbox($config->thumbnail_size));

        $this->_container = $config->container;
        $this->_folder    = $config->folder;
    }

    protected function _initialize(KObjectConfig $config)
    {
        $config->append(array(
            'container'      => 'docman-images',
            'folder'         => 'generated',
            'thumbnail_size' => array('x' => 512, 'y' => 512)
        ));

        parent::_initialize($config);
    }

    public function getDefaultFilename($entity, $path = true)
    {
        $filename = md5($entity->id) . '.png';

        if ($path) {
            $filename = $this->_folder . '/' . $filename;
        }

        return $filename;
    }

    public function getThumbnailSize()
    {
        return $this->_thumbnail_size;
    }

    /**
     * @param array $size An array with x and y properties
     */
    public function setThumbnailSize(array $size)
    {
        $this->_thumbnail_size = $size;
        return $this;
    }

    protected function _getContainer()
    {
        if (!$this->_container instanceof KModelEntityInterface)
        {
            $container = $this->getObject('com:files.model.containers')
                ->slug($this->_container)
                ->fetch();

            $this->_setContainer($container);
        }

        return $this->_container;
    }

    protected function _setContainer(KModelEntityInterface $container)
    {
        $this->_container = $container;
        $folder           = $container->fullpath . '/' . $this->_folder;

        if (!file_exists($folder))
        {
            jimport('joomla.filesystem.folder');
            JFolder::create($folder);
        }

        $container->folder = $folder . '/';

        return $this;
    }

    protected function _createThumbnail($file)
    {
        @ini_set('memory_limit', '256M');

        $result = false;

        try
        {
            if ($this->_canGenerate($file))
            {
                $imagine = new \Imagine\Gd\Imagine();
                $image   = $imagine->open($file);

                $size = $this->getThumbnailSize();

                $image_size = $image->getSize();
                $larger     = max($image_size->getWidth(), $image_size->getHeight());
                $scale      = max($size['x'], $size['y']);
                $new_size   = $image_size->scale(1/($larger/$scale));

                $image = $image->thumbnail($new_size, \Imagine\Image\ImageInterface::THUMBNAIL_OUTBOUND);

                $file = new SplTempFileObject();
                $file->fwrite((string) $image);

                $result = $file;
            }
        }
        catch (Exception $e) {}

        return $result;
    }



    /**
     * Checks if a thumbnail for the current source and provided size can be generated given the
     * amount of memory that's available.
     *
     * @param  string $file Source file
     * @return bool True if the thumbnail can be "safely" processed, false otherwise.
     */
    protected function _canGenerate($file)
    {
        $result = false;

        // Multiplier to take into account memory consumed by the Image Processing Library.
        $tweak_factor  = 6;

        $source = @getimagesize($file);

        $channels      = isset($source['channels']) ? $source['channels'] : 4;
        $bits          = isset($source['bits']) ? $source['bits'] : 8;
        $source_memory = ceil($source[0] * $source[1] * $bits * $channels / 8 * $tweak_factor);

        $thumb = $this->getThumbnailSize();

        // We assume the same amount of bits and channels as source.
        $thumb_memory = ceil($thumb['x'] * $thumb['y'] * $bits * $channels / 8 * $tweak_factor);

        //If memory is limited
        $limit = ini_get('memory_limit');
        if ($limit != '-1')
        {
            $limit = self::convertToBytes($limit);
            $available_memory = $limit - memory_get_usage();

            // Leave 16 megs for the rest of the request
            $available_memory -= 16777216;

            if ($source_memory + $thumb_memory < $available_memory) {
                $result = true;
            }
        }
        else $result = true;

        return $result;
    }

    public static function convertToBytes($value)
    {
        $keys = array('k', 'm', 'g');
        $last_char = strtolower(substr($value, -1));
        $value = (int) $value;

        if (in_array($last_char, $keys)) {
            $value *= pow(1024, array_search($last_char, $keys)+1);
        }

        return $value;
    }

    protected function _saveThumbnail(KModelEntityInterface $entity)
    {
        $result = false;
        $config = $this->getObject('com://admin/docman.model.configs')->fetch();

        if ($config->thumbnails)
        {
            if ($entity->storage_type == 'file' && in_array($entity->extension, self::$_extensions))
            {
                $thumbnail = $this->_createThumbnail($entity->storage->fullpath);

                if ($thumbnail)
                {
                    $container = $this->_getContainer();

                    try
                    {
                        $data = array(
                            'file'      => $thumbnail,
                            'name'      => $this->getDefaultFilename($entity, false),
                            'folder'    => $this->_folder,
                            'overwrite' => true
                        );

                        $image = $this->getObject('com:files.controller.file', array(
                            'behaviors' => array(
                                'permissible' => array(
                                    'permission' => 'com://admin/docman.controller.permission.file'
                                )
                            )))->container($container->slug)->add($data);

                        $entity->image = $image->path;
                        $result = (bool) $entity->save();

                    }
                    catch (KControllerException $e) {}
                }
            }
        }

        return $result;
    }

    /**
     * Create a thumbnail for new files
     *
     * @param KControllerContextInterface $context
     */
    protected function _afterAdd(KControllerContextInterface $context)
    {
        if ($context->response->getStatusCode() !== 201 || !empty($context->result->image) || !$context->result->automatic_thumbnail) {
            return;
        }

        $this->_saveThumbnail($context->result);
    }

    /**
     * Figure out if the file has changed and if so regenerate the thumbnail on after save
     *
     * @param KControllerContextInterface $context
     */
    protected function _beforeEdit(KControllerContextInterface $context)
    {
        $item = $this->getModel()->fetch();
        $data = $context->request->data;

        $filename = $this->getDefaultFilename($item);

        // None or custom to automatic thumbnail.
        if ($data->automatic_thumbnail && ($data->image !== $filename)) {
            $this->_regenerate = true;
        }

        if ($data->image && ($data->image === $filename))
        {
            // Force a re-generate if the document file changes.
            if ($data->storage_path && ($item->storage_path !== $data->storage_path)) {
                $this->_regenerate = true;
            }

            // Make sure that the thumb still exists ... re-generate if it doesn't.
            if (!file_exists($this->_getContainer()->fullpath . '/' . $filename)) {
                $this->_regenerate = true;
            }
        }
    }

    protected function _afterEdit(KControllerContextInterface $context)
    {
        $status_code = $context->getResponse()->getStatusCode();

        if ($status_code < 200 || $status_code >= 300) {
            return;
        }

        if ($this->_regenerate)
        {
            foreach ($context->result as $entity) {
               $this->_saveThumbnail($entity);
            }
        }
    }

    /**
     * Remove the attached thumbnail
     *
     * @param KControllerContextInterface $context
     */
    protected function _afterDelete(KControllerContextInterface $context)
    {
        foreach ($context->result as $entity)
        {
            $default = $this->getDefaultFilename($entity);

            $thumbnails = array($default);

            // Check if a custom thumbnail is set.
            if (($thumbnail = $entity->image) && ($thumbnail != $default))
            {
                // See if the custom image is being used on another document and mark it for deletion.
                if ($this->getObject('com://admin/docman.model.documents')->image($thumbnail)->count() == 0) {
                    $thumbnails[] = $thumbnail;
                }
            }

            // Delete thumbnails.
            foreach ($thumbnails as $thumbnail)
            {
                if (file_exists($this->_getContainer()->fullpath . '/' . $thumbnail))
                {
                    try
                    {
                        $this->getObject('com:files.controller.file')
                            ->container('docman-images')
                            ->folder(dirname($thumbnail))
                            ->name(basename($thumbnail))
                            ->delete();

                    } catch (KControllerException $e) {
                        // Do nothing.
                    }
                }
            }

            // Reset image on all documents making use of the default thumbnail.
            $documents = $this->getObject('com://admin/docman.model.documents')->image($default)->fetch();

            if (count($documents))
            {
                $documents->image = "";
                $documents->save();
            }
        }
    }
}
