<?php class crystal extends baseobject { 
	
	protected static array $fields=array(
            'name'=>validator::REQUIRED,
            'prefix'=>validator::REQUIRED,
            'suffix'=>validator::ANY,
            'welldropid'=>array(validator::REQUIRED, validator::INTEGER),
            'dropimageid'=>array(validator::REQUIRED, validator::INTEGER),
            'numberindrop'=>array(validator::REQUIRED, validator::INTEGER),
            'pixelx'=>validator::INTEGER,
            'pixely'=>validator::INTEGER,
            'isdummycoordinate'=>validator::BOOLEAN,
            'isfished'=>validator::BOOLEAN,
            'starrating'=>validator::INTEGER,
	        'spacegroup'=>validator::ANY,
    	    'unitcella'=>validator::FLOAT,
    	    'unitcellb'=>validator::FLOAT,
    	    'unitcellc'=>validator::FLOAT,
    	    'unitcellalpha'=>validator::FLOAT,
    	    'unitcellbeta'=>validator::FLOAT,
    	    'unitcellgamma'=>validator::FLOAT,
	);

	protected static array $helpTexts=array(
			'name'=>'A unique name for this crystal',
			'welldropid'=>'The drop where this crystal grew',
			'dropimageid'=>'The image on which the crystal was marked',
			'numberindrop'=>'The scoring system used for this plate',
            'pixelx'=>'Location of the crosshair in pixels from left of image',
            'pixely'=>'Location of the crosshair in pixels from top of image',
            'isdummycoordinate'=>'If true, location was auto-generated, not picked by user',
            'isfished'=>'Whether the crystal has been fished onto a pin for data collection',
	        'starrating'=>'A subjective score of this crystal\'s importance',
	        'spacegroup'=>'The crystal space group',
    	    'unitcella'=>'The crystal\'s unit cell a dimension',
    	    'unitcellb'=>'The crystal\'s unit cell b dimension',
    	    'unitcellc'=>'The crystal\'s unit cell c dimension',
    	    'unitcellalpha'=>'The crystal\'s unit cell &alpha; dimension',
    	    'unitcellbeta'=>'The crystal\'s unit cell &beta; dimension',
    	    'unitcellgamma'=>'The crystal\'s unit cell &gamma; dimension',
	);

	public static string $defaultSortOrder='welldropid ASC, numberindrop ASC';

    protected static string $adminSelect= 'SELECT SQL_CALC_FOUND_ROWS crystal.*,
                project.name AS projectname, project.isarchived AS isarchived, project.owner as projectownerid,
				construct.name AS constructname, protein.name AS proteinname, protein.proteinacronym AS proteinacronym,
                welldrop.name AS welldropname, welldrop.dropnumber AS dropnumber,
                platewell.id AS platewellid, platewell.name AS platewellname, 
                platewell.row AS `row`, platewell.col AS `col`,
                plate.id AS plateid, plate.name AS platename,
                dropimage.imagingsessionid AS imagingsessionid
			FROM crystal 
            JOIN dropimage ON dropimage.id=crystal.dropimageid 
            JOIN project ON project.id=crystal.projectid 
            JOIN welldrop ON welldrop.id=crystal.welldropid 
            JOIN platewell ON platewell.id=welldrop.platewellid 
            JOIN plate ON plate.id=platewell.plateid 
			LEFT JOIN construct ON construct.id=welldrop.constructid 
			LEFT JOIN protein ON protein.id=construct.proteinid
            WHERE 1=1 
			 ';
    protected static string $normalSelect= 'SELECT SQL_CALC_FOUND_ROWS crystal.*,
                project.name AS projectname, project.isarchived AS isarchived, project.owner as projectownerid,
				construct.name AS constructname, protein.name AS proteinname, protein.proteinacronym AS proteinacronym,
                welldrop.name AS welldropname, welldrop.dropnumber AS dropnumber,
                platewell.id AS platewellid, platewell.name AS platewellname, 
                platewell.row AS `row`, platewell.col AS `col`,
                plate.id AS plateid, plate.name AS platename,
                dropimage.imagingsessionid AS imagingsessionid
			FROM crystal 
            JOIN dropimage ON dropimage.id=crystal.dropimageid 
            JOIN project ON project.id=crystal.projectid 
            JOIN welldrop ON welldrop.id=crystal.welldropid 
            JOIN platewell ON platewell.id=welldrop.platewellid 
            JOIN plate ON plate.id=platewell.plateid 
			LEFT JOIN construct ON construct.id=welldrop.constructid 
			LEFT JOIN protein ON protein.id=construct.proteinid
            WHERE 1=1 
			 ';

	/**
	 * @param string $key
	 * @param string $value
	 * @param array $request
	 * @return array|null
	 * @throws BadRequestException
	 * @throws ServerException
	 */
	public static function getByProperty(string $key, string $value, array $request=[]): ?array {

		if('plateid'==$key){
			$sqlStatement='SELECT crystal.*, project.name,
				platewell.row AS `row`, platewell.col AS `col`, welldrop.dropnumber AS dropnumber, dropimage.imagingsessionid AS imagingsessionid
				FROM crystal,platewell,welldrop,dropimage,project
				WHERE platewell.plateid=:id AND crystal.welldropid=welldrop.id AND welldrop.platewellid=platewell.id 
					AND project.id=platewell.projectid 
					AND dropimage.id=crystal.dropimageid'.
					database::getProjectClause('read')
//					.' ORDER BY `row`, `col`, `dropnumber`, crystal.numberindrop
                    .database::getOrderClause($request,'crystal');
			$params=array(':id'=>$value);
			$sqlStatement.=database::getLimitClause($request);
			
			return database::queryGetAll($sqlStatement, $params);
		}
		return parent::getByProperty($key, $value, $request);
	}

    /**
     * @param array $request
     * @return array
     * @throws BadRequestException
     * @throws ForbiddenException
     * @throws NotFoundException
     * @throws ServerException
     */
	public static function create(array $request=[]): array {
		if(!isset($request['welldropid'])){
			return parent::create($request); //will fail validation and throw correct exception
		}
		$wellDrop=welldrop::getbyid($request['welldropid']);
		$plateWell=platewell::getbyid($wellDrop['platewellid']);
		$plate=plate::getById($plateWell['plateid']);
		$dropImage=dropimage::getById($request['dropimageid']);
		if(!$dropImage){
			$images=welldrop::gettimecourseimages($request['welldropid']);
			if($images){
				$images=array_reverse($images['rows']);
				
				//first off, is there a visible one?
				foreach ($images as $i){
					if('Visible'==$i['lighttype']){
						$dropImage=$i;
						break;
					}
				}
				//If no visible images, use the first one - SONICC, UV, whatever...
				if(!$dropImage){
					$dropImage=$images[0];
				}
			}
		}
		if(!$dropImage){
		    if(!isset($request['allowcreatedummyinspection'])){
    			throw new BadRequestException('Cannot create crystal because no image was found for this drop');
		    } else {
		        imagingsession::createWithDummyImages($plate['id']);
		        $images=welldrop::gettimecourseimages($request['welldropid']);
		        if(empty($images)){
		            throw new BadRequestException('Cannot create crystal because no image was found for this drop. A dummy imaging session could not be created.');
		        }
		        $dropImage=$images['rows'][0];
		    }
		}
		$request['dropimageid']=$dropImage['id'];
		$rowLabels=array('','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','Q','X','Y','Z');
		$row=$rowLabels[$plateWell['row']];
		$col=$plateWell['col'];
		if(strlen($col)<2){ $col='0'.$col; }
		$wellLabel=$row.$col;
		$request['name']=$plate['name'].$wellLabel.'d'.$wellDrop['dropnumber'].'c'.$request['numberindrop'];
		if(!empty($wellDrop['constructid'])){
			$construct=construct::getbyid($wellDrop['constructid']);
			$request['name']=$construct['proteinacronym'].'_'.$request['name'];
		}
		$request['prefix']=$request['name'];
		$created=parent::create($request);
		$diffractionRequest=diffractionrequest::create(array(
		    'crystalid'=>$created['created']['id']
		));
		$created['created']['diffractionrequests']=array();
		$created['created']['diffractionrequests'][]=$diffractionRequest['created'];

        $crystalScore=crystalscoringsystem::getScoreBestMatchingName($plate['crystalscoringsystemid'],'crystals');
        if(!empty($crystalScore)){
            $scoreId=$crystalScore['id'];
            dropimage::update($dropImage['id'], array(
                'latestcrystalscoreid'=>$scoreId,
                'scoringservicename'=>scoreofdropimage::SCORING_SERVICE_NAME_ICEBEAR,
                'scoringenginename'=>scoreofdropimage::SCORING_ENGINE_CRYSTAL_MARKED
            ));
            $created['scoreId']=$scoreId;
        }

		return $created;
	}

	/**
	 * @param int $id
	 * @param array $request
	 * @return array
	 * @throws BadRequestException
	 * @throws ForbiddenException
	 * @throws NotFoundException
	 * @throws ServerException
	 */
	public static function update(int $id, array $request=[]): array {
		$crystal=static::getById($id);
		if(isset($request['prefix']) || isset($request['suffix'])){
			$newPrefix=$crystal['prefix'];
			$newSuffix=$crystal['suffix'];
			if(isset($request['prefix'])){ $newPrefix=trim($request['prefix']); }
			if(isset($request['suffix'])){ $newSuffix=trim($request['suffix']); }
			if(empty($newSuffix)){
				$request['name']=$newPrefix;
			} else {
				$request['name']=$newPrefix.'_'.$newSuffix;
			}
			if(45<strlen($request['name'])){
				throw new BadRequestException('Name was '.strlen($request['name']).' characters long, but cannot be more than 45 characters (limit imposed by synchrotron ISPyB system).');
			}
		}
		if(isset($request['crystalidatremotefacility']) && isset($request['shipmentid'])){
		    //Update the remote ID on an associated diffractionrequest.
		    //This will be one with shipmentid either NULL or the specified shipment ID; either way, shipment ID will be set to the specified one after this.
		    //Numbers are low so this inefficient approach of iterating twice is OK.
            $wasAdmin=session::isAdmin();
            $diffractionRequests=static::getdiffractionrequests($id);
            $dr=false;
            foreach($diffractionRequests as $d){
                if(empty($d['shipmentid'])){
                    $dr=$d;
                    break;
                }
            }
            foreach($diffractionRequests as $d){
                if($d['shipmentid']==$request['shipmentid']){
                    $dr=$d;
                    break;
                }
            }
            if($dr){
                diffractionrequest::update($dr['id'], array(
                    'crystalidatremotefacility'=>$request['crystalidatremotefacility'],
                    'shipmentid'=>$request['shipmentid']
                ));
            }
            session::set('isAdmin', $wasAdmin);
		}
		return parent::update($id, $request);
	}

	/**
	 * @param int $id
	 * @return array
	 * @throws BadRequestException
	 * @throws ForbiddenException
	 * @throws NotFoundException
	 * @throws ServerException
	 */
	public static function delete(int $id): array {
	    $crystal=static::getById($id);
	    if(!$crystal){ throw new NotFoundException('No crystal with ID '.$id); }
	    if(1==$crystal['isfished']){ throw new ForbiddenException('Crystal cannot be deleted because it has been fished.'); }
        return parent::delete($id);	    
	}

	/**
	 * Get ALL datasets for the crystal.
	 * @param int $id
	 * @return array|null
	 * @throws BadRequestException
	 * @throws ServerException
	 */
	private static function getdatasets(int $id): ?array {
	    return dataset::getByProperty('crystalid', $id, array('all'=>1));
    }

	/**
	 * @param int $id
	 * @param array $request
	 * @return array|null
	 * @throws BadRequestException
	 * @throws ForbiddenException
	 * @throws NotFoundException
	 * @throws ServerException
	 */
	public static function getdiffractionrequests(int $id, array $request=[]): ?array {
	    $ret=diffractionrequest::getByProperty('crystalid', $id, $request);
	    if(!$ret){
	        //Crystal existed before we did things this way.Create an empty shipping request
	        $created=diffractionrequest::create(array(
	            'crystalid'=>$id,
	            'diffractiontype'=>'OSC' //Doing this in code, not as a database default, in case we need to handle non-ISPyB synchrotrons in future
	        ));
	        $rows=array();
	        $rows[]=$created['created'];
	        $ret=array('total'=>1, 'rows'=>$rows);
	        $GLOBALS['forceCommit']=true;
	    }
	    return $ret;
	}

	/**
	 * @param int $id
	 * @param array $request
	 * @return array|null
	 * @throws BadRequestException
	 * @throws NotFoundException
	 * @throws ServerException
	 */
    public static function getpdbdepositions(int $id, array $request=[]): ?array {
        if(!static::getById($id)){
            throw new NotFoundException('That crystal does not exist, or you do not have permission to see it');
        }
        return database::queryGetAll(
            'SELECT j.id AS pdbdepositioncrystalid, d.* 
                FROM project, pdbdeposition AS d, pdbdepositioncrystal AS j 
                WHERE j.crystalid=:id AND j.pdbdepositionid=d.id AND project.id=j.projectid '
                .database::getFilterClause($request)
                .database::getProjectClause('read')
                .database::getOrderClause($request,'pdbdeposition')
                .database::getLimitClause($request),
            array(':id'=>$id)
        );
    }


	/**
	 * @param int $id
	 * @return bool
	 * @throws BadRequestException
	 * @throws NotFoundException
	 * @throws ServerException
	 */
    public static function canUpdate(int $id): bool {
        return parent::canUpdate($id) || session::isShipper();
    }

	/**
	 * @param int $parentId
	 * @throws BadRequestException
	 * @throws ForbiddenException
	 * @throws NotFoundException
	 * @throws ServerException
	 */
    public static function updateFileNoteAndRecordingProjectIds(int $parentId): void {
        //set for the crystal
        parent::updateFileNoteAndRecordingProjectIds($parentId);
        //Then set for all the diffractionrequests
        $diffractionRequests=crystal::getdiffractionrequests($parentId, array('all'=>1));
        if($diffractionRequests && isset($diffractionRequests['rows'])){
            foreach ($diffractionRequests['rows'] as $diffractionRequest){
                baseobject::updateFileNoteAndRecordingProjectIds($diffractionRequest['id']);
            }
        }
        //Then set for all the datasets
        $datasets=crystal::getdatasets($parentId);
        if($datasets && isset($datasets['rows'])){
            foreach ($datasets['rows'] as $dataset){
                baseobject::updateFileNoteAndRecordingProjectIds($dataset['id']);
            }
        }
    }


}