<?php

/**
 * Controller class for handling ShowImage-related functionality.
 * Changelog:
 * 16/09/2014 : v1.0   Created class skeleton, added image upload functionality
 * 18/09/2014 : v1.1   Changed auth references to static calls
 * 06/02/2015 : v1.2   Changed endpoints to be more RESTful, changed classname to
 *                     better reflect functionality
 * 09/02/2015 : v1.2.1 Added more comments.
 *
 * @author  sourcefabric
 *
 * @version 1.2.1
 */
class Rest_ShowImageController extends Zend_Rest_Controller
{
    public function init()
    {
        // Remove layout dependencies
        $this->view->layout()->disableLayout();
        // Remove reliance on .phtml files to render requests
        $this->_helper->viewRenderer->setNoRender(true);
    }

    /**
     * headAction is needed as it is defined as an abstract function in the base controller.
     */
    public function headAction()
    {
        Logging::info('HEAD action received');
    }

    public function indexAction()
    {
        Logging::info('INDEX action received');
    }

    public function getAction()
    {
        Logging::info('GET action received');
    }

    public function putAction()
    {
        Logging::info('PUT action received');
    }

    /**
     * RESTful POST endpoint; used when uploading show images.
     */
    public function postAction()
    {
        $showId = $this->getShowId();

        if (!$showId) {
            $this->getResponse()
                ->setHttpResponseCode(400)
                ->appendBody('No show ID provided')
            ;

            return;
        }

        try {
            $path = $this->processUploadedImage($showId, $_FILES['file']['tmp_name']);
        } catch (Exception $e) {
            $this->getResponse()
                ->setHttpResponseCode(500)
                ->appendBody('Error processing image: ' . $e->getMessage())
            ;

            return;
        }

        $show = CcShowQuery::create()->findPk($showId);

        $con = Propel::getConnection();

        try {
            $con->beginTransaction();

            $show->setDbImagePath($path);
            $show->save();

            $con->commit();
        } catch (Exception $e) {
            $con->rollBack();
            $this->getResponse()
                ->setHttpResponseCode(500)
                ->appendBody("Couldn't add show image: " . $e->getMessage())
            ;
        }

        $this->getResponse()
            ->setHttpResponseCode(201)
        ;
    }

    /**
     * RESTful DELETE endpoint; used when deleting show images.
     */
    public function deleteAction()
    {
        $showId = $this->getShowId();

        if (!$showId) {
            $this->getResponse()
                ->setHttpResponseCode(400)
                ->appendBody('No show ID provided')
            ;

            return;
        }

        try {
            self::deleteShowImagesFromStor($showId);
        } catch (Exception $e) {
            $this->getResponse()
                ->setHttpResponseCode(500)
                ->appendBody('Error processing image: ' . $e->getMessage())
            ;
        }

        $show = CcShowQuery::create()->findPk($showId);

        $con = Propel::getConnection();

        try {
            $con->beginTransaction();

            $show->setDbImagePath(null);
            $show->save();

            $con->commit();
        } catch (Exception $e) {
            $con->rollBack();
            $this->getResponse()
                ->setHttpResponseCode(500)
                ->appendBody("Couldn't remove show image: " . $e->getMessage())
            ;
        }

        $this->getResponse()
            ->setHttpResponseCode(201)
        ;
    }

    /**
     * Verify and process an uploaded image file, copying it into
     * .../stor/imported/:owner-id/show-images/:show-id/ to differentiate between
     * individual users and shows.
     *
     * @param int    $showId       the ID of the show we're adding the image to
     * @param string $tempFilePath temporary filepath assigned to the upload generally of the form /tmp/:tmp_name
     *
     * @throws Exception
     *                   - when a file with an unsupported file extension is uploaded or an
     *                   error occurs in copyFileToStor
     *
     * @return string the path to the new location for the file
     */
    private function processUploadedImage($showId, $tempFilePath)
    {
        $ownerId = RestAuth::getOwnerId();

        //Only accept files with a file extension that we support.
        $fileExtension = $this->getFileExtension($tempFilePath);

        if (!in_array(strtolower($fileExtension), explode(',', 'jpg,png,gif,jpeg'))) {
            @unlink($tempFilePath);

            throw new Exception('Bad file extension.');
        }

        $storDir = Application_Model_MusicDir::getStorDir();
        $importedStorageDirectory = $storDir->getDirectory() . 'imported/' . $ownerId . '/show-images/' . $showId;

        try {
            $importedStorageDirectory = $this->copyFileToStor($tempFilePath, $importedStorageDirectory, $fileExtension);
        } catch (Exception $e) {
            @unlink($tempFilePath);

            throw new Exception('Failed to copy file: ' . $e->getMessage());
        }

        return $importedStorageDirectory;
    }

    /**
     * Check the MIME type of an uploaded file to determine what extension it should have.
     *
     * @param $tempFilePath the file path to the uploaded file in /tmp
     *
     * @return string the file extension for the new file based on its MIME type
     */
    private function getFileExtension($tempFilePath)
    {
        // Don't trust the extension - get the MIME-type instead
        $fileInfo = finfo_open();
        $mime = finfo_file($fileInfo, $tempFilePath, FILEINFO_MIME_TYPE);

        return $this->getExtensionFromMime($mime);
    }

    /**
     * Use a hardcoded list of accepted MIME types to return a file extension.
     *
     * @param $mime the MIME type of the file
     *
     * @return string the file extension based on the given MIME type
     */
    private function getExtensionFromMime($mime)
    {
        $extensions = [
            'image/jpeg' => 'jpg',
            'image/png' => 'png',
            'image/gif' => 'gif',
        ];

        return $extensions[$mime];
    }

    /**
     * Copy a given file in /tmp to the user's stor directory.
     *
     * @param string $tempFilePath             the path to the file in /tmp
     * @param string $importedStorageDirectory the path to the new location for the file
     * @param string $fileExtension            the file's extension based on its MIME type
     *
     * @throws Exception if either the storage directory does not exist and cannot be
     *                   created, the storage directory does not have write permissions
     *                   enabled, or the user's hard drive does not have enough space to
     *                   store the file
     *
     * @return string the new full path to the file in stor
     */
    private function copyFileToStor($tempFilePath, $importedStorageDirectory, $fileExtension)
    {
        $image_file = $tempFilePath;

        // check if show image dir exists and if not, create one
        if (!file_exists($importedStorageDirectory)) {
            if (!mkdir($importedStorageDirectory, 0777, true)) {
                throw new Exception('Failed to create storage directory.');
            }
        }

        if (chmod($image_file, 0644) === false) {
            Logging::info("Warning: couldn't change permissions of {$image_file} to 0644");
        }

        $newFileName = substr($tempFilePath, strrpos($tempFilePath, '/')) . '.' . $fileExtension;

        // Did all the checks for real, now trying to copy
        $image_stor = Application_Common_OsPath::join($importedStorageDirectory, $newFileName);
        Logging::info('Adding image: ' . $image_stor);
        Logging::info("copyFileToStor: moving file {$image_file} to {$image_stor}");

        if (@rename($image_file, $image_stor) === false) {
            //something went wrong likely there wasn't enough space in .
            //the audio_stor to move the file too warn the user that   .
            //the file wasn't uploaded and they should check if there  .
            //is enough disk space                                     .
            unlink($image_file); //remove the file after failed rename

            throw new Exception('The file was not uploaded, this error can occur if the computer '
                                . 'hard drive does not have enough disk space or the stor '
                                . 'directory does not have correct write permissions.');
        }

        return $image_stor;
    }

    // Should this be an endpoint instead?
    /**
     * Delete any images belonging to the show with the given ID.
     *
     * @param int $showId the ID of the show we're deleting images from
     *
     * @return bool true if the images were successfully deleted, otherwise false
     */
    public static function deleteShowImagesFromStor($showId)
    {
        $ownerId = RestAuth::getOwnerId();

        $storDir = Application_Model_MusicDir::getStorDir();
        $importedStorageDirectory = $storDir->getDirectory() . 'imported/' . $ownerId . '/show-images/' . $showId;

        Logging::info('Deleting images from ' . $importedStorageDirectory);

        // to be safe in case image uploading functionality is extended later
        if (!file_exists($importedStorageDirectory)) {
            Logging::info('No uploaded images for show with id ' . $showId);

            return true;
        }

        return self::delTree($importedStorageDirectory);
    }

    // from a note @ http://php.net/manual/en/function.rmdir.php
    private static function delTree($dir)
    {
        $files = array_diff(scandir($dir), ['.', '..']);
        foreach ($files as $file) {
            (is_dir("{$dir}/{$file}")) ? self::delTree("{$dir}/{$file}") : unlink("{$dir}/{$file}");
        }

        return rmdir($dir);
    }

    /**
     * Fetch the id parameter from the request.
     *
     * @return bool|int false if the show id wasn't
     *                  provided, otherwise returns the id
     */
    private function getShowId()
    {
        if (!($id = $this->_getParam('id', false))) {
            $resp = $this->getResponse();
            $resp->setHttpResponseCode(400);
            $resp->appendBody('ERROR: No show ID specified.');

            return false;
        }

        return $id;
    }
}