<?php class FormulatrixImport extends Device {

    const USERNAME='formulatriximporter';
    const USERFULLNAME='Formulatrix Importer';

    const SCORING_SYSTEM_NAME='Formulatrix';

    const BARCODE_PATTERN='AAAA'; // Four alphanumeric, e.g., 9a62

    /**
     * @return array
	 * @throws BadRequestException
     * @throws ServerException
	 */
    public static function getScores(): array {
        return static::getScoresWithScoringSystemName(static::SCORING_SYSTEM_NAME);
    }

	/**
	 * Does the actual work of getScores - split for testing.
	 * @param string $scoringSystemName
	 * @return array
	 * @throws BadRequestException
	 * @throws ServerException
	 */
    public static function getScoresWithScoringSystemName(string $scoringSystemName): array{
        return ImageScoring::getScoresWithScoringSystemName($scoringSystemName);
    }

	/**
	 * @param array $params
	 * @return array
	 * @throws BadRequestException
	 * @throws ForbiddenException
	 * @throws NotFoundException
	 * @throws ServerException
	 */
    public static function createScores(array $params): array {
        return static::createScoresWithScoringSystemName($params, static::SCORING_SYSTEM_NAME);
    }

	/**
	 * Does the actual work of createScores. We split it out like this so that we can test the code doing the actual
	 * work - testing creation with a pre-existing "Formulatrix" scoring system will fail.
	 * @param array $params
	 * @param string $scoringSystemName
	 * @return array
	 * @throws BadRequestException
	 * @throws ForbiddenException
	 * @throws NotFoundException
	 * @throws ServerException
	 */
    public static function createScoresWithScoringSystemName(array $params, string $scoringSystemName): array {
        Log::debug('In createScoresWithScoringSystemName');
        $params['name']=$scoringSystemName;
        $system=ImageScoring::createScoringSystem($params);
        Log::debug('Returning from createScoresWithScoringSystemName');
        return $system;
    }

    /**
     * @throws BadRequestException
     * @throws ServerException
     * @return array containing keys "fxImagingTaskId" and "numImages" (how many images are registered to this task in IceBear)
     */
    public static function getLatestImagingTask(): array {
        return static::getImageCountTotalAndImagedTime();
    }

	/**
	 * @param array $params
	 * @return int[]
	 * @throws BadRequestException
	 * @throws ServerException
	 */
    public static function getImagingTaskByFormulatrixId(array $params): array {
        static::require('id', $params);
        return static::getImageCountTotalAndImagedTime($params['id']);
    }

    /**
     * @param bool $fxImagingTaskId
     * @return int[]
     * @throws BadRequestException
     * @throws ServerException
     */
    private static function getImageCountTotalAndImagedTime(int $fxImagingTaskId=0): array {
        session::becomeAdmin();
        $ret=array(
            "fxImagingTaskId"=>0,
            "dateTime"=>0,
            "numImages"=>0
        );
        $stmt='SELECT ims.manufacturerdatabaseid AS manufacturerdatabaseid, ims.imageddatetime AS imageddatetime
                FROM imagingsession AS ims, imager AS im
                WHERE ims.imagerid=im.id';
        $order=' ORDER BY ims.imageddatetime DESC';
        if(!$fxImagingTaskId){
            //No ImagingTask ID specified. Get the Fx ID of the latest Formulatrix imagingsession.
            $stmt=$stmt.' AND im.manufacturer=:manufacturer '.$order;
            $latestImagingSession=database::queryGetOne($stmt,array(':manufacturer'=>'Formulatrix'));
            if(!$latestImagingSession){ return $ret; }
            $fxImagingTaskId=$latestImagingSession['manufacturerdatabaseid'];
        } else {
            //Specific Fx imaging task
            $stmt=$stmt.' AND ims.manufacturerdatabaseid=:id '.$order;
            $latestImagingSession=database::queryGetOne($stmt,array(':id'=>$fxImagingTaskId));
            if(!$latestImagingSession){ return $ret; }
        }

        $ret['dateTime']=$latestImagingSession['imageddatetime'];
        $ret['fxImagingTaskId']=$fxImagingTaskId;

        $images=database::queryGetAll('
            SELECT SQL_CALC_FOUND_ROWS di.id
            FROM imagingsession AS ims, dropimage AS di
            WHERE di.imagingsessionid=ims.id
            AND ims.manufacturerdatabaseid=:fxid
        ', array(
            ':fxid'=>$fxImagingTaskId
        ));
        if($images){
            $ret['numImages']=$images['total'];
        }
        return $ret;
    }

	/**
	 * @param array $params
	 * @return array|null
	 * @throws BadRequestException
	 * @throws ForbiddenException
	 * @throws NotFoundException
	 * @throws ServerException
	 */
    public static function createUser(array $params=[]): ?array {
        return UserManagement::createUser($params);
    }

	/**
	 * @param array $params
	 * @return array
	 * @throws BadRequestException
	 * @throws ForbiddenException
	 * @throws NotFoundException
	 * @throws ServerException
	 */
    public static function createPlateType(array $params): array{
        Log::debug('In FormulatrixImport::createPlateType');
        $plateType=PlateImport::createPlateType($params);
        Log::debug('Returning from FormulatrixImport::createPlateType');
        return $plateType;
    }

	/**
	 * @param array $params
	 * @return array
	 * @throws BadRequestException
	 * @throws ForbiddenException
	 * @throws NotFoundException
	 * @throws NotModifiedException
	 * @throws ServerException
	 */
    public static function createPlate(array $params): array {
        Log::debug('In FormulatrixImport::createPlate');
        $plate=PlateImport::createPlate($params);
        Log::debug('Returning from FormulatrixImport::createPlate');
        return $plate;
    }

	/**
	 * @param array $params
	 * @return array
	 * @throws BadRequestException
	 * @throws ForbiddenException
	 * @throws NotFoundException
	 * @throws ServerException
	 */
    public static function createImager(array $params): array {
        Log::debug('In FormulatrixImport::createImager');
        static::require('name', $params);
        static::requireOneOf(['temperature','nominalTemperature'], $params);
        if(!isset($params['nominalTemperature'])){ $params['nominalTemperature']=$params['temperature']; }
        $params['manufacturerName']='Formulatrix';
        $imagerName=$params['name'];
        if(!preg_match('/^RI\d+-\d+$/', $imagerName) && !preg_match('/^RI\d+-\d+-\d+$/', $imagerName)){
            throw new BadRequestException('Imager name does not match Formulatrix serial format RInn-nnnn');
        }
        //Formulatrix imager serials are RIxx-yyyy(-zzzz), where xx represents the nominal capacity.
        //Imagers may be down-licensed to a lower capacity, but we can't detect this easily. Set xx as the capacity.
        $parts=explode('-', substr($imagerName, 2));
        $params['plateCapacity']=1*$parts[0];
        Log::debug('Setting imager plate capacity to '.$params['plateCapacity']);
        $imager=ImagingManagement::createImager($params);
        Log::debug('Returning from FormulatrixImport::createImager');
        return $imager;
    }


	/**
	 * @param array $params
	 * @return mixed
	 * @throws BadRequestException
	 * @throws ForbiddenException
	 * @throws NotFoundException
	 * @throws ServerException
	 */
    public static function createImagingSettings(array $params): array {
        /*
                {
                    "captureProfile":{
                        "name:"Test settings [[TIME]]",
                        "id":[[TIME]],
                        "currentVersionId":[[TIME]]0,
                        "settings":{
                            "LightPath":"Visible",
                            "TestSetting":"Test"
                        }
                    },
                    "imager":{
                        "name":"RI54-0039",
                        "temperature":20
                    }
                }
         */
        static::require(['captureProfile','imager'],$params);
        static::require(['name','id','currentVersionId','settings'],$params['captureProfile']);
        static::require(['name','temperature'],$params['imager']);
        session::becomeAdmin();
        $sharedProject=project::getByName(baseproject::SHARED);
		if(!$sharedProject){
			throw new NotFoundException('Could not read shared project.');
		}
        $sharedProjectId=$sharedProject['id'];
        $imager=static::createImager($params['imager']);
        $imagerId=$imager['id'];
        $fxCaptureProfileName=$params['captureProfile']['name'];
        $fxCaptureProfileId=$params['captureProfile']['id'];
        $fxCaptureProfileVersionId=$params['captureProfile']['currentVersionId'];
        $ibImagingParameters=imagingparameters::getByProperties(array(
            'manufacturer'=>'Formulatrix',
            'manufacturerdatabaseid'=>$fxCaptureProfileId
        ));
        if($ibImagingParameters) {
            $ibImagingParametersId=$ibImagingParameters['rows'][0]['id'];
        } else {
            $ibImagingParameters=imagingparameters::create(array(
                'name'=>$params['captureProfile']['name'],
                'projectid'=>$sharedProjectId,
                'manufacturer'=>'Formulatrix',
                'manufacturerdatabaseid'=>$fxCaptureProfileId,
            ));
            $ibImagingParametersId=$ibImagingParameters['created']['id'];
        }
        $ibImagingParametersVersion=imagingparametersversion::getByProperties(array(
            'imagingparametersid'=>$ibImagingParametersId,
            'manufacturerdatabaseid'=>$fxCaptureProfileVersionId
        ));
        if($ibImagingParametersVersion){
            $ibImagingParametersVersion=$ibImagingParametersVersion['rows'][0];
            $ibImagingParametersVersionId=$ibImagingParametersVersion['id'];
        } else {
            $ibImagingParametersVersion=imagingparametersversion::create(array(
                'name'=>$params['captureProfile']['name'],
                'projectid'=>$sharedProjectId,
                'imagingparametersid'=>$ibImagingParametersId,
                'manufacturerdatabaseid'=>$fxCaptureProfileVersionId
            ));
            $ibImagingParametersVersionId=$ibImagingParametersVersion['created']['id'];
            imagingparameters::update($ibImagingParametersId, array(
                'currentversionid'=>$ibImagingParametersVersionId,
            ));
            foreach ($params['captureProfile']['settings'] as $setting=>$value){
                imagingsetting::create(array(
                    'settingname'=>$setting,
                    'projectid'=>$sharedProjectId,
                    'imagerid'=>$imagerId,
                    'imagingparametersversionid'=>$ibImagingParametersVersionId,
                    'name'=>'v'.$ibImagingParametersVersionId.'_robot'.$imagerId.'_'.$setting,
                    'settingvalue'=>$value
                ));
            }
        }
        imagingparametersversion::update($ibImagingParametersVersionId, array(
            'name'=>$params['captureProfile']['name'].'_v'.$ibImagingParametersVersionId
        ));
        imagingparameters::update($ibImagingParametersId, array(
            'currentversionid'=>$ibImagingParametersVersionId
        ));
        $ibImagingParameters=imagingparameters::getByProperties(array(
            'name'=>$fxCaptureProfileName,
            'manufacturer'=>'Formulatrix'
        ))["rows"][0];
        $ibImagingParameters['imagingparametersversion']=imagingparametersversion::getById($ibImagingParameters['currentversionid']);
        return $ibImagingParameters;
    }

	/**
	 * @param array $params
	 * @return mixed
	 * @throws BadRequestException
	 * @throws ForbiddenException
	 * @throws NotFoundException
	 * @throws ServerException
	 */
    public static function createImagingSession(array $params): array {
        static::require(['imagingTaskId','plateBarcode','temperature','imagerName','imagedDateTime','captureProfileVersionId'], $params);
        session::becomeAdmin();
        $imagingParametersVersion=imagingparametersversion::getByProperties(array(
            'manufacturerdatabaseid'=>$params['captureProfileVersionId']
        ));
        if(!$imagingParametersVersion){
            throw new BadRequestException('No IceBear imagingparametersversion with manufacturerdatabaseid ['.$params['captureProfileVersionId'].']');
        }
        $imagingParametersVersion=$imagingParametersVersion['rows'][0];
        $imagingParametersVersionId=$imagingParametersVersion['id'];

        $imagingSession=imagingsession::getByProperties(array(
            'manufacturerdatabaseid'=>$params['imagingTaskId'],
            'imagingparametersversionid'=>$imagingParametersVersionId
        ));
        $imager=null;

        if($imagingSession){

            if(!$imagingSession['rows'] || 1!==count($imagingSession['rows'])){
                throw new ServerException('Found no/multiple imagingSession matches, should be 1');
            }
            $imagingSession=$imagingSession['rows'][0];

        } else {

            $imagingParameters=imagingparameters::getByProperties(array(
                'currentversionid'=>$imagingParametersVersionId,
                'manufacturer'=>'Formulatrix'
            ));
            if(!$imagingParameters){
                throw new BadRequestException('No IceBear imagingparameters with currentversionid ['.$imagingParametersVersionId.']');
            }
            $imagingParameters=$imagingParameters['rows'][0];
            $imagingParametersId=$imagingParameters['id'];
            $imagingParametersName=$imagingParameters['name'];
            $imagingParametersVersionId=$imagingParameters['currentversionid'];
            $captureProfileId=$imagingParameters['manufacturerdatabaseid'];

            //Subtract "captureProfileId" seconds to the imaged date/time, ensuring that the plate does not have two
            //imagingsessions at exactly the same time. (A Formulatrix ImagingTask can contain images from more than
            //one capture profile, which are split into different imagingsessions in IceBear.) We subtract because
            //"Imager defaults" is most likely lowest CaptureProfileId, and we want that to appear as the most recent.
            $imagedDateTime=str_replace('_',' ', $params['imagedDateTime']);
            $imagedDateTime=str_replace('T',' ', $imagedDateTime);
            try {
                $date=new DateTime($imagedDateTime);
            } catch (Exception){
                throw new BadRequestException('Bad date string in createImagingSession: '.$params['imagedDateTime']);
            }
            $date->sub(DateInterval::createFromDateString('+'.$captureProfileId.' seconds'));
            $imagedDateTime=date_format($date, 'Y-m-d H:i:s');

            $lightType='Visible';
            if(in_array(substr($imagingParametersName,0,3), array('UV ','UV-','UV_'))){
                $lightType='UV';
            } else {
                $settings=imagingsetting::getByProperty('imagingparametersversionid', $imagingParametersVersionId);
                if($settings){
                    foreach ($settings['rows'] as $setting){
                        if("LightType"==$setting['settingname'] || "LightPath"==$setting['settingname']){
                            $lightType=$setting['settingvalue'];
                        }
                    }
                }
            }

            $imager=imager::getByName($params['imagerName']);
            if(!$imager){
                throw new BadRequestException('No IceBear imager with name '.$params['imagerName']);
            }
            $plate=plate::getByName($params['plateBarcode']);
            if(!$plate){
                throw new BadRequestException('No IceBear plate with barcode '.$params['plateBarcode']);
            }
            $imagingSession=imagingsession::create(array(
                'plateid'=>$plate['id'],
                'projectid'=>$plate['projectid'],
                'imagerid'=>$imager['id'],
                'imagingparametersversionid'=>$imagingParametersVersionId,
                'manufacturerdatabaseid'=>$params['imagingTaskId'],
                'temperature'=>$params['temperature'],
                'imageddatetime'=>$imagedDateTime,
                'lighttype'=>$lightType,
                'name'=>$params['plateBarcode'].'_'.$imagedDateTime.'_profile'.$imagingParametersId
            ));
            $imagingSession=$imagingSession['created'];
        }
        $imagingSession['imager']=$imager;
        return $imagingSession;
    }

	/**
	 * @param array $params
	 * @return array
	 * @throws BadRequestException
	 */
    public static function createImages(array $params): array {
        static::require('images',$params);
        $ret=[];
        foreach($params['images'] as $image) {
            try{
                $ret[]=static::createImage($image);
            } catch (Exception $e){
                $ret[]=array('error'=>$e->getMessage());
            }
        }
        return $ret;
    }

    /**
     * @param $params
     * @return array|mixed
     * @throws BadRequestException
     * @throws ForbiddenException
     * @throws NotFoundException
     * @throws ServerException
     * @throws Exception
     */
    public static function createImage(array $params): array {
        static::require(['iceBearImagingSessionId','wellRowNumber','wellColumnNumber','wellDropNumber','image','thumb'],$params);
        static::requireOneOf(['plateBarcode','iceBearPlateId'], $params);

        set_time_limit(300);

        session::becomeAdmin();

        $imagingSession=imagingsession::getById($params['iceBearImagingSessionId']);
        if(!$imagingSession){
            throw new BadRequestException('No IceBear imagingsession with ID '.$params['iceBearImagingSessionId']);
        }

        if(isset($params['iceBearPlateId'])){
            $plate=plate::getById($params['iceBearPlateId']);
        } else {
            $plate=plate::getByName($params['plateBarcode']);
        }
        if(!$plate){
            throw new BadRequestException('No IceBear plate with ID '.$params['iceBearPlateId']);
        }
        $barcode=$plate['name'];
        $projectId=$plate['projectid'];

        $wellDrop=welldrop::getByPlateIdAndRowColumnDrop(
            $params['iceBearPlateId'],
            $params['wellRowNumber'],
            $params['wellColumnNumber'],
            $params['wellDropNumber']
        );
        if(!$wellDrop){
            throw new BadRequestException('No IceBear welldrop matching plateid '.$params['iceBearPlateId'].
                ' row '.$params['wellRowNumber'].' column '.$params['wellColumnNumber'].' drop '.$params['wellDropNumber']
            );
        }
        $wellDropId=$wellDrop['id'];

        $wellLabel=platewell::getWellLabel($params['wellRowNumber'], $params['wellColumnNumber']);
        $imageName=$barcode.'_'.$wellLabel.'.'.$params['wellDropNumber'].'_is'.$params['iceBearImagingSessionId'];
        $existing=dropimage::getByName($imageName);
        if($existing){
            return $existing;
        }

        $imageStore=rtrim(config::get('core_imagestore'),'/').'/';
        $imageFinalPath=substr($barcode,0,2).'/'.substr($barcode,0,3).'/'.$barcode.'/imagingsession'.$params['iceBearImagingSessionId'].'/';
        $thumbFinalPath=$imageFinalPath.'thumbs/';

        $random=bin2hex(random_bytes(24));
        $imageTempPath=$imageStore.$random.'_image';
        $thumbTempPath=$imageStore.$random.'_thumb';

        @file_put_contents($imageTempPath, base64_decode($params['image']));
        @file_put_contents($thumbTempPath, base64_decode($params['thumb']));

        if(!file_exists($imageTempPath) || !file_exists($imageStore.$random.'_thumb')){
            throw new ServerException('Could not write drop image/thumb to temporary storage');
        }
        $imageInfo=getimagesize($imageTempPath);
        $thumbInfo=getimagesize($thumbTempPath);
        if(!$imageInfo || !$thumbInfo){
            @unlink($imageTempPath);
            @unlink($thumbTempPath);
            throw new ServerException('Could not determine dimensions and type. Either image or thumb is corrupt.');
        }
        $pixelWidth=$imageInfo[0];
        $pixelHeight=$imageInfo[1];
        $imageType=$imageInfo[2];
        if(IMAGETYPE_JPEG==$imageType){
            $extension='.jpg';
        } else if(IMAGETYPE_PNG==$imageType){
            $extension='.png';
        } else {
            throw new BadRequestException("Image is not a JPEG or PNG");
        }

        if(!file_exists($imageStore.$thumbFinalPath) && !@mkdir($imageStore.$thumbFinalPath, 0755, true)){
            throw new ServerException('Could not create final storage directory for image/thumb: '.$imageStore.$thumbFinalPath);
        }

//        $wellLabel=platewell::getWellLabel($params['wellRowNumber'], $params['wellColumnNumber']);
        $filename=$wellLabel.'_'.$params['wellDropNumber'].$extension;
        $imageFinalPath.=$filename;
        $thumbFinalPath.=$filename;

        if(!@rename($imageTempPath, $imageStore.$imageFinalPath) || !@rename($thumbTempPath, $imageStore.$thumbFinalPath)){
            throw new ServerException('Could not move temporary image/thumb to final storage');
        }

//        $imageName=$barcode.'_'.$wellLabel.'.'.$params['wellDropNumber'].'_is'.$params['iceBearImagingSessionId'];

        $image=dropimage::create(array(
            'name'=>$imageName,
            'projectid'=>$projectId,
            'imagingsessionid'=>$params['iceBearImagingSessionId'],
            'plateid'=>$params['iceBearPlateId'],
            'welldropid'=>$wellDropId,
            'pixelwidth'=>$pixelWidth,
            'pixelheight'=>$pixelHeight,
            'micronsperpixelx'=>$params['micronsPerPixel'],
            'micronsperpixely'=>$params['micronsPerPixel'],
            'imagestorepath'=>$imageStore,
            'imagepath'=>$imageFinalPath,
            'thumbnailpath'=>$thumbFinalPath
        ));

        return $image['created'];
    }

	/**
	 * @param array $params
	 * @return array[]
	 * @throws BadRequestException
	 * @throws ForbiddenException
	 * @throws NotFoundException
	 * @throws NotModifiedException
	 * @throws ServerException
	 */
    public static function updateImagerInventories(array $params): array {
        static::require('imagers',$params);
        session::becomeAdmin();
        $sharedProject=project::getByName(baseproject::SHARED);
		if(!$sharedProject){
			throw new NotFoundException('Could not read shared project.');
		}
        $updatedImagers=[];
        foreach($params['imagers'] as $fxImager){
            static::require(['name','inventory'],$fxImager);

            $imagerName=$fxImager["name"];
            $ibImager=imager::getByName($imagerName);
            if(!$ibImager){
                // This could happen when importing into a newly-installed IceBear. Just leave it, we'll
                // update its inventory when it's been created, after we import the first inspection from that imager.
                continue;
            }

            $active=[];
            $expired=[];

            // Set all plates with imager id to null locationid
            database::query('UPDATE plate SET locationid=NULL, nextinspectiontime=NULL, finalinspectiontime=NULL, inspectionsremaining=NULL
                        WHERE locationid=:imagerid', array(':imagerid'=>$ibImager['id']));

            // Set location, next/final inspection, and number of remaining inspections for all plates in the imager
            // (except where they don't exist in IceBear yet)
            $inventory=$fxImager['inventory'];
            foreach ($inventory as $fxPlate) {
                if(0==$fxPlate['InspectionsRemaining']){
                    $expired[]=$fxPlate;
                } else {
                    $active[]=$fxPlate;
                }

                $ibPlate=plate::getByName($fxPlate['Barcode']);
                if(!$ibPlate){
                    //Could happen if the Formulatrix side calls updateImagerInventories before trying to import inspections, or
                    //if some newly-inserted plates have not had their first inspection imported yet.
                    continue;
                }
                $parameters=array(
                    'locationid'=>$ibImager['id'],
                    'nextinspectiontime'=>$fxPlate['NextInspection'],
                    'finalinspectiontime'=>$fxPlate['FinalInspection'],
                    'inspectionsremaining'=>$fxPlate['InspectionsRemaining'],
                );
                plate::update($ibPlate['id'],$parameters);
            }

            //Update the imager's active/expired count
            imager::update($ibImager['id'], array(
                'platesactive'=>count($active),
                'platesexpired'=>count($expired)
            ));

            //Write the imager's active/expired plate count to the imager load log
            $date=gmdate("Y-m-d");
            $request=array(
                'projectid'=>$sharedProject['id'],
                'imagerid'=>$ibImager['id'],
                'loadingdate'=>$date,
                'platesactive'=>count($active),
                'platesexpired'=>count($expired),
            );
            imagerloadlog::log($request);

            $updatedImagers[]=$ibImager["name"];
        }

        return array('updatedImagers'=>$updatedImagers);
    }

	/**
	 * @param array $params
	 * @return array[]
	 * @throws BadRequestException
	 * @throws ServerException
	 */
    public static function updateDiskSpaces(array $params): array {
        $updatedDrives=Device::updateDiskSpaces($params);
        $content=array(
            'total'=>count($updatedDrives),
            'rows'=>$updatedDrives['updatedDrives']
        );
        caching::setContent(diskusage::FILESYSTEM_CACHE_FORMULATRIX, $content, '+12 hours');
        return $content;
    }

	/**
	 * @return array
	 * @throws BadRequestException
	 * @throws ForbiddenException
	 * @throws NotFoundException
	 * @throws ServerException
	 */
    public static function getPlateForReimport(): array {
        return PlateImport::getPlateForReimport(['imagerType'=>'Formulatrix']);
    }

}
