<?php class plate extends baseobject {

	protected static array $fields=array(
		'name'=>validator::REQUIRED,
		'description'=>validator::ANY,
		'platetypeid'=>array(validator::REQUIRED, validator::INTEGER),
		'ownerid'=>array(validator::REQUIRED, validator::INTEGER),
		'screenid'=>validator::INTEGER,
		'locationid'=>validator::INTEGER,
		'crystalscoringsystemid'=>array(validator::REQUIRED, validator::INTEGER),
		'offsetinscreenx'=>validator::INTEGER,
		'offsetinscreeny'=>validator::INTEGER,
		'firstinspectiontime'=>validator::DATETIME,
		'nextinspectiontime'=>validator::DATETIME,
		'finalinspectiontime'=>validator::DATETIME,
		'inspectionsremaining'=>validator::INTEGER,
		'hasconstructs'=>validator::BOOLEAN,
		'datedestroyed'=>validator::DATE,
		'bestprobability'=>validator::FLOAT,
		'bestcrystalscoreid'=>validator::INTEGER
	);

	protected static array $fieldLabels=array(
		'name'=>'Name/barcode',
		'description'=>'Description',
		'platetypeid'=>'Plate type',
		'ownerid'=>'Owner',
		'screenid'=>'Screen',
		'locationid'=>'Location',
		'crystalscoringsystemid'=>'Crystal scoring system',
		'offsetinscreenx'=>'X offset in screen',
		'offsetinscreeny'=>'Y offset in screen',
		'firstinspectiontime'=>'First inspection time',
		'nextinspectiontime'=>'Next inspection time',
		'finalinspectiontime'=>'Final inspection time',
		'inspectionsremaining'=>'Inspections remaining',
		'hasconstructs'=>'Has constructs',
		'datedestroyed'=>'Date destroyed',
		'bestprobability'=>'Best probability of crystals',
		'bestcrystalscoreid'=>'Best crystal score'
	);

	protected static array $helpTexts=array(
			'name'=>'A unique name for this plate, typically its barcode',
			'description'=>'A short description of this plate',
			'platetypeid'=>'The type of plate used',
			'ownerid'=>'The owner of the plate',
			'screenid'=>'The crystallization screen used',
			'locationid'=>'Where the plate is',
			'crystalscoringsystemid'=>'The scoring system used for this plate',
			'offsetinscreenx'=>'How many columns the plate is offset from column 1 in the screen (usually 0; left is positive)',
			'offsetinscreeny'=>'How many rows the plate is offset from row A in the screen (usually 0; down is positive)',
			'hasconstructs'=>'Whether at least one well of this plate has a protein construct defined',
	        'datedestroyed'=>'When the plate was destroyed'
	);

	protected static string $adminSelect='SELECT SQL_CALC_FOUND_ROWS plate.*,project.name AS projectname, project.isarchived AS isarchived, 
			platetype.name AS platetypename, platetype.dropmapping as dropmapping, platetype.rows AS `rows`, platetype.cols AS cols, platetype.subs AS subs, user.fullname AS ownername,
			crystalscore.label as bestscorelabel, crystalscore.scoreindex as bestscoreindex, crystalscore.color as bestscorecolor
			FROM plate
			JOIN platetype ON platetype.id=plate.platetypeid
			JOIN project ON project.id=plate.projectid
			JOIN user ON user.id=plate.ownerid
			LEFT JOIN crystalscore ON plate.bestcrystalscoreid=crystalscore.id
			WHERE 1=1 ';
	protected static string $normalSelect='SELECT SQL_CALC_FOUND_ROWS plate.*,project.name AS projectname, project.isarchived AS isarchived, 
			platetype.name AS platetypename, platetype.dropmapping as dropmapping, platetype.rows AS `rows`, platetype.cols AS cols, platetype.subs AS subs, user.fullname AS ownername,
			crystalscore.label as bestscorelabel, crystalscore.scoreindex as bestscoreindex, crystalscore.color as bestscorecolor
			FROM plate
			JOIN platetype ON platetype.id=plate.platetypeid
			JOIN project ON project.id=plate.projectid
			JOIN user ON user.id=plate.ownerid
			LEFT JOIN crystalscore ON plate.bestcrystalscoreid=crystalscore.id
			WHERE 1=1 ';
	
	
    protected static string $searchClause='CONCAT(plate.name,": ",plate.description)';

	protected static function getProjectClause(string $accessType, bool $forceSharedProject=false): string {
		if('readOne'===$accessType){
			$ownPlatesClause='plate.ownerid='.basesession::getUserId();
		} else if('read'===$accessType){
			$ownPlatesClause='plate.ownerid='.basesession::getUserId();
			if(!session::getShowArchivedProjects()){
				$ownPlatesClause.=' AND project.isarchived=0';
			}
		} else {
			$ownPlatesClause='plate.ownerid='.basesession::getUserId().' AND project.isarchived=0';
		}
		if(!('read'===$accessType || (!basesession::getShowArchivedProjects() && 'readOne'===$accessType))){
			$ownPlatesClause.=' AND project.isarchived=0';
		}
		return ' AND ((1=1 '.database::getProjectClause($accessType,$forceSharedProject).') OR ('.$ownPlatesClause.'))';
	}


    /**
     * @param int $id
     * @return array|mixed
     * @throws BadRequestException
	 * @throws ServerException
     */
	public static function getById(int $id): ?array {
        $plate=parent::getById($id);
		if(!$plate){ return $plate; }
		return static::addImagingStatusByDropPositionNumber($plate);
    }

    /**
     * @param string $name
     * @return array|null
     * @throws BadRequestException
	 * @throws ServerException
     */
    public static function getByName(string $name): ?array {
        $plate=parent::getByName($name);
        if(!$plate){ return $plate; }
		return static::addImagingStatusByDropPositionNumber($plate);
    }

    public static function getByProperty(string $key, string $value, array $request=[]): ?array {
        $plates=parent::getByProperty($key, $value, $request);
        if(!$plates || 1!=$plates['total']){
            return $plates;
        }
        $plates['rows'][0]=static::addImagingStatusByDropPositionNumber($plates['rows'][0]);
        return $plates;
    }

	/**
	 * @param array $plate
	 * @return array
	 * @throws BadRequestException
	 * @throws ServerException
	 */
    private static function addImagingStatusByDropPositionNumber(array $plate): array {
        $numDrops=1*$plate['subs'];
        $plate['isimaged']=false;
        $plate['isimagedbydropposition']=array();
        for($i=1;$i<=$numDrops;$i++){
            $image=database::queryGetOne('SELECT dropimage.id AS id FROM plate 
                JOIN platewell ON plate.id = platewell.plateid
                JOIN welldrop ON platewell.id = welldrop.platewellid
                JOIN dropimage ON welldrop.id = dropimage.welldropid
                WHERE plate.id=:plateid AND welldrop.dropnumber=:dropnumber
            ',array(
                ':plateid'=>$plate['id'],
                ':dropnumber'=>$i
            ));
            $isImaged=(null!==$image);
            $plate['isimagedbydropposition'][]=array(
              'drop'=>$i,
              'isimaged'=>1*$isImaged
            );
            if($isImaged){
                $plate['isimaged']=true;
            }
        }
        $plate['isimaged']=1*$plate['isimaged'];
	    return $plate;
    }


    /**
     * Creates a plate.
     * @param array $request The details of the plate.
     * @return array
     * @throws BadRequestException
     * @throws ForbiddenException
     * @throws NotFoundException
     * @throws ServerException
	 */
	public static function create(array $request=[]): array {
		$ret=parent::createByClassName($request,'plate');
		if(!$ret){ throw new ServerException('Could not create plate'); }
		$plate=$ret['created'];
		$plateId=$plate['id'];
		$plateType=platetype::getById($plate['platetypeid']);
		if(!$plateType){ throw new ServerException('Could not find plate type with ID '.$plate['platetypeid']); }
		$numRows=$plateType['rows'];
		$numCols=$plateType['cols'];
		$numDrops=$plateType['subs'];
		$well=array(
			'plateid'=>$plateId,
			'projectid'=>$request['projectid'],
			'row'=>1,
			'col'=>1,
			'numdrops'=>$numDrops
		);
		for($r=1;$r<=$numRows;$r++){
			for($c=1;$c<=$numCols;$c++){
				$well['row']=$r;
				$well['col']=$c;
				platewell::create($well);
			}
		}
		$baseObject=baseobject::getById($plateId);
		$createTime=$baseObject['createtime'];
        database::query(
            'UPDATE plate SET createtime=:createtime WHERE id=:id',
            array(':createtime'=>$createTime, ':id'=>$plateId)
        );
		$plate['createtime']=$createTime;
		return $ret;
	}

    /**
     * Updates the plate.
     * @param int $id The plate ID
     * @param array $request The request parameters
     * @return array
     * @throws BadRequestException
     * @throws ForbiddenException
     * @throws NotFoundException
     * @throws NotModifiedException
     * @throws ServerException
     */
	public static function update(int $id, array $request=[]): array {
		$plate=static::getById($id);

		if(isset($request['datedestroyed'])){
		    return static::updateDestroyed($id, $request['datedestroyed']);
		}

		$dropIdsToUpdate=array();
		$plateType=platetype::getById($plate['platetypeid']);
		$rows=(int)($plateType['rows']);
		$cols=(int)($plateType['cols']);
		
		//Set drop number to update - default to -1 (all drops)
		$dropNumber= $request['selectionDrop'] ?? -1;
		//Set the range of wells to update - defaults are -1 to 10000, which then narrows to smaller of (selection) or (entire plate)
		$request['selectionTop']= $request['selectionTop'] ?? -1;
		$request['selectionLeft']= $request['selectionLeft'] ?? -1;
		$request['selectionBottom']= $request['selectionBottom'] ?? 10000;
		$request['selectionRight']= $request['selectionRight'] ?? 10000;
		$selectionTop=max( (int)($request['selectionTop']), 1);
		$selectionLeft=max( (int)($request['selectionLeft']), 1);
		$selectionBottom=min( (int)($request['selectionBottom']), $rows);
		$selectionRight=min( (int)($request['selectionRight']), $cols);
		$drops=static::getwelldrops($id);
		if(!empty($drops)){
			foreach($drops['rows'] as $d){
				if($d['row']>=$selectionTop && $d['row']<=$selectionBottom && $d['col']>=$selectionLeft && $d['col']<=$selectionRight){
					if(-1==$dropNumber || ($dropNumber==$d['dropnumber'])){
						$dropIdsToUpdate[]=$d['id'];
					}
				}
			}
		}

		if(isset($request['constructid'])){
			if(empty($dropIdsToUpdate)){
				throw new BadRequestException('No drops to update');
			}
			$constructId=$request['constructid'];
			$construct=construct::getById($constructId);
			if($plate['projectname']!= baseproject::DEFAULTPROJECT && $construct['projectid'] != $plate['projectid']){
				throw new BadRequestException('You cannot use proteins from projects other than the plate project');
			}
			$wasAdmin=session::isAdmin();
			session::set('isAdmin', true);
			if($plate['projectname']== baseproject::DEFAULTPROJECT){
				static::setProjectId($id, $construct['projectid']);
			}
			$params=array(':constructid'=>$constructId, 'projectid'=>$construct['projectid']);
			database::query('UPDATE welldrop SET projectid=:projectid, constructid=:constructid WHERE id IN('. implode(',',$dropIdsToUpdate) .')', $params);
			session::set('isAdmin', $wasAdmin);
			unset($request['constructid']);
			$request['hasconstructs']=true;
		}
		$solutionFields=array(
				'proteinconcentrationamount','proteinconcentrationunit','proteinbuffer',
				'proteinsolutionamount','proteinsolutionunit','wellsolutionamount','wellsolutionunit'
		);
		$solutionFieldsToUpdate=array();
		foreach($solutionFields as $s){
			if(isset($request[$s])){
                if(strpos($s, 'unit')>0 && ""==trim($request[$s])){
                    throw new BadRequestException('Unit field cannot be empty');
                } else if(strpos($s, 'amount')>0 && ""==trim($request[$s])){
                    $solutionFieldsToUpdate[$s]=database::$nullValue;
                } else {
                    $solutionFieldsToUpdate[$s]=$request[$s];
                }
				unset($request[$s]);
			}
		}
		$updatedDrops=array();
		if(!empty($solutionFieldsToUpdate)){
			foreach($dropIdsToUpdate as $dropId){
				$drop=welldrop::update($dropId,$solutionFieldsToUpdate);
				$updatedDrops[]=$drop['updated'];
			}
		}
		
		$request['_classname']='plate';
		$plate=parent::update($id, $request);
		$plate['updated']['updateddrops']=$updatedDrops;
		
		return $plate;
	}

    /**
     * Deletes the plate.
     * @param int $id The plate ID.
     * @return array
     * @throws BadRequestException
     * @throws ForbiddenException
     * @throws NotFoundException
     * @throws ServerException
     */
	public static function delete(int $id): array {
		if(!self::canDelete($id)){
			throw new ForbiddenException('Either this plate does not exist or you do not have permission to delete it');
		}
		$sqlStatement='DELETE FROM baseobject WHERE baseobject.id=:plateid';
		database::query($sqlStatement, array(':plateid'=>$id));
		return array('deleted'=>$id);
	}

	/**
	 * @param int $id
	 * @param array $request
	 * @return array|null
	 * @throws BadRequestException
	 * @throws NotFoundException
	 * @throws ServerException
	 * @noinspection PhpUnusedParameterInspection
	 */
	public static function getconstructs(int $id, array $request=[]): ?array {
		$plate=static::getById($id);
		if(!$plate){ throw new NotFoundException('Plate not found'); }
		$sqlStatement='SELECT welldrop.constructid 
				FROM platewell, welldrop, construct 
				WHERE welldrop.platewellid=platewell.id 
					AND welldrop.constructid=construct.id
					AND platewell.plateid=:plateid 
					AND welldrop.constructid IS NOT NULL ';
		$result=database::queryGetAll($sqlStatement, array(':plateid'=>$id));
		if(empty($result)){ throw new NotFoundException('No constructs found for plate'); }
		$constructIds=array_unique(array_column($result['rows'], 'constructid'));
		$ret=array(
				'total'=>count($constructIds), 
				'rows'=>array()
		);
		foreach($constructIds as $c){
			$ret['rows'][]=construct::getById($c);
		}	
		return $ret;
	}

	/**
	 * @param int $id
	 * @param array $request
	 * @return array|null
	 * @throws BadRequestException
	 * @throws ServerException
	 */
	public static function getplatewells(int $id, array $request=[]): ?array {
		$request['all']=1;
		$request['sortby']='name';
		return platewell::getByProperty('plateid', $id, $request);
	}

	/**
	 * Gets all the drops in the plate.
	 * @param int $id The plate ID.
	 * @param array $request
	 * @return array|null
	 * @throws BadRequestException
	 * @throws ServerException
	 */
	public static function getwelldrops(int $id, array $request=[]): ?array {
		$request['all']=1;
		$request['sortby']='name';
		$sqlStatement='SELECT platewell.row AS `row`, platewell.col AS `col`, welldrop.id AS id,
 				welldrop.dropnumber as dropnumber, welldrop.constructid AS constructid, 
 				welldrop.proteinconcentrationamount, welldrop.proteinconcentrationunit, welldrop.proteinbuffer,
				welldrop.proteinsolutionamount, welldrop.proteinsolutionunit, 
				welldrop.latestcrystalscoreid, welldrop.latestcrystalprobabilitypercent,
				welldrop.wellsolutionamount, welldrop.wellsolutionunit, project.id AS projectid
				FROM platewell, welldrop, project
				WHERE platewell.plateid=:plateid
				AND platewell.projectid=project.id
				AND welldrop.platewellid=platewell.id ';
		$params=array(':plateid'=>$id);
		if(isset($request['dropnumber']) && $request['dropnumber']>0){
			$sqlStatement.='AND welldrop.dropnumber=:dropnum ';
			$params[':dropnum']=$request['dropnumber'];
		}
		//NO - need this during setting of construct, where drops are in default project - user can't read them.
		//$sqlStatement.=database::getProjectClause('read');
		$sqlStatement.=database::getOrderClause($request,'welldrop');
		$sqlStatement.=database::getLimitClause($request);
		$drops=database::queryGetAll($sqlStatement, $params);
		foreach ($drops['rows'] as &$drop){
		    $image=database::queryGetOne(
		        'SELECT id FROM dropimage WHERE welldropid=:wd',
                array(':wd'=>$drop['id'])
            );
		    if($image) {
                $drop['isimaged']=1;
            } else {
                $drop['isimaged']=0;
            }
        }
		return $drops;
	}

	/**
	 * Gets the crystals for the plate.
	 * @param int $id The plate ID.
	 * @param array $request The request parameters, e.g., pagination.
	 * @return array|null
	 * @throws BadRequestException
	 * @throws NotFoundException
	 * @throws ServerException
	 */
	public static function getcrystals(int $id, array $request=[]): ?array {
		return crystal::getByProperty('plateid', $id, $request);
	}

	/**
	 * Gets the imaging sessions for the plate.
	 * @param int $id The plate ID.
	 * @param array $request The request parameters, e.g., pagination.
	 * @return array|null
	 * @throws BadRequestException
	 * @throws ServerException
	 */
	public static function getimagingsessions(int $id, array $request=[]): ?array {
		return imagingsession::getByProperty('plateid', $id, $request);
	}

	/**
	 * Sets the project ID for the plate, and for its child wells, drops, imaging sessions, images, crystals, and scores.
	 * @param int $plateId
	 * @param int $projectId
	 * @throws BadRequestException
	 * @throws NotFoundException
	 * @throws ServerException
	 */
	private static function setProjectId(int $plateId,int $projectId): void {
		$result1=database::queryGetAll('SELECT plate.id as pid, platewell.id AS wid, welldrop.id AS did 
				FROM plate 
				LEFT JOIN platewell ON platewell.plateid=plate.id 
				LEFT JOIN welldrop ON welldrop.platewellid=platewell.id 
				WHERE plate.id=:plateid', array(':plateid'=>$plateId) );
		$result2=database::queryGetAll('SELECT imagingsession.id AS isid, dropimage.id AS diid, scoreofdropimage.id AS sdid  
				FROM plate 
				LEFT JOIN imagingsession ON imagingsession.plateid=plate.id 
				LEFT JOIN dropimage ON dropimage.imagingsessionid=imagingsession.id 
				LEFT JOIN scoreofdropimage ON scoreofdropimage.dropimageid=dropimage.id 
				WHERE plate.id=:plateid', array(':plateid'=>$plateId) );
		$ids=array_filter(array_merge(
				array_unique(array_column($result1['rows'],'pid' )),
				array_unique(array_column($result1['rows'],'wid' )),
				array_unique(array_column($result1['rows'],'did' )),
				array_unique(array_column($result2['rows'],'isid' )),
				array_unique(array_column($result2['rows'],'diid' )),
				array_unique(array_column($result2['rows'],'sdid' ))
		));
		database::query(
				'UPDATE baseobject set projectid=:projectid WHERE id IN('.implode(',', $ids).')',
				array('projectid'=>$projectId) 
		);
		database::query('UPDATE plate
				LEFT JOIN platewell ON platewell.plateid=plate.id
				LEFT JOIN welldrop ON welldrop.platewellid=platewell.id
				LEFT JOIN crystal ON crystal.welldropid=welldrop.id
				SET plate.projectid=:projectid, platewell.projectid=:projectid, welldrop.projectid=:projectid, crystal.projectid=:projectid
				WHERE plate.id=:plateid', 
				array(':plateid'=>$plateId, 'projectid'=>$projectId) 
		);
		database::query('UPDATE plate
				LEFT JOIN imagingsession ON imagingsession.plateid=plate.id
				LEFT JOIN dropimage ON dropimage.imagingsessionid=imagingsession.id
				LEFT JOIN scoreofdropimage ON scoreofdropimage.dropimageid=dropimage.id
				SET imagingsession.projectid=:projectid, dropimage.projectid=:projectid, scoreofdropimage.projectid=:projectid
				WHERE plate.id=:plateid', 
				array(':plateid'=>$plateId, 'projectid'=>$projectId) 
		);
		plate::updateFileNoteAndRecordingProjectIds($plateId);
	}

    public static function updateFileNoteAndRecordingProjectIds(int $parentId): void {
	    parent::updateFileNoteAndRecordingProjectIds($parentId);
    }


    /**
     * Whether the current user can delete the specified plate.
     * @param int $id The ID of the plate.
     * @return bool true if the user can delete the plate, otherwise false.
     * @throws BadRequestException
	 * @throws ServerException
     */
	public static function canDelete(int $id): bool {
		if(parent::canDelete($id)){ return true; }
		$plate=plate::getById($id);
		if($plate['ownerid']==session::getUserId()){
			return true;
		}
		return false;
	}

	/**
	 * Gets the best scores for all drops in the plate.
	 * @param int $id The plate ID.
	 * @param array $request
	 * @return array|null
	 * @throws BadRequestException
	 * @throws ServerException
	 */
	public static function getdropbestscores(int $id, array $request=[]): ?array {
		$plate=static::getById($id);
		$scoringSystemId=$plate['crystalscoringsystemid'];
		$scores=crystalscoringsystem::getscores($scoringSystemId);
		$scores=$scores['rows'];
		foreach($scores as $s){
			$scores['s'.$s['id']]=$s;
		}
		
		$result=database::queryGetAll('SELECT wd.id, pw.row AS `row`, pw.col AS `col`, wd.dropnumber AS dropnumber, MAX(cs.scoreindex) AS bestscoreindex
				FROM welldrop AS wd
				JOIN platewell AS pw ON wd.platewellid=pw.id 
				LEFT JOIN dropimage AS di ON di.welldropid=wd.id 
				LEFT JOIN scoreofdropimage AS sdi ON sdi.dropimageid=di.id 
				LEFT JOIN crystalscore AS cs ON sdi.crystalscoreid=cs.id
				WHERE pw.plateid=:id
				GROUP BY wd.id ORDER BY bestscoreindex DESC
				', array(':id'=>$id)
		);
		foreach($result['rows'] as &$row){
		    if(!empty($row['bestscoreindex'])){
		        $score=$scores[$row['bestscoreindex']];
				$row['bestscorecolor']=$score['color'];
				$row['bestscorelabel']=$score['label'];
			}
		}
		
		return $result;
	}

    /**
     * Mark the plate as destroyed, or unmark a previously marked plate.
     * @param int $plateId The plate ID.
     * @param string $dateDestroyed The date destroyed, in YYYY-MM-DD format.
     * @return array
     * @throws BadRequestException
     * @throws ForbiddenException
     * @throws NotFoundException
     * @throws NotModifiedException
     * @throws ServerException
     */
	private static function updateDestroyed(int $plateId, string $dateDestroyed): array {
	    if(!static::canUpdateDestroyed($plateId)){
	        throw new ForbiddenException('Either the plate does not exist, or you do not have permission to update its status.');
	    }
	    $wasAdmin=session::isAdmin();
	    session::set('isAdmin',true);
	    if(""==$dateDestroyed || "0000-00-00"==$dateDestroyed){
	        $dateDestroyed=database::$nullValue;
	    } else {
	        $result=database::queryGetOne(
	            'SELECT datedestroyed FROM plate WHERE id=:id',
	            array(':id'=>$plateId)
	        );
	        if(!$result){
	            throw new NotFoundException('Either the plate does not exist, or you do not have permission to see it.'); 
	        }
	        if(!empty($result['datedestroyed'])){
	            //Plate already destroyed
	            throw new NotModifiedException();
	        }
	    }
	    
	    parent::update($plateId, array(
	        'datedestroyed'=>$dateDestroyed,
	        '_classname'=>'plate'
	    ));
	    if(!empty($dateDestroyed)){
	        //@TODO add place ("Destroyed") to request - if doesn't exist, create it in Shared, isAdmin=true
	        //add "destroyed" note
	        note::create(array(
	            'parentid'=>$plateId,
	            'text'=>'Plate destroyed'
	        ));
	    } else {
	        //@TODO add place (null) to request - pulled it from the trash but don't know where it is
	        //add "un-destroyed" note
	        note::create(array(
	            'parentid'=>$plateId,
	            'text'=>'Plate destruction undone'
	        ));
	    }
	    session::set('isAdmin',$wasAdmin);
	    return array('updated'=>plate::getById($plateId));
	}

    /**
     * Whether the current user can update the "destroyed" status of the current plate.
     * Intent: Anyone in the Administrators, Technicians, or Plate Destroyers groups can destroy any
     *         plate. Others can destroy only their own plates.
     * @param int $plateId The ID of the plate to destroy.
     * @return boolean true if the current user can destroy/undestroy the plate, otherwise false.
     * @throws BadRequestException
     * @throws NotFoundException
     * @throws ServerException
     */
	private static function canUpdateDestroyed(int $plateId): bool {
	    if(session::isAdmin() || session ::isTechnician() || session::isPlateDestroyer()){ return true; }
	    $result=database::queryGetOne(
	           'SELECT ownerid FROM plate WHERE id=:id',
	           array(':id'=>$plateId)
	    );
	    if(!$result){ return false; }
	    return ($result['ownerid']==session::getUserId());
	}

    /**
     * Looks up the plate by name and calls updateDestroyed on it.
     * @param string $name The plate name or barcode.
     * @param array $request The request. Only one parameter, "datedestroyed", is recognised:
     *          - if the parameter is not set, today's date will be set
     *          - if a date in YYYY-MM-DD format, that date will be set
     *          - if an empty string, the plate will be un-destroyed
     * @return array
     * @throws BadRequestException
     * @throws ForbiddenException
     * @throws NotFoundException if no plate with that name is found.
     * @throws NotModifiedException
     * @throws ServerException
     * @noinspection PhpUnused
     */
	public static function destroyByName(string $name, array $request=[]): array {
        $dateDestroyed=gmdate('Y-m-d');
	    if(isset($request['datedestroyed'])){
	        $dateDestroyed=$request['datedestroyed'];
	    }
	    $wasAdmin=session::isAdmin();
	    session::set('isAdmin',true);
	    $plate=plate::getByName($name);
	    session::set('isAdmin',$wasAdmin);
	    if(!$plate){
	        throw new NotFoundException('No plate with name '.$name);
	    }
	    return plate::updateDestroyed($plate['id'], $dateDestroyed);
	}

	/**
	 * @param int $plateId
	 * @return void
	 * @throws BadRequestException
	 * @throws ForbiddenException
	 * @throws NotFoundException
	 * @throws NotModifiedException
	 * @throws ServerException
	 */
    public static function calculateBestScores(int $plateId): void {
        $plateBestScoreId=database::$nullValue;
        $plateBestProbabilityPercent=database::$nullValue;

        $wasAdmin=session::isAdmin();
        if(!$wasAdmin){ session::becomeAdmin(); } //plate might be in default project

        $plate=static::getById($plateId);
        if(!$plate){ throw new NotFoundException('No plate found with ID '.$plateId); }

        //Get the possible scores for the plate's scoring system, and sort best-first
        $scores=crystalscoringsystem::getscores($plate['crystalscoringsystemid']);
        if(!$scores){ return; }
        $scores=$scores['rows'];
        usort($scores, function ($a, $b){
            if($a==$b) { return 0; }
            return (1*$a['scoreindex']>1*$b['scoreindex']) ? -1 : 1;
        });
        //Get the distinct scores for all the drops in the plate
        $drops=plate::getwelldrops($plate['id'])['rows'];
        $dropScores=array_unique(array_column($drops, 'latestcrystalscoreid'));

        //Best score first, see if each score exists on the plate. If yes, this is the plate best score, so stop.
        foreach($scores as $score){
            if(in_array($score['id'], $dropScores)){
                $plateBestScoreId=$score['id'];
                break;
            }
        }

        //Get the crystal probabilities, sort descending, choose the first. If none/empty, set null.
        $dropProbabilities=array_column($drops,'latestcrystalprobabilitypercent');
        if(!empty($dropProbabilities)){
            rsort($dropProbabilities, SORT_NUMERIC);
            $plateBestProbabilityPercent=$dropProbabilities[0];
        }
        if(!$plateBestProbabilityPercent){ $plateBestProbabilityPercent=database::$nullValue; }

        //update the plate's best score and highest probability
        plate::update($plateId, array(
            'bestprobability'=>$plateBestProbabilityPercent,
            'bestcrystalscoreid'=>$plateBestScoreId
        ));
        if(!$wasAdmin){ session::revokeAdmin(); }
    }

}