<?php

namespace App\Models\Abstracted;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Schema;
use DB;

use App\Models\{Person,Tag};

abstract class Catalog extends Model
{
	protected $dates = ['date_created'];

	public function people()
	{
		return $this->belongsToMany(Person::class);
	}

	public function tags()
	{
		return $this->belongsToMany(Tag::class);
	}

	/**
	 * Multimedia NOT duplicate.
	 *
	 * @return \Illuminate\Database\Eloquent\Builder
	 */
	public function scopeNotDuplicate($query)
	{
		return $query->where(function($query)
		{
			$query->where('duplicate','<>',TRUE)
				->orWhere('duplicate','=',NULL);
		});
	}

	/**
	 * Multimedia NOT pending removal.
	 *
	 * @return \Illuminate\Database\Eloquent\Builder
	 */
	public function scopeNotRemove($query)
	{
		return $query->where(function($query)
		{
			$query->where('remove','<>',TRUE)
				->orWhere('remove','=',NULL);
		});
	}

	/**
	 * Multimedia NOT scanned.
	 *
	 * @return \Illuminate\Database\Eloquent\Builder
	 */
	public function scopeNotScanned($query)
	{
		return $query->where(function($query)
		{
			$query->where('scanned','<>',TRUE)
				->orWhere('scanned','=',NULL);
		});
	}

    abstract public function setDateCreated();
    abstract public function setLocation();
    abstract public function setMakeModel();
    abstract public function setSignature();
    abstract public function setSubSecTime();
    abstract public function setThumbnail();
    abstract public function view();

	/**
	 * Date the multimedia was created
	 * @todo return Carbon date or NULL
	 */
	public function date_taken(): string
	{
		return $this->date_created ? date('Y-m-d H:i:s',$this->date_created) : 'UNKNOWN';
	}

	/**
	 * Return the date of the file
	 * @todo return Carbon date or NULL
	 */
	public function file_date($type,$format=FALSE)
	{
		if (! is_readable($this->file_path()))
			return NULL;

		switch ($type)
		{
			case 'a': $t = fileatime($this->file_path());
				break;
			case 'c': $t = filectime($this->file_path());
				break;
			case 'm': $t = filemtime($this->file_path());
				break;
		}

		return $format ? date('d-m-Y H:i:s',$t) : $t;
	}

	/**
	 * Determine the new name for the image
	 * @todo Make Generic for Photo and Video
	 */
	public function file_path($short=FALSE,$new=FALSE)
	{
		$file = $this->filename;

		if ($new)
			$file = sprintf('%s.%s',((is_null($this->date_created) OR ! $this->date_created)
				? sprintf('UNKNOWN/%07s',$this->file_path_id())
				: date('Y/m/d-His',$this->date_created)),$this->type());

		return (($short OR preg_match('/^\//',$file)) ? '' : config('video.dir').DIRECTORY_SEPARATOR).$file;
	}

	/**
	 * Calculate a file path ID based on the id of the file
	 */
	public function file_path_id($sep=3,$depth=9): string
	{
		return trim(chunk_split(sprintf("%0{$depth}s",$this->id),$sep,'/'),'/');
	}

	/**
	 * Display the file signature
	 */
	public function file_signature($short=FALSE)
	{
		return $short ? static::signaturetrim($this->file_signature) : $this->file_signature;
	}

	/**
	 * Return the file size
	 * @deprecated
	 */
	public function file_size()
	{
		return (! is_readable($this->file_path())) ? NULL : filesize($this->file_path());
	}

	/**
	 * Return item dimensions
	 */
	public function getDimensionsAttribute(): string
	{
		return $this->width.'x'.$this->height;
	}

	/**
	 * Return HTML Checkbox for duplicate
	 */
	public function getDuplicateCheckboxAttribute()
	{
		return $this->HTMLCheckbox('duplicate',$this->id,$this->duplicate);
	}

	/**
	 * Return the file size
	 *
	 * @return false|int|null
	 */
	public function getFileSizeAttribute()
	{
		return (! is_readable($this->file_path())) ? NULL : filesize($this->file_path());
	}

	/**
	 * Return HTML Checkbox for flagged
	 */
	public function getFlagCheckboxAttribute()
	{
		return $this->HTMLCheckbox('flag',$this->id,$this->flag);
	}

	/**
	 * @deprecated
	 */
	public function getDuplicateTextAttribute()
	{
		return $this->TextTrueFalse($this->duplicate);
	}

	/**
	 * @deprecated
	 */
	public function getFlagTextAttribute()
	{
		return $this->TextTrueFalse($this->flag);
	}

	/**
	 * Return HTML Checkbox for remove
	 */
	public function getRemoveCheckboxAttribute()
	{
		return $this->HTMLCheckbox('remove',$this->id,$this->remove);
	}

	/**
	 * Display the GPS coordinates
	 */
	public function gps(): string
	{
		return ($this->gps_lat AND $this->gps_lon) ? sprintf('%s/%s',$this->gps_lat,$this->gps_lon) : 'UNKNOWN';
	}

	/**
	 * Return an HTML checkbox
	 */
	protected function HTMLCheckbox($name,$id,$value)
	{
		return sprintf('<input type="checkbox" name="%s[%s]" value="1"%s>',$name,$id,$value ? ' checked="checked"' : '');
	}

	/**
	 * Get ID Info link
	 */
	protected function HTMLLinkAttribute($id,$url)
	{
		return sprintf('<a href="%s" target="%s">%s</a>',url($url.$id),$id,$id);
	}

	/**
	 * Determine if the parent dir is writable
	 *
	 * @param $dir
	 * @return bool
	 */
	public function isParentWritable($dir): bool
	{
		if (file_exists($dir) AND is_writable($dir) AND is_dir($dir))
			return TRUE;

		elseif ($dir == dirname($dir) OR file_exists($dir))
			return FALSE;

		else
			return ($this->isParentWritable(dirname($dir)));
	}

	/**
	 * Determine if a file is moveable
	 *
	 * useID boolean Determine if the path is based on the the ID or date
	 * @todo Change to boolean and rename isMoveable() Log any FALSE reason.
	 */
	public function moveable()
	{
		// If the source and target are the same, we dont need to move it
		if ($this->file_path() == $this->file_path(FALSE,TRUE))
			return FALSE;

		// If there is already a file in the target.
		// @todo If the target file is to be deleted, we could move this file
		if (file_exists($this->file_path(FALSE,TRUE)))
			return 1;

		// Test if the source is readable
		if (! is_readable($this->file_path()))
			return 2;

		// Test if the dir is writable (so we can remove the file)
		if (! $this->isParentWritable(dirname($this->file_path())))
			return 3;

		// Test if the target dir is writable
		// @todo The target dir may not exist yet, so we should check that a parent exists and is writable.
		if (! $this->isParentWritable($this->file_path(FALSE,TRUE)))
			return 4;

		return TRUE;
	}

	/**
	 * Get the id of the previous record
	 */
	public function next()
	{
		return DB::table($this->getTable())
			->where('id','>',$this->id)
			->orderby('id','ASC')
			->first();
	}

	/**
	 * Return my class shortname
	 */
	public function objectType(): string
	{
		return (new \ReflectionClass($this))->getShortName();
	}

	/**
	 * Get the id of the previous record
	 */
	public function previous()
	{
		return DB::table($this->getTable())
			->where('id','<',$this->id)
			->orderby('id','DESC')
			->first();
	}

	public function setHeightWidth()
	{
		$this->height = $this->property('height');
		$this->width = $this->property('width');
		$this->orientation = $this->property('orientation');
	}

	/**
	 * Display the media signature
	 */
	public function signature($short=FALSE)
	{
		return $short ? static::signaturetrim($this->signature) : $this->signature;
	}

	/**
	 * Trim a string
	 *
	 * @todo rename stringtrim
	 * @param string $string
	 * @param int $chrs
	 * @return string
	 */
	public static function stringtrim(string $string,int $chrs=6)
	{
		return sprintf('%s...%s',substr($string,0,$chrs),substr($string,-1*$chrs));
	}

	/**
	 * @deprecated
	 * @param string $string
	 * @param int $chrs
	 * @return string
	 */
	public static function signaturetrim(string $string,int $chrs=6)
	{
		return static::stringtrim($string,$chrs);
	}

	/**
	 * Determine if the image should be moved
	 */
	public function shouldMove(): boolean
	{
		return ($this->filename != $this->file_path(TRUE,TRUE));
	}

	protected function TextTrueFalse($value): string
	{
		return $value ? 'TRUE' : 'FALSE';
	}

	/**
	 * @todo Check if this is redundant
	 *
	 * @param bool $includeme
	 * @return mixed
	 */
	public function list_duplicate($includeme=FALSE)
	{
		return $this->list_duplicates($includeme)->pluck('id');
	}

	/**
	 * Find duplicate images based on some attributes of the current image
	 * @todo Optimise this query
	 */
	public function list_duplicates($includeme=FALSE)
	{
		$o = static::select();

		if ($this->id AND ! $includeme)
			$o->where('id','!=',$this->id);

		// Ignore photo's pending removal.
		if (! $includeme)
			$o->where(function($query)
			{
				$query->where('remove','<>',TRUE)
					->orWhere('remove','=',NULL);
			});

		// Where the signature is the same
		$o->where(function($query)
		{
			$query->where('signature','=',$this->signature);

			// Or they have the same time taken with the same camera
			if ($this->date_created AND ($this->model OR $this->make))
			{
				$query->orWhere(function($query)
				{
					$query->where('date_created','=',$this->date_created ? $this->date_created : NULL);
					if (Schema::hasColumn($this->getTable(),'subsectime'))
						$query->where('subsectime','=',$this->subsectime ? $this->subsectime : NULL);

					if (! is_null($this->model))
						$query->where('model','=',$this->model);

					if (! is_null($this->make))
						$query->where('make','=',$this->make);

				});
			}
		});

		return $o->get();
	}
}