<?php
namespace Clevercherry\Backslash4\Controllers\Admin;

use Illuminate\Http\Request;
use Clevercherry\Backslash4\Controllers\Controller;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\View;
use App\Models\MediaManager;
use Clevercherry\Backslash4\Backslash4;
use Clevercherry\Backslash4\Traits\checkControllerSecurityAbort;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Storage;

class MediaManagerController extends BkAdminController
{
    use checkControllerSecurityAbort;

    public $adminView = "backslash4::admin.media";
    public $adminRoute = 'admin.mediamanager';
    public $AppModel = MediaManager::class;
    public $drive = 'local';

    function __construct()
    {
        $this->drive = env('MEDIASYSTEM_DISK', env('FILESYSTEM_DISK','local'));
    }

    public function create(Request $request)
    {
        $this->checkControllerSecurityAbort($this->AppModel);

        $item = new ($this->AppModel)();

        return view($this->adminView.'.uploader')
                    ->with('action', route($this->adminRoute.'.store'))
                    ->with('cancel', route($this->adminRoute.'.index'))
                    ->with('item', $item);
    }

    public function search(Request $request)
    {
        $this->checkControllerSecurityAbort($this->AppModel);

        $q = @$request->q ?? '';
        $popup = boolval(@$request->popup ?? false);

        if(empty($q))
            return ""; //return the empty string response to not load any ajax

        //else
        $words = Backslash4::explode("\r\n\t ,;", $q);

        $items = MediaManager::where('original', 'like', "%images/%{$q}%")
                    ->orWhere(function ($query) use($words) {
                        foreach($words as $word) {
                            $query->where('original', 'like', "%images/%{$word}%");
                        }
                    })
                    ->paginate(config('backslash4.media-paginate',9999))
                    ->appends(['q' => $q])
                    ->appends(['popup' => $popup]);

        // <!--
        //     BadMethodCallException: Call to undefined method Illuminate\Database\Eloquent\Builder::appends() 
        //     in file /Volumes/localhost-cs/localhost/phqv3/vendor/laravel/framework/src/Illuminate/Support/Traits/ForwardsCalls.php on line 71
                    

        
        return view('backslash4::components.bs-media.image-list')
                    ->with('media', $items)
                    ->with('no_message', 'No Images found with that search');

        // return view('backslash4::admin.media.no-dashboard.search_bs40')
        //     ->with('items', $items)
        //     ->with('files', $items)
        //     ->with('mediapopup', $popup);
    }

    public function getDirListing(Request $request)
    {
        $this->checkControllerSecurityAbort($this->AppModel);

        $dir = @$request->dir;
        if(empty($dir))
            return 'No Dir in Ajax Request: '.json_encode($dir);

        $tenant_prefix = empty(config('backslash4.tenants-on',0)) ? '' : Backslash4::getTenantKey().'/';

        if(Str::startsWith($dir,['images/',$tenant_prefix.'images/', 'movies/',$tenant_prefix.'movies/'])) //media_manager items
        {   return view('backslash4::components.bs-media.image-list')
                    ->with('media', MediaManager::getMediaInDirectory($dir));
        }  else //file_system items not in media manager
        {
            $cacheKey = 'media_manager_files_in_directory_' . md5($dir);
            $time = config('backslash4.cache.files.expire', 3600); // 1 hour default
            //cache()->forget($cacheKey); //clear the cache for this dir
            //Log::info('cache retrive: ', ['d'=>$dir, 'k'=>$cacheKey]);
            $files = cache()->remember($cacheKey, $time, function () use ($dir) {
                return MediaManager::getFilesInDirectory($dir);
            });
            return view('backslash4::components.bs-media.file-list')
                ->with('files', $files);
        }
    }

    public function getMediaStorageDrive()
    {
        return $this->drive;
    }

    public function getStoragePath($path='')
    {
        if($this->drive=='local')
        {   return storage_path("app/public/{$path}");
        }
        //else
        
        if(Str::startsWith($path, 's3:'))
            $path = substr($path,3);

        $tenant_prefix = empty(config('backslash4.tenants-on',0)) ? '' : '/'.Backslash4::getTenantKey();

        return env('APP_NAME','bs4').$tenant_prefix."/{$path}";
    }

    public function makeDir(Request $request)
    {
        $this->checkControllerSecurityAbort($this->AppModel);

        $mkdir = @$request->mkdir;
        if(!empty($mkdir))
            $this->ensureDirectoryExists($mkdir);
            
        return response()->json(MediaManager::getUploadDirectories('', '', dirname($mkdir)));
    }

    public function removeDir(Request $request)
    {
        $this->checkControllerSecurityAbort($this->AppModel);

        //from a post request

        $rmdir = @$request->rmdir;
        if(!empty($rmdir))
            $this->removeDirectory($rmdir);
    
        return redirect()->route('admin.mediamanager.index');
    }

    public function removeDirectory($rmdir)
    {
        $this->checkControllerSecurityAbort($this->AppModel);

        if($this->drive=='local')
        {   $path=storage_path("app/public/{$rmdir}");
            if(File::exists($path))
                File::deleteDirectory(storage_path("app/public/{$rmdir}"));
        } else
        {   
            //ensure the app name exists in the bucket
            $appname = env('APP_NAME','backslash4');
            $tenant_prefix = empty(config('backslash4.tenants-on',0)) ? '' : '/'.Backslash4::getTenantKey();

            $path = $appname.$tenant_prefix.'/'.$rmdir;
            if(Storage::disk($this->drive)->exists($path))
            {   
                Storage::disk($this->drive)->deleteDirectory($path);
            }
        }
    }

    public function ensureDirectoryExists($mkdir)
    {
        if($this->drive=='local')
                File::ensureDirectoryExists(storage_path("app/public/{$mkdir}"));
        else
        {   
            //ensire the app name exists in the bucket
            $appname = env('APP_NAME','backslash4');
            if(!Storage::disk($this->drive)->exists($appname))
                Storage::disk($this->drive)->makeDirectory($appname);

            $tenant_prefix = empty(config('backslash4.tenants-on',0)) ? '' : '/'.Backslash4::getTenantKey();
            if(!empty($tenant_prefix))
            {
                $path = $appname.$tenant_prefix;
                if(!Storage::disk($this->drive)->exists($path))
                    Storage::disk($this->drive)->makeDirectory($path);
            }


            $path = $appname.$tenant_prefix.'/'.dirname($mkdir);
            if(!Storage::disk($this->drive)->exists($path))
                Storage::disk($this->drive)->makeDirectory($path);

            $path = $appname.$tenant_prefix.'/'.$mkdir;
            if(!Storage::disk($this->drive)->exists($path))
                Storage::disk($this->drive)->makeDirectory($path);
        }
    }

    /**
     * tidy up a file name to ASCII and filename friendly characters
     * @param  String $filename  a file name
     * @return String            a filename without spaces nor weird characters norfilename unfriendly characters
     */
    public static function tidyFilename($filename)
    {
        // use Str::ascii and slug to get rid of unwanted characters
        $f = Str::slug(Str::ascii($filename));
        $f = Str::replace([' ','!','@','$','%','^','&','*','-','+','=','"',"'",':',';',',','/','\\','~','`','<','>','?'], '_', $f);
        $f = Str::replace('___', '_',$f);
        $f = Str::replace('__', '_',$f);

        return $f;
    }

    /**
     * return the top level folder by file extension
     * @param  String  $ext file extension
     * @return String  top folder name folder name
     */
    public static function getDefaultTopFolder($ext)
    {
        $lext = strtolower($ext);

        $tenant_prefix = empty(config('backslash4.tenants-on',0)) ? '' : Backslash4::getTenantKey().'/';

        //test against the fixed array (coz thats fatser), else test against extended configuration options
        if(in_array($lext, ['png', 'jpg', 'jpeg', 'gif', 'tga', 'bmp', 'webp', 'xbm', 'xpm', 'svg',
                            'mov', 'avi', 'mp4', 'mkv', 'm4v', 'mpg', 'mpeg', 'webm'])
                || in_array($lext, explode(' ',strtolower(MediaManager::getImageTypes()[0])))
                || in_array($lext, explode(' ',strtolower(MediaManager::getVideoTypes()[0]))) ) {
            return $tenant_prefix.'images'; //and videos
        }
        
        //else //documents generic files
        
        return $tenant_prefix.'misc';
    }

    public function dropFileUpload(Request $request, $image = null, $public = true)
    {  
        $this->checkControllerSecurityAbort($this->AppModel);

        try
        {
            if(empty($image)) {
                $image = $request->file('file');
            }
            if(gettype($image)=='string'){
                $image = $request->file($image);
            }
        
            $dir = @$request->dir;
            if(!empty($dir))
                $dir .= '/';

            $ext = strtolower(pathinfo($image->getClientOriginalName(), PATHINFO_EXTENSION));
            if($ext=='jpeg')
                $ext='jpg';
        
            $type = strtolower($image->extension());
            if(($ext!=$type) && ($type!='txt') && !in_array($ext, ['jpg','gif','png','webp']))
                return json_encode([ 'success'=>0, 
                'ext' => $ext,
                'type' => $type,
                'error' => "filetype contents ({$type}) does not match filename extension .".$ext]);
        
            //$imageName = basename($image->path()).'.'.$image->extension();
            $fileNameA = self::tidyFileName(pathinfo($image->getClientOriginalName(), PATHINFO_FILENAME));

            //get the toplevel folder, or use a default
            $typeFolder = @$request->topdir;
            if(empty($typeFolder) || $typeFolder=='null')
                $typeFolder = self::getDefaultTopFolder($ext);
            $typeFolder .= '/';
            
            if($this->drive=='s3')
            {   
                $path = storage_path("app/public"); //for s3 this is temporary
                $s3Dir=$typeFolder.$dir;
                $finalDestinationPath = $this->getStoragePath($s3Dir);
                $dir = '';
            } else
            {   
                $dir =  $typeFolder.$dir;
                $path = storage_path("app/public/".$dir);
                $finalDestinationPath = $path;            
            }

            // if not overwrite then we have to test for duplicate names
            // test if the filename already exists and renumber the filename
            $fileName = $fileNameA.'.'.$ext;
            $i=0;
            if(!Backslash4::is_true($request->overwrite, false)) {
                // if no overwrite switch generate a unique filename
                while(Storage::drive($this->drive)->exists($finalDestinationPath.'/'.$fileName))
                {
                    $i++;
                    $fileName= $fileNameA.'_('.$i.').'.$ext;
                }
            }

            $image->move($path,$fileName);

            $img    = null;
            $webp   = null;
            $thumb  = null;
            $width  = null;
            $height = null;

            if(!Str::endsWith($path, '/'))
                $path.='/';

            $extraFiles=[];

            //Log::info(json_encode([Backslash4::getDashboardConfigSetting('files.image-optimise',1),$path]) );
            if(!Str::startsWith($typeFolder,'file/') || Backslash4::getDashboardConfigSetting('files.image-optimise', 1))
            {
                // Get image size and type
                list($width, $height, $imageType) = getimagesize($path . $fileName);

                // Map the numerical image type to the appropriate imagecreatefrom* function
                switch ($imageType) {
                    case IMAGETYPE_PNG:
                        $img = imagecreatefrompng($path . $fileName);
                        break;
                    case IMAGETYPE_JPEG:
                        $img = imagecreatefromjpeg($path . $fileName);
                        break;
                    case IMAGETYPE_GIF:
                        $img = imagecreatefromgif($path . $fileName);
                        break;
                    case IMAGETYPE_BMP:
                        $img = imagecreatefrombmp($path . $fileName);
                        break;
                    case IMAGETYPE_WEBP:
                        $img = imagecreatefromwebp($path . $fileName);
                        break;
                    case IMAGETYPE_XBM:
                        $img = imagecreatefromxbm($path . $fileName);
                        break;
                    // Add more cases if needed
                    default:
                        $img = null;
                        break;
                }

                if($img!=null )
                {
                    //@TODO: remove errored files somehow

                    // Check image orientation
                    $exif = @exif_read_data($path . $fileName);
                    $orientation = 1;

                    if ($exif !== false && isset($exif['Orientation'])) {
                        $orientation = $exif['Orientation'];
                    }

                    imagepalettetotruecolor($img);
                    imagealphablending($img, true);
                    imagesavealpha($img, true);
                    if(imagesx($img)>2560) //image width>2560 scalse down to 2560
                    {   
                        $optimg = imagescale($img, 2560, -1, IMG_BILINEAR_FIXED); //IMG_BICUBIC);
                        $optimg = $this->rotateImage($optimg, $orientation);
                        $width = imagesx($optimg);
                        $height = imagesy($optimg);
                        
                        $webp =  $fileNameA.($i==0 ? '' : '_('.$i.')').'.opt.webp';
                        imagewebp($optimg, $path .'/'. $webp, 75);
                        imagedestroy($optimg);
                    } else
                    {
                        $width = imagesx($img);
                        $height = imagesy($img);
                        
                        $webp =  $fileNameA.($i==0 ? '' : '_('.$i.')').'.opt.webp';
                        imagewebp($img, $path .'/'. $webp, 75);
                    }

                    $thumbsize=320;
                    //dump($thumbsize);
                    $thimg = imagescale($img, 320, -1, IMG_BILINEAR_FIXED); //IMG_BICUBIC);
                    $thimg = $this->rotateImage($thimg, $orientation);
                    //dump($img);
                    $thumb = $fileNameA.($i==0 ? '' : '_('.$i.')').'.thumb.webp';
                    //dump($thumb);
                    imagewebp($thimg, $path .'/'.$thumb, 70);
                    //dump($img);
                    imagedestroy($thimg);
                    //dump($img);

                    $basemodel = Request()->model;
                    $model = 'App\\Models\\'.$basemodel;
                    
                    $fieldname = Request()->fieldname;

                    $extrasizes=[];
                    if(!empty($basemodel) && class_exists($model) && method_exists($model, 'getFieldMediaSizes'))
                    {   $extrasizes = ($model)::getFieldMediaSizes($fieldname);
                    } else
                    {
                        $extrasizes = \App\Models\MediaManager::getFieldMediaSizes($fieldname, $basemodel);
                    }
                    //Log::info($extrasizes);
                    foreach($extrasizes as $size)
                    {
                        $szFilename = $fileNameA.($i==0 ? '' : '_('.$i.')').'.'.Str::slug($size).'.webp';
                        if(!in_array($szFilename,$extraFiles))
                        {   $dims = Backslash4::explode([',',';','x'],$size);
                            $thumbsize = trim($dims[0]);
                            if(Str::endsWith($thumbsize,'%'))
                                $thumbsize = floor(imagesx($img)*intval(trim($thumbsize,'%'))/100);
                            //dump($thumbsize);
                            $szimg = imagescale($img, $thumbsize, -1, IMG_BILINEAR_FIXED); //IMG_BICUBIC);
                            $szimg = $this->rotateImage($szimg, $orientation);
                            //dump($img);
                            imagewebp($szimg, $path .'/'.$szFilename, 70);
                            //dump($img);
                            imagedestroy($szimg);
                            //dump($img);

                            $extraFiles[] = $szFilename;
                        }
                    }

                    imagedestroy($img);
                }
            }

            if($this->drive=='s3')
            {   //upload the files and remove them from local
                $visibility = ($public) ? 'public' : 'private';

                $this->ensureDirectoryExists($s3Dir);

                if ( !Str::endsWith($s3Dir, '/') ) {
                    $s3Dir.='/';
                }

                // default for Amazon as uploads default to public and it doesn't like the alt
                // $visibleArray =[];
                // // checking config is faster and cached ???
                // //if(Str::contains(Storage::disk('s3')->getClient()->getEndpoint(),'digitaloceanspaces',true)) {
                // if(Str::contains(config('filesystems.disks.s3.url', ''),'digitaloceanspaces',true)) {
                //     $visibleArray =['visibility' => $visibility];
                // }
                // amazon doesn't like the $visibilty in putFileAs, so set $visibility after each upload

                $result = Storage::disk('s3')
                                ->putFileAs(
                                    $finalDestinationPath,
                                    $path.$fileName,
                                    $fileName
                                );
                if (!$result) {
                    throw new \Exception("446 Failed to upload file (original) to S3: " . $finalDestinationPath .' - '
                                . json_encode([$finalDestinationPath,
                                    $path.$fileName,
                                    $fileName,
                                    $visibility])
                                );
                }
                Storage::disk('s3')->setVisibility($finalDestinationPath.'/'.$fileName , $visibility);
                File::delete($path.$fileName);
                $fileName = 's3:'.$s3Dir.$fileName;

                if(!empty($webp))
                {
                    $result = Storage::disk('s3')->putFileAs($finalDestinationPath, $path.$webp, $webp);
                    if (!$result) {
                        throw new \Exception("454 Failed to upload file (webp) to S3: " . $finalDestinationPath);
                    }
                    Storage::disk('s3')->setVisibility($finalDestinationPath.'/'.$webp , $visibility);
                    File::delete($path.$webp);
                    $webp = 's3:'.$s3Dir.$webp;
                }

                if(!empty($thumb))
                {
                    $result = Storage::disk('s3')->putFileAs($finalDestinationPath, $path.$thumb, $thumb);
                    if (!$result) {
                        throw new \Exception("465 Failed to upload file (thumb) to S3: " . $finalDestinationPath);
                    }
                    Storage::disk('s3')->setVisibility($finalDestinationPath.'/'.$thumb , $visibility);
                    File::delete($path.$thumb);
                    $thumb = 's3:'.$s3Dir.$thumb;
                }

                foreach($extraFiles as $exFileName)
                {
                    $result = Storage::disk('s3')->putFileAs($finalDestinationPath, $path.$exFileName, $exFileName);
                    // if (!$result) {
                    //     throw new \Exception("Failed to upload extra file to S3: " . $finalDestinationPath);
                    // }
                    Storage::disk('s3')->setVisibility($finalDestinationPath.'/'.$exFileName , $visibility);
                    File::delete($path.$exFileName);
                }
                
            } else
            {
                $fileName = $dir.$fileName;
                $webp =  $webp ? $dir.$webp : null;
                $thumb =  $thumb ? $dir.$thumb : null; 

            }

            if(in_array($typeFolder, ['images/', 'movies/']))
            {

                if($request->overwrite) {
                    //find an existing item
                    $media = MediaManager::where('original', $fileName)->first();

                    if(empty($media)) {
                        $media = new MediaManager;
                    }
                } else {
                    $media = new MediaManager;
                }

                $media->original = $fileName;
                $media->optimised = $webp;
                $media->thumbnail = $thumb;
                $media->opt_width = $width;
                $media->opt_height = $height;
                $media->fields = json_encode(['extrafiles' => $extraFiles]);
                $media->save();
            
                return json_encode([ 'success'=>1, 
                                  'thumbnail'=>$media->thumbnailUrl(),
                                  'original'=>$media->originalUrl(),
                                  'optimised'=>$media->optimisedUrl(),
                                  'id'=>$media->id
                              ]);
            } else
            {
                //clear the caches for this dir - so next refresh is forced to refresh
                $cdir = dirname($fileName);
                if (strpos($cdir, 's3:') === 0) {
                    $cacheKeyMM = 'media_manager_files_s3_' . md5(MediaManager::getStoragePath($cdir));
                    cache()->forget($cacheKeyMM);
                    $cdir = substr($cdir, 3);
                } else {
                    $cacheKeyMM = 'media_manager_files_local_' . md5(MediaManager::getStoragePath($cdir));
                    cache()->forget($cacheKeyMM);
                }
                $cacheKey = 'media_manager_files_in_directory_' . md5($cdir);
                //Log::info('cache clear: ', ['d'=>$cdir, 'k'=>$cacheKey]);
                //Log::info('cache clear: ', ['d'=>$cdir, 'km'=>$cacheKeyMM]);
                cache()->forget($cacheKey);

                return json_encode(['success'=>1, 
                    'thumbnail'=>$thumb ?? $fileName,
                    'original'=>$fileName,
                    'optimised'=>$webp ?? $fileName]);

            }

        } catch (\Exception $th)
        {
            Log::info('Media Manager exception', [$th]);
            return json_encode([ 'success'=>0, 
                                 'error' => $th->getMessage() ]);
        }                        
    }

    public function rotateImage($img, $orientation)
    {
        switch ($orientation) {
            case 3:
                $img = imagerotate($img, 180, 0);
                break;
            case 6:
                $img = imagerotate($img, -90, 0);
                break;
            case 8:
                $img = imagerotate($img, 90, 0);
                break;
        }

        return $img;
    }

    public function delete(Request $request, $id)
    {
        $this->checkControllerSecurityAbort($this->AppModel);

        $item = MediaManager::where('id',$id)->first();

        if($item!=null)
        {
            //remove the 3 associated files
            $files=[];
            $files[]=$item->original;
            $files[]=$item->optimised;
            $files[]=$item->thumbnail;

            //check same files are not on any other record
            $others = MediaManager::where('id','!=', $item->id)
                ->where( function($query) use($item, $files) {
                    $query->whereIn('original', $files)
                          ->orWhereIn('optimised', $files)
                          ->orWhereIn('thumbnail', $files);
                })->first();

            if(empty($others)) {
                foreach ($files as $filename) {

                    $fullpath = $this->getStoragePath($filename);
                    if (Str::startsWith($filename, 's3:')) {

                        if (Storage::drive('s3')->exists($fullpath)) {
                            Storage::drive('s3')->delete($fullpath);
                        }
    
                    } else
                    {
                        if(File::exists($fullpath))
                            File::delete($fullpath);
                    }
    
                }
            }
            //remove the record
            $item->delete();
        }

        return redirect()->back();
    }

    public function deleteUrl(Request $request)
    {
        $this->checkControllerSecurityAbort($this->AppModel);

        $url = @$request->url;
        $awsurl = env('AWS_URL', '');

        if ( !empty($awsurl) && Str::startsWith($url, $awsurl) ) {

            //S3: filespace
            $file = substr($url, strlen($awsurl));

            if (Storage::drive('s3')->exists($file)) {
                Storage::drive('s3')->delete($file);
            }

            //remove the hostname from the path
            $path=trim(dirname($file),'/ \t\n\r\0\x0B\\');
            if(Str::startsWith($path, env('APP_NAME')))
                $path = substr($path, strlen(env('APP_NAME')));
            
            $path=trim($path,'/ \t\n\r\0\x0B\\');
            
        } else
        {
            //normal local filespace
            $file = $url;

            $fullpath = $this->getStoragePath();
            if(File::exists($fullpath))
                    File::delete($fullpath);
            
            $path = dirname($fullpath);
        }

        return response()->json([
                'success'=>1, 
                'url' => $request->url,
                'file' => $file,
                's3' => (!empty($awsurl) && Str::startsWith($url, $awsurl)) ,
                'for' => $request->for,
                'fulldir' => $path
            ]);
    }
}
