<?php class scoreofdropimage extends baseobject {

    const PROBABILITY_PERCENT_OUT_OF_BOUNDS = 'Probability percent out of bounds (should be 0 to 100)';
    const HUMANSCORER_MISSING_NO_SCORINGSERVICE_OR_SCORINGENGINE = 'If humanscorerid not supplied, scoringservicename and scoringenginename must be present';
    const HUMANSCORER_AND_AUTOSCORER_BOTH_PRESENT = 'If humanscorerid supplied, scoringservicename and scoringenginename must be empty';
    const HUMANSCORERID_USERID_MISMATCH = 'humanscorerid must match logged-in user ID';

    const SCORING_SERVICE_NAME_ICEBEAR = 'IceBear';
    const SCORING_ENGINE_CARRY_FORWARD = '(carried forward)';
    const SCORING_ENGINE_CRYSTAL_MARKED = '(crystal marked)';

    protected static array $fields=array(
        'name'=>validator::REQUIRED,
        'dropimageid'=>array(validator::REQUIRED, validator::INTEGER),
        'scoreddatetime'=>validator::DATETIME,
        'crystalscoreid'=>validator::INTEGER,
        'crystalprobabilitypercent'=>validator::FLOAT,
        'humanscorerid'=>validator::INTEGER,
        'scoringservicename'=>validator::ANY,
        'scoringenginename'=>validator::ANY
    );

	/**
	 * @throws NotFoundException
	 * @throws NotModifiedException
	 * @throws ServerException
	 * @throws BadRequestException
	 * @throws ForbiddenException
	 */
	public static function create(array $request=[]): array {

        $imageId=$request['dropimageid'];
        $image=dropimage::getById($imageId);
        if(!$image){ throw new NotFoundException('No image with id '.$imageId); }
        $wellDrop=welldrop::getById($image['welldropid']);
        $request['projectid']=$wellDrop['projectid'];
        $plateWell=platewell::getById($wellDrop['platewellid']);
		$plate=null;
		if($plateWell){
			$plateId=$plateWell['plateid'];
			$plate=plate::getById($plateId);
		}

        if(!$plate){
            session::becomeAdmin();
            $wellDrop=welldrop::getById($image['welldropid']);
            $request['projectid']=$wellDrop['projectid'];
            $plateWell=platewell::getById($wellDrop['platewellid']);
            $plateId=$plateWell['plateid'];
            $plate=plate::getById($plateId);
            $defaultProjectId=project::getByName(baseproject::DEFAULTPROJECT)['id'];
            if($plate['projectid']==$defaultProjectId && $plate['ownerid']!=session::getUserId()){
                //in default, not the owner, and not an admin because we would've found the plate already
                $plate=null;
            }
            session::revertAdmin();
        }
        if(!$plate){
            throw new NotFoundException("Either the plate does not exist, or you do not have permission to see it");
        }
		$plateId=$plateWell['plateid'];

        //Must have a score or a probability (or both)
        if(!isset($request['crystalprobabilitypercent']) && !isset($request['crystalscoreid'])){
            throw new BadRequestException('Score must have either a score ID or a crystal probability percent');
        }

        //Must have a human scorer id that matches user session, XOR both scoring service and scoring engine names
        if(isset($request['humanscorerid'])){
            if($request['humanscorerid']!=session::getUserId()){
                throw new ForbiddenException(self::HUMANSCORERID_USERID_MISMATCH);
            }
            if(isset($request['scoringservicename']) || isset($request['scoringenginename'])){
                throw new BadRequestException(self::HUMANSCORER_AND_AUTOSCORER_BOTH_PRESENT);
            }
        } else if(!isset($request['scoringservicename']) || !isset($request['scoringenginename'])){
            throw new BadRequestException(self::HUMANSCORER_MISSING_NO_SCORINGSERVICE_OR_SCORINGENGINE);
        }

        //Default to current date/time
        if(!isset($request['scoreddatetime'])){
            $request['scoreddatetime']=gmdate('Y-m-d H:i:s');
        }
        $scoreName='img'.$imageId.' at '.$request['scoreddatetime'];

        $imageParameters=array('_classname'=>'dropimage');
        if(isset($request['crystalprobabilitypercent'])){
            $percent=$request['crystalprobabilitypercent'];
            if(0>=$percent || 100<$percent){
                throw new BadRequestException(self::PROBABILITY_PERCENT_OUT_OF_BOUNDS);
            }
            $imageParameters['latestcrystalprobabilitypercent']=$percent;
            $scoreName.=' prob'.$percent;
        }
        if(isset($request['crystalscoreid'])){
            $scoreId=$request['crystalscoreid'];
            $score=crystalscore::getById($scoreId);
            if(!$score){ throw new NotFoundException('No crystalscore with ID '.$scoreId); }
            if(1*$score['crystalscoringsystemid']!==1*$plate['crystalscoringsystemid']){
                throw new BadRequestException('Scoring system ID mismatch between score and plate');
            }
            $imageParameters['latestcrystalscoreid']=$scoreId;
            $scoreName.=' score'.$scoreId;
        }

        session::becomeAdmin(); //needed because of plates in default project
        $request['name']=$scoreName;
        $created=parent::createByClassName($request,'scoreofdropimage');

        baseobject::update($imageId, $imageParameters);
        session::revertAdmin();

        welldrop::updateLatestScoreIdAndCrystalProbability($wellDrop['id']);
        plate::calculateBestScores($plateId);
        return $created;
    }

    /**
     * @throws ServerException
     * @throws BadRequestException
     * @throws NotFoundException
     * @throws ForbiddenException|NotModifiedException
	 */
    public static function deleteAllForDropImage(int $imageId): ?array {
        $image=dropimage::getById($imageId);
        if(!$image){ throw new NotFoundException('No image with id '.$imageId); }
        $wellDrop=welldrop::getById($image['welldropid']);
        $plateWell=platewell::getById($wellDrop['platewellid']);
        $plateId=$plateWell['plateid'];

        $scores=dropimage::getscores($imageId);
        if(!empty($scores)){
            foreach ($scores['rows'] as $score) {
                if (empty($score['crystalprobabilitypercent'])){
                    scoreofdropimage::delete($score['id']);
                } else {
                    scoreofdropimage::update($score['id'], array('crystalscoreid'=>database::$nullValue));
                }
            }
        }
        baseobject::update($imageId, array(
            '_classname'=>'dropimage',
            'latestcrystalscoreid'=>database::$nullValue
        ));
        welldrop::updateLatestScoreIdAndCrystalProbability($wellDrop['id']);
        plate::calculateBestScores($plateId);
        return dropimage::getById($imageId);
    }

}