<?php class welldrop extends baseobject { 
	
	protected static array $fields=array(
			'name'=>validator::REQUIRED,
			'platewellid'=>array(validator::REQUIRED, validator::INTEGER),
			'constructid'=>validator::INTEGER,
			'dropnumber'=>array(validator::REQUIRED, validator::INTEGER),
			'proteinconcentrationamount'=>validator::FLOAT,
			'proteinconcentrationunit'=>validator::ANY,
			'proteinbuffer'=>validator::ANY,
			'proteinsolutionamount'=>validator::FLOAT,
			'proteinsolutionunit'=>validator::ANY,
			'wellsolutionamount'=>validator::FLOAT,
			'wellsolutionunit'=>validator::ANY,
            'latestcrystalscoreid'=>validator::INTEGER,
            'latestcrystalprobabilitypercent'=>validator::FLOAT
	);
	
	protected static array $helpTexts=array(
			'name'=>'A unique name for this drop',
			'platewellid'=>'The parent well',
			'constructid'=>'The protein construct in this drop',
			'dropnumber'=>'Number of the drop within the well',
            'latestcrystalscoreid'=>'The most recent score on the most recent image',
            'latestcrystalprobabilitypercent'=>'The most recent probability on the most recent image'
	);
		
	protected static string $adminSelect='SELECT SQL_CALC_FOUND_ROWS welldrop.*, construct.name AS constructname, protein.id AS proteinid, protein.name AS proteinname
			FROM welldrop LEFT JOIN construct ON construct.id=welldrop.constructid LEFT JOIN protein ON protein.id=construct.proteinid
			LEFT JOIN project ON project.id=welldrop.projectid
			WHERE 1=1';
	protected static string $normalSelect='SELECT SQL_CALC_FOUND_ROWS welldrop.*, construct.name AS constructname, protein.id AS proteinid, protein.name AS proteinname
			FROM welldrop LEFT JOIN construct ON construct.id=welldrop.constructid LEFT JOIN protein ON protein.id=construct.proteinid 
			LEFT JOIN project ON project.id=welldrop.projectid
			WHERE 1=1';

	/**
	 * @param int $id The ID of the well drop.
	 * @param array $request The request parameters, e.g., pagination.
	 * @return array|null
	 * @throws BadRequestException
	 * @throws ServerException
	 */
	public static function gettimecourseimages(int $id, array $request=[]): ?array {
        return database::queryGetAll(dropimage::getSelectClause().' AND welldropid=:wd
                ORDER BY imagingsession.imageddatetime ASC',
                array(':wd'=>$id));
	}

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

	/**
	 * @param int $plateId
	 * @param int $rowNumber
	 * @param int $colNumber
	 * @param int $dropNumber
	 * @return array|null
	 * @throws BadRequestException
	 * @throws ServerException
	 */
	public static function getByPlateIdAndRowColumnDrop(int $plateId, int $rowNumber, int $colNumber, int $dropNumber): ?array {
        return database::queryGetOne('
	        SELECT welldrop.*
	        FROM welldrop, platewell 
            WHERE platewell.id=welldrop.platewellid
            AND platewell.row=:row AND platewell.col=:col
            AND platewell.plateid=:plateid AND welldrop.dropnumber=:drop
	    ',array(
            ':plateid'=>$plateId,
            ':row'=>$rowNumber,
            ':col'=>$colNumber,
            ':drop'=>$dropNumber
        ));
    }

	/**
	 * @param int $id
	 * @return void
	 * @throws BadRequestException
	 * @throws ForbiddenException
	 * @throws NotFoundException
	 * @throws ServerException
	 */
    public static function carryForwardScores(int $id): void {
        $wellDrop=welldrop::getById($id);
        if(!$wellDrop){
            throw new NotFoundException('No well drop with ID '.$id);
        }
        $images=welldrop::gettimecourseimages($id);
        if(empty($images) || !isset($images['rows'])){
            return;
        }
        $currentScore=-1;
        foreach($images["rows"] as $image){
            if(!empty($image['latestcrystalscoreid'])){
                $currentScore=$image['latestcrystalscoreid'];
            } else if(-1!==$currentScore){
                dropimage::update($image['id'], array(
                    'latestcrystalscoreid'=>$currentScore,
                    'scoringservicename'=>scoreofdropimage::SCORING_SERVICE_NAME_ICEBEAR,
                    'scoringenginename'=>scoreofdropimage::SCORING_ENGINE_CARRY_FORWARD
                ));
            }
        }
    }

    /**
     * Sets the latest score ID and crystal probability percent for this welldrop, based on the latest values
     * for its dropimages. Checks the most recent dropimage first, then works backward.
     * @throws NotFoundException
     * @throws BadRequestException
     * @throws ForbiddenException
     * @throws ServerException
     */
    public static function updateLatestScoreIdAndCrystalProbability(int $id): array|bool {
        $ret=false;
        $wasAdmin=session::isAdmin();
        if(!$wasAdmin){ session::becomeAdmin(); } //Plate might be in default project
        $wellDrop=welldrop::getById($id);
        if(!$wellDrop){
            throw new NotFoundException('No well drop with ID '.$id);
        }
        $images=static::gettimecourseimages($id);
        if($images){
            $latestScoreId=database::$nullValue;
            $latestProbability=database::$nullValue;
            $images=array_reverse($images['rows']); //latest first
            foreach($images as $image){
                if($latestScoreId===database::$nullValue && !empty($image['latestcrystalscoreid'])){
                    $latestScoreId=$image['latestcrystalscoreid'];
                }
                if($latestProbability===database::$nullValue && !empty($image['latestcrystalprobabilitypercent'])){
                    $latestProbability=$image['latestcrystalprobabilitypercent'];
                }
            }
            $ret=static::update($id, array(
                'latestcrystalscoreid'=>$latestScoreId,
                'latestcrystalprobabilitypercent'=>$latestProbability
            ));
        }
        if(!$wasAdmin){ session::revokeAdmin(); }
        return $ret;
    }

    /**
     * You should probably call updateLatestScoreIdAndCrystalProbability instead. This is intended for database updates/
     * datafixes where there are no probability percents, because eliminating those checks makes this much more performant.
     * Sets the latest score ID for this welldrop, based on the latest values
     * for its dropimages. Checks the most recent dropimage first, then works backward.
     * @throws NotFoundException
     * @throws BadRequestException
     * @throws ForbiddenException
     * @throws ServerException
     */
    public static function updateLatestScoreIdOnly(int $id): array|bool {
        $wasAdmin=session::isAdmin();
        if(!$wasAdmin){ session::becomeAdmin(); }
        $wellDrop=welldrop::getById($id);
        if(!$wellDrop){
            throw new NotFoundException('No well drop with ID '.$id);
        }
        $images=static::gettimecourseimages($id);
        if($images){
            $latestScoreId=database::$nullValue;
            $images=array_reverse($images['rows']);
            foreach($images as $image){
                if($latestScoreId===database::$nullValue && !empty($image['latestcrystalscoreid'])){
                    $latestScoreId=$image['latestcrystalscoreid'];
                }
            }

            return static::update($id, array(
                'latestcrystalscoreid'=>$latestScoreId
            ));
        }
        if(!$wasAdmin){ session::revokeAdmin(); }
        return false;
    }

}