<?php class dropimage extends baseobject {
	
	protected static array $fields=array(
			'name'=>validator::REQUIRED,
			'imagingsessionid'=>array(validator::REQUIRED, validator::INTEGER),
			'welldropid'=>array(validator::REQUIRED, validator::INTEGER),
            'pixelheight'=>array(validator::INTEGER),
            'pixelwidth'=>array(validator::INTEGER),
			'micronsperpixelx'=>array(validator::FLOAT),
			'micronsperpixely'=>array(validator::FLOAT),
            'latestcrystalscoreid'=>validator::INTEGER,
            'latestcrystalprobabilitypercent'=>validator::FLOAT,
			'imagestorepath'=>validator::REQUIRED,
			'imagepath'=>validator::REQUIRED,
			'thumbnailpath'=>validator::REQUIRED,



	);
	
	protected static array $helpTexts=array(
			'name'=>'A unique name for this drop image',
			'imagingsessionid'=>'The imaging session where this image was taken',
			'welldropid'=>'The drop that was imaged',
			'pixelheight'=>'Height of the image in pixels',
			'pixelwidth'=>'Width of the image in pixels',
			'micronsperpixelx'=>'Scale on the x axis',
			'micronsperpixely'=>'Scale on the y axis',
			'imagestorepath'=>'The base directory of the image store',
			'imagepath'=>'Path from image store to the main image',
			'thumbnailpath'=>'Path from image store to thumbnail',
	);
	
	public static string $defaultSortOrder='platewell.row, platewell.col, welldrop.dropnumber, imagingsession.imageddatetime';
	
	protected static string $adminSelect="SELECT SQL_CALC_FOUND_ROWS dropimage.*, imagingsession.imageddatetime, imagingsession.lighttype,
			CONCAT('/api/dropimagefile/',dropimage.id,'/full.',SUBSTRING_INDEX(dropimage.imagepath,'.',-1) ) AS fullimageurl, 
			CONCAT('/api/dropimagethumb/',dropimage.id,'/thumb.',SUBSTRING_INDEX(dropimage.thumbnailpath,'.',-1)) AS thumbnailurl,
			platewell.id AS platewellid, platewell.row, platewell.col, welldrop.dropnumber
			FROM dropimage, welldrop, platewell, imagingsession 
			WHERE dropimage.welldropid=welldrop.id AND welldrop.platewellid=platewell.id AND dropimage.imagingsessionid=imagingsession.id";
	protected static string $normalSelect="SELECT SQL_CALC_FOUND_ROWS dropimage.*, imagingsession.imageddatetime, imagingsession.lighttype, 
			CONCAT('/api/dropimagefile/',dropimage.id,'/full.',SUBSTRING_INDEX(dropimage.imagepath,'.',-1) ) AS fullimageurl, 
			CONCAT('/api/dropimagethumb/',dropimage.id,'/thumb.',SUBSTRING_INDEX(dropimage.thumbnailpath,'.',-1)) AS thumbnailurl,
			platewell.id AS platewellid, platewell.row, platewell.col, welldrop.dropnumber,
			dropimage.latestcrystalscoreid AS latestcrystalscoreid
			FROM dropimage, welldrop, platewell, imagingsession 
			WHERE dropimage.welldropid=welldrop.id AND welldrop.platewellid=platewell.id AND dropimage.imagingsessionid=imagingsession.id ";
	
	private static int $THUMBNAIL_MAXHEIGHT=120;
	private static int $THUMBNAIL_MAXWIDTH=150;
	
	
	public static function getSelectClause(): string {
		return static::$normalSelect;
	}
	
	protected static function getProjectClause(string $accessType, bool $forceSharedProject=false): string {
		return ''; //Images are publicly accessible, nothing in the metadata worth hiding.
	}
	
	public static function create(array $request=[]): array {
		
		//Formulatrix importer doesn't have a $_FILES record.
		if(empty($_FILES) || empty($_FILES['dropimage'])){
            $limsStorage=rtrim($request['imagestorepath'],'/').'/';
            $request['imagepath']=str_replace($limsStorage, '', $request['imagepath']);
            $request['thumbnailpath']=str_replace($limsStorage, '', $request['thumbnailpath']);
            $fullImagePath=$limsStorage.ltrim($request['imagepath'],'/');
            $fullThumbPath=$limsStorage.ltrim($request['thumbnailpath'],'/');
            if(!file_exists($fullImagePath) || !file_exists($fullThumbPath)){
                throw new ServerException('Image or thumbnail does not exist');
            }
            $info=@getimagesize($fullImagePath);
            if(!$info || 0==$info[0]){
                throw new BadRequestException('Image file does not appear to be an image');
            }
            $request['pixelwidth']=$info[0];
            $request['pixelheight']=$info[1];
            if(!isset($request['micronsperpixelx'])){ $request['micronsperpixelx']=-1; }
            if(!isset($request['micronsperpixely'])){ $request['micronsperpixely']=-1; }
            $created=parent::create($request);
            static::carryForwardScores($created['created']['id']);
            return $created;
		}

		$gdAvailable=defined('GD_VERSION');
		if(!$gdAvailable){
			throw new ServerException('Cannot upload drop images because the GD image library is not available in PHP. Talk to your administrator.');
		}

		$file=$_FILES['dropimage'];
		if(1==$file['error']){
            throw new BadRequestException('The image could not be uploaded, probably because its file size is too big. An administrator will need to fix this.');
        }

		$request['filename']=$file['name'];
		$request['mimetype']=$file['type'];
		$request['bytes']=$file['size'];
		
		/**
		 * TODO Check that the file is png/jpg
		 */
		$info=@getimagesize($file['tmp_name']);
		if(!$info || 0==$info[0]){
			throw new BadRequestException('Uploaded file does not appear to be an image');
		}
		$request['pixelwidth']=$info[0];
		$request['pixelheight']=$info[1];
		$mimeType=$info['mime'];
		if($mimeType=='image/jpeg'){
			$extension='jpg';
		} else if($mimeType=='image/png'){
			$extension='png';
		} else {
			throw new BadRequestException('Uploaded file does not appear to be JPEG or PNG image.\n('.$mimeType.')');
		}
		
		$wellDrop=welldrop::getById($request['welldropid']);
		$plateWell=platewell::getById($wellDrop['platewellid']);
		$imagingSession=imagingsession::getById($request['imagingsessionid']);
		$plate=plate::getById($imagingSession['plateid']);
		$barcode=$plate['name'];
		$request['projectid']=$imagingSession['projectid'];
		if(!session::isAdmin() && !in_array($imagingSession['projectid'], session::getUpdateProjects())){
			throw new ForbiddenException('You need "Update" permission on this project to attach files');
		}
		
		$dropName=platetype::$rowLabels[$plateWell['row']].str_pad($plateWell['col'],2,'0',STR_PAD_LEFT).'_'.$wellDrop['dropnumber']; //A01_1
		
		$limsStorage=config::get('core_imagestore');
		$limsStorage=rtrim($limsStorage,'/\\').'/';
		if(!file_exists($limsStorage)){
			throw new ServerException('IceBear image store does not exist: '.$limsStorage);
		}
		$request['imagestorepath']=$limsStorage;
		
		$destinationDir=substr($barcode,0,2).'/'.substr($barcode,0,3).'/'.$barcode.'/imagingsession'.$imagingSession['id'].'/';
		$thumbsDir=$destinationDir.'thumbs/';
		if(!file_exists($limsStorage.$destinationDir)){
			@mkdir($limsStorage.$thumbsDir,0755,true);
		}

		$fullPath=$limsStorage.$destinationDir.$dropName.'.'.$extension;
		$thumbPath=$limsStorage.$destinationDir.'thumbs/'.$dropName.'.'.$extension;
		
		if(!@move_uploaded_file($file['tmp_name'], $fullPath)){
			throw new ServerException('Could not copy uploaded image to IceBear image store');
		}
		
		$thumbScale=max( ($request['pixelheight']/static::$THUMBNAIL_MAXHEIGHT), ($request['pixelwidth']/static::$THUMBNAIL_MAXWIDTH) );
		$newHeight=(int)(min( $request['pixelheight']/$thumbScale, static::$THUMBNAIL_MAXHEIGHT));
		$newWidth=(int)(min( $request['pixelwidth']/$thumbScale, static::$THUMBNAIL_MAXWIDTH));

		$thumb=imagecreatetruecolor($newWidth,$newHeight);
		if('jpg'==$extension){
			$image=imagecreatefromjpeg($fullPath);
			@imagecopyresized($thumb, $image, 0, 0, 0, 0, $newWidth, $newHeight, $request['pixelwidth'], $request['pixelheight']);
			imagejpeg($thumb, $thumbPath);
		} else {
		    $pngHeaders=file::getPngPixelsPerUnit($fullPath);
			$image=imagecreatefrompng($fullPath);
			@imagecopyresized($thumb, $image, 0, 0, 0, 0, $newWidth, $newHeight, $request['pixelwidth'], $request['pixelheight']);
			imagepng($thumb, $thumbPath);
		}
		
		if(!file_exists($thumbPath)){
			throw new ServerException('Could not write image thumbnail file');
		}
		
		$request['name']=$barcode.'_'.str_replace('_', '.', $dropName).'_is'.$imagingSession['id'];
		
		$request['micronsperpixelx']=-1;
		$request['micronsperpixely']=-1;
		if(isset($pngHeaders) && 'metres'==$pngHeaders['units']){
		    // We're going to assume that this came from a Zeiss and deal with the fallout when we find something that
            // has differing scale info. Furthermore, we're going to ignore anything where the units differ by more than
            // 5% - not dealing with non-square pixels!
            $pixelsPerUnitX=(int)($pngHeaders['pixelsPerUnitX']);
            $pixelsPerUnitY=(int)($pngHeaders['pixelsPerUnitY']);
            $pixelRatio=$pixelsPerUnitX/$pixelsPerUnitY;
            if(0.95<$pixelRatio && 1.05>$pixelRatio){
                $scale=((int)$request['pixelwidth'])/$pixelsPerUnitX;
                $request['micronsperpixelx']=$scale;
                $request['micronsperpixely']=$scale;
            }
        }

		$request['imagestorepath']=$limsStorage;
		$request['imagepath']=str_replace($limsStorage, '', $fullPath);
		$request['thumbnailpath']=str_replace($limsStorage, '', $thumbPath);

        $ret=parent::create($request);
        static::carryForwardScores($ret['created']['id']);
        return $ret;
	}

    /**
     * @param $id
     * @param array $request
     * @return array
     * @throws BadRequestException
     * @throws ForbiddenException
     * @throws NotFoundException
     * @throws ServerException
     */
	public static function update(int $id, array $request=[]): array {

        $defaultProjectId=baseproject::getDefaultProjectId();
        $wasAdmin=session::isAdmin();
        session::becomeAdmin();
        $dropImage=dropimage::getById($id);
        if(!$dropImage){ throw new NotFoundException('No drop image with ID '.$id); }
        if($dropImage['projectid']==$defaultProjectId && !$wasAdmin){
            $wellDrop=welldrop::getById($dropImage['welldropid']);
            $plateWell=platewell::getById($wellDrop['platewellid']);
            $plate=plate::getById($plateWell['plateid']);
            if($plate['ownerid']!=session::getUserId()){
                session::revertAdmin();
                throw new ForbiddenException('Plate is in the default project and you are not its owner. You cannot update this plate.');
            }
        }
        if(!$wasAdmin){
            session::revokeAdmin();
        }

        static::beforeUpdateHook($id,$request);
        if(!isset($request['latestcrystalscoreid']) && !isset($request['latestcrystalprobabilitypercent'])){
            //Actually updating the dropimage
            $request['_classname']='dropimage';
            $ret=parent::update($id,$request);
        } else {
            //Scoring the dropimage
            $request['dropimageid']=$id;
            if(isset($request['latestcrystalscoreid']) && "0"===$request['latestcrystalscoreid']){
                //nuke all scores on this image and leave it unscored
                //scoreofdropimage method will update drop image and plate
                $ret=scoreofdropimage::deleteAllForDropImage($id);
            } else {
                //record the score in the dedicated table
                //scoreofdropimage will update drop image and plate
                if(isset($request['latestcrystalscoreid'])){
                    $request['crystalscoreid']=$request['latestcrystalscoreid'];
                }
                if(isset($request['latestcrystalprobabilitypercent'])){
                    $request['crystalprobabilitypercent']=$request['latestcrystalprobabilitypercent'];
                }
                $created=scoreofdropimage::create($request)['created'];
                $updated=dropimage::getById($id);
                $ret=array('created'=>$created, "updated"=>$updated);
            }
        }
        static::afterUpdateHook($id);
        return $ret;
	}

	/**
	 * @param int $id
	 * @return array
	 * @throws BadRequestException
	 * @throws ForbiddenException
	 * @throws NotFoundException
	 * @throws ServerException
	 */
	public static function delete(int $id): array {
		$dropImage=static::getById($id);
		if(!$dropImage){
			throw new NotFoundException('No drop image with ID '.$id);
		}
		$wellDrop=welldrop::getById($dropImage['welldropid']);
		$imagingSession=imagingsession::getById($dropImage['imagingsessionid']);
		$imager=imager::getById($imagingSession['imagerid']);
		$plate=plate::getById($imagingSession['plateid']);
		$projectId=$plate['projectid'];

		/*
		 * Non-admins can delete images if the imager is manual AND (they are the plate owner OR they have delete permissions)
		 * No image can be deleted if it has crystals marked - not even by an admin.
		 */
		if(!session::isAdmin()){
			if($plate['ownerid']!=session::getUserId() && !in_array($projectId, session::getDeleteProjects())){
				throw new ForbiddenException('You must be the plate owner or have permission to delete in its project');
			}
			if(!$imager['manualimaging']){
				throw new BadRequestException('Cannot delete images on automatic imagers');
			}
		}
		$crystals=welldrop::getcrystals($wellDrop['id']);
		if($crystals){
			throw new BadRequestException('Crystals have been marked on this image, so it cannot be deleted');
		}
	
		//Remove the DB record
		$ret=parent::delete($id);
		if($ret['deleted']){
			//remove images
			if(!empty($dropImage['imagestorepath']) && !empty($dropImage['imagepath']) && !empty($dropImage['thumbnailpath']) ){
				if(!str_contains($dropImage['imagepath'],'dummy')){ @unlink($dropImage['imagestorepath'].$dropImage['imagepath']); }
				if(!str_contains($dropImage['thumbnailpath'],'dummy')){ @unlink($dropImage['imagestorepath'].$dropImage['thumbnailpath']); }
			}
		}
		return $ret;
	}

	/**
	 * @param int $id
	 * @param array $request
	 * @return array|null
	 * @throws BadRequestException
	 * @throws ServerException
	 */
    public static function getscores(int $id, array $request=[]): ?array {
        $request['dropimageid']=$id;
        return scoreofdropimage::getByProperty('dropimageid', $id, $request);
    }

	/**
	 * @param int $id
	 * @return void
	 * @throws BadRequestException
	 * @throws ForbiddenException
	 * @throws NotFoundException
	 * @throws ServerException
	 */
    private static function carryForwardScores(int $id): void {
	    $dropImage=static::getById($id);
	    if(!$dropImage){ throw new NotFoundException('No drop image with ID '.$id); }
	    $wellDropId=$dropImage['welldropid'];
        welldrop::carryForwardScores($wellDropId);
    }
}