<?php

/**
 * AjaxController
 * 
 * @author Visual Media
 */

namespace VisualM\MediaManagerBundle\Controller;

use Exception;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Finder\SplFileInfo;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use VisualM\MediaManagerBundle\Configuration\ConfigurationInterface;
use VisualM\MediaManagerBundle\FileSystem\File;
use VisualM\MediaManagerBundle\FileSystem\Folder;

/**
 * AjaxController
 * 
 * @author Visual Media
 */
class AjaxController extends ControllerBase
{

    /**
     * Delete a file or recursively
     *
     * @param string $path Path to file or directory
     * 
     * @return boolean Success
     */
    protected function recursiveDelete($path)
    {
        if (is_file($path)) {
            return @unlink($path);
        } elseif (is_dir($path)) {
            $scan = glob(rtrim($path, '/') . '/*');
            foreach ($scan as $index => $path) {
                $this->recursiveDelete($path);
            }
            return @rmdir($path);
        }
    }

    /**
     * Validate filename
     * 
     * @param ConfigurationInterface $configuration Configuration
     * @param string                 $filename      Filename
     * @param boolean                $isFile        File is regular file
     * 
     * @return void
     */
    protected function checkValidFilename($configuration, $filename, $isFile = true)
    {
        // Check if empty name is given
        if (empty($filename)) {
            throw new Exception("No name given");
        }

        // Cannot create . file that would make it disapper
        if (substr($filename, 0, 1) == '.') {
            throw new Exception(
                $this->translator->trans('exception.cannot_start_with_dot', array('%filename%' => $filename), 'VisualMMediaManagerBundle')                
            );
        }

        // Check for spaces at the end and the beginning
        if (trim($filename) != $filename) {
            throw new Exception(
                $this->translator->trans('exception.not_start_end_with_space', array('%filename%' => $filename), 'VisualMMediaManagerBundle')                
            );
        }

        // Check for invalid characters
        foreach (array('..', '/', '?', '*', ':' . ';', ',', '{', '}', '\\', "\r", "\n") as $invalid) {
            if (strpos($filename, $invalid) !== false) {
                throw new Exception(
                    $this->translator->trans('exception.invalid_character', array('%filename%' => $filename, '%char%' => $invalid), 'VisualMMediaManagerBundle')                
                );
            }
        }
        
        if ($isFile) {
            if (!$configuration->allowedForUpload($filename)) {
                throw new Exception(
                    $this->translator->trans('exception.not_allowed_in_config', array('%filename%' => $filename), 'VisualMMediaManagerBundle')                
                );
            }
        }
        
    }
    
    /**
     * Create HTML for File/Folder item
     * 
     * @param string       $config         Configuration key
     * @param string       $rel_base_path  Relative Base Path (Used for links)
     * @param SplFileInfo  $file           SPL File item
     * @param array        $extra_args     Arguments to add to file URL
     * 
     * @return string HTML
     */
    protected function renderItem($config, $rel_base_path, SplFileInfo $file, $extra_args) {
        
        $configuration = $this->getConfiguration($config);
        
        if ($file->isDir()) {
            $item = new Folder($configuration, $rel_base_path, $file);
        } elseif ($file->isFile()) {                
            $item = new File($configuration, $rel_base_path, $file);
        }

        return $this->renderView('VisualMMediaManagerBundle:Filesystem:item.html.twig', array(
            'config'    => $config,
            'file_item' => $item,
            'extra_args' => $extra_args
        ));
        
    }

    /**
     * Upload files
     * 
     * @param Request $request HTTP Request
     * @param string  $config  Configuration to load
     * 
     * @return void
     */
    public function uploadAction(Request $request, $config)
    {

        $success = true;
        $message = '';

        $configuration = $this->getConfiguration($config);
        $rel_path = $request->query->get('path', DIRECTORY_SEPARATOR);

        try {

            $path = $this->getFullpath($configuration->getBasePath(), $rel_path);

            // Check if an folder.. we cannot add an folder to a file..
            if (!is_dir($path)) {
                throw new Exception(sprintf('Given path "%s" should be a folder, file given', $rel_path));
            }

            // Check if we are allowed to edit folder
            $flags = $configuration->getFolderFlags($path);
            if (($flags & ConfigurationInterface::FLAG_EDIT) != ConfigurationInterface::FLAG_EDIT) {
                throw new Exception("Action is not allowed");
            }

            // Make sure path ends with an DIRECTORY_SEPARATOR
            if (substr($path, -1, 1) != DIRECTORY_SEPARATOR) {
                $path .= DIRECTORY_SEPARATOR;
            }
            
            // Check if file was uploaded
            if (null === ($uploaded_file = $request->files->get('file'))) {
                throw new Exception("No file found for upload");
            }
            /* @var $uploaded_file UploadedFile */

            // Get filename
            $filename = $uploaded_file->getClientOriginalName();
            $this->checkValidFilename($configuration, $filename);

            // Make sure file is unique
            $cntr = 1;
            while (file_exists($path . $filename)) {
                $filename = $uploaded_file->getClientOriginalName();
                $pathinfo = pathinfo($filename);
                $filename = $pathinfo['filename'] . ' (' . $cntr . ')' . '.' . $pathinfo['extension'];
                $cntr++;
            }
            
            // Store uploaded file
            $uploaded_file->move($path, $filename);
            
            $success = true;
            
        } catch (Exception $ex) {
            $success = false;
            $message = $ex->getMessage();
        }

        // Check if request was an AJAX request and then redirect or return response        
        if ($request->isXmlHttpRequest() == false) {
            return new RedirectResponse(
                $this->generateUrl('visual_m_media_manager_folder_index', array_merge(
                    array(
                        'config' => $config,
                        'path' => $rel_path
                    ), 
                    $this->getExtraArgs($request)
                ))
            );
        } else {
            
            if ($success) {
                $spl_file = new SplFileInfo($path . $filename, $rel_path, $filename);
                $html = $this->renderItem($config, $rel_path, $spl_file, $this->getExtraArgs($request));
            } else {
                $html = '';
            }
            
            return new Response(json_encode(array(
                'success' => $success,
                'message' => $message,
                'html'    => $html
            )));
        }
    }

    /**
     * Add Folder
     * 
     * @param Request $request HTTP Request
     * @param string  $config  Configuration to load
     * 
     * @return void
     */
    public function addFolderAction(Request $request, $config)
    {
       try {

            $configuration = $this->getConfiguration($config);
            $rel_path = $request->query->get('path', DIRECTORY_SEPARATOR);
            $new_name = $request->request->get('new_name', '');
            $path = $this->getFullpath($configuration->getBasePath(), $rel_path);

            // Check if an folder.. we cannot add an folder to a file..
            if (!is_dir($path)) {
                throw new Exception(sprintf('Given path "%s" should be a folder, file given', $rel_path));
            }

            // Check if we are allowed to edit folder
            $flags = $configuration->getFolderFlags($path);
            if (($flags & ConfigurationInterface::FLAG_EDIT) != ConfigurationInterface::FLAG_EDIT) {
                throw new Exception("Action is not allowed");
            }

            // Make sure path ends with an DIRECTORY_SEPARATOR
            if (substr($path, -1, 1) != DIRECTORY_SEPARATOR) {
                $path .= DIRECTORY_SEPARATOR;
            }

            // Check folder name
            $this->checkValidFilename($configuration, $new_name, false);

            // Create folder
            if (!@mkdir($path . $new_name, 0777)) {
                throw new Exception("Error creating folder");
            }

            // Render Item for View
            $spl_file = new SplFileInfo($path . $new_name, $rel_path, $new_name);
            $html = $this->renderItem($config, $rel_path, $spl_file, $this->getExtraArgs($request));
            
            return new Response(json_encode(array(
                'success' => true,
                'html'    => $html
            )));
            
            
        } catch (Exception $e) {
            return new Response(json_encode(array(
                    'success' => false,
                    'error' => $e->getMessage()
            )));
        }
    }

    /**
     * Rename File/Folder
     * 
     * @param Request $request HTTP Request
     * @param string  $config  Configuration to load
     * 
     * @return void
     */
    public function renameAction(Request $request, $config)
    {
        try {

            $configuration = $this->getConfiguration($config);
            $rel_path = $request->request->get('path', DIRECTORY_SEPARATOR);
            $new_name = $request->request->get('new_name', '');
            $path = $this->getFullpath($configuration->getBasePath(), $rel_path);

            
            
            if (is_dir($path)) {
                $flags = $configuration->getFolderFlags($path);
            } else {
                $flags = $configuration->getFileFlags($path);
            }

            if (($flags & ConfigurationInterface::FLAG_EDIT) != ConfigurationInterface::FLAG_EDIT) {
                throw new Exception("Action is not allowed");
            }

            $this->checkValidFilename($configuration, $new_name, !is_dir($path));

            // We have an valid name
            $new_fullname = dirname($path) . DIRECTORY_SEPARATOR . $new_name;
            if (!rename($path, $new_fullname)) {
                throw new Exception("Error renaming file");
            }

            if (is_dir($new_fullname)) {
                $base_rel_path = join(DIRECTORY_SEPARATOR, array_slice(explode(DIRECTORY_SEPARATOR, $rel_path), 0, -2)) . DIRECTORY_SEPARATOR;
            } else {
                $base_rel_path = join(DIRECTORY_SEPARATOR, array_slice(explode(DIRECTORY_SEPARATOR, $rel_path), 0, -1)) . DIRECTORY_SEPARATOR;
            }
                        
            // Render Item for View
            $spl_file = new SplFileInfo($new_fullname, $base_rel_path, $new_name);
            $html = $this->renderItem($config, $base_rel_path, $spl_file, $this->getExtraArgs($request));
            
            return new Response(json_encode(array(
                'success' => true,
                'html'    => $html
            )));
        } catch (Exception $e) {
            return new Response(json_encode(array(
                    'success' => false,
                    'error' => $e->getMessage()
            )));
        }
    }

    /**
     * Delete File/Folder
     * 
     * @param Request $request HTTP Request
     * @param string  $config  Configuration to load
     * 
     * @return void
     */
    public function deleteAction(Request $request, $config)
    {
        try {

            $configuration = $this->getConfiguration($config);
            $rel_path = $request->request->get('path', DIRECTORY_SEPARATOR);
            $path = $this->getFullpath($configuration->getBasePath(), $rel_path);

            if (is_dir($path)) {
                $flags = $configuration->getFolderFlags($path);
            } else {
                $flags = $configuration->getFileFlags($path);
            }

            if (($flags & ConfigurationInterface::FLAG_DELETE) != ConfigurationInterface::FLAG_DELETE) {
                throw new Exception("Action is not allowed");
            }

            $this->recursiveDelete($path);

            return new Response(json_encode(array(
                'success' => true
            )));
        } catch (Exception $e) {
            return new Response(json_encode(array(
                'success' => false,
                'error' => $e->getMessage()
            )));
        }
    }

}
