<?php class ImagingManagement extends Device {

    const IMAGER_TEMPERATURE_MISMATCH='Imager already exists but temperatures do not match. Update either sender or IceBear.';
    const IMAGER_BAD_TEMPERATURE='Temperature must be a number';
    const IMAGER_BAD_CAPACITY='Capacity must be a number greater than or equal to zero';

	/**
	 * Creates an imager, or returns one by the same name without modifying it.
	 * @param array $imager
	 * @return array
	 * @throws BadRequestException
	 * @throws ForbiddenException
	 * @throws NotFoundException
	 * @throws ServerException
	 */
    public static function createImager(array $imager): array {
        Log::debug('In ImagingManagement::createImager');
        static::require(['name','manufacturerName','nominalTemperature','plateCapacity'], $imager);
        $imagerName=$imager['name'];
        $existing=imager::getByName($imagerName);
        if($existing){
            Log::info("Imager $imagerName exists");
            if(1*$imager['nominalTemperature']!==1*$existing['temperature']){
                Log::warning(self::IMAGER_TEMPERATURE_MISMATCH.' ['.$imager['name'].'] Imager: '.$imager['nominalTemperature'].', IceBear imager: '.$existing['temperature']);
            }
            $existing['receiversId']=$existing['id'];
            Log::debug('Returning from ImagingManagement::createImager');
            return $existing;
        }
        Log::info("Imager $imagerName does not exist, creating...");
        if(!validator::isValid($imager['nominalTemperature'],validator::FLOAT)){
            throw new BadRequestException(self::IMAGER_BAD_TEMPERATURE);
        }
        $capacity=$imager['plateCapacity'];
        if(!validator::isValid($capacity,validator::INTEGER) || 1*$capacity<0){
            throw new BadRequestException(self::IMAGER_BAD_CAPACITY);
        }
        session::becomeAdmin();
        imager::create(array(
            'name'=>$imagerName,
            'friendlyname'=>$imagerName,
            'manufacturer'=>$imager['manufacturerName'],
            'temperature'=>$imager['nominalTemperature'],
            'platecapacity'=>$capacity,
            'alertlevel'=>floor(0.75*$capacity),
            'warninglevel'=>floor(0.9*$capacity),
        ));
        $imager=imager::getByName($imagerName);
        $imager['receiversId']=$imager['id'];
        Log::info('...imager created');
        Log::debug('Returning from ImagingManagement::createImager');
        return $imager;
    }

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

    /**
     * @throws NotFoundException
     * @throws ServerException
     * @throws BadRequestException
     * @throws ForbiddenException
     */
    public static function createImage(array $image): array {
        Log::debug('In ImagingManagement::createImage');
        static::require(['iceBearImagingSessionId','rowNumber','columnNumber','subPositionNumber','imageData'], $image);
        $micronsPerPixel=-1;
        if(isset($image['micronsPerPixel'])){
            $micronsPerPixel=1*$image['micronsPerPixel'];
            if(0>=$micronsPerPixel){
                throw new BadRequestException('micronsPerPixel must be numeric and greater than zero');
            }
        }
        $row=1*$image['rowNumber'];
        $col=1*$image['columnNumber'];
        $sub=1*$image['subPositionNumber'];
        if(!$row){
            $row=array_search($image['rowNumber'], platetype::$rowLabels);
        }
        session::becomeAdmin();
        $imagingSessionId=$image['iceBearImagingSessionId'];
        $imagingSession=imagingsession::getById($imagingSessionId);
        if(!$imagingSession){
            throw new NotFoundException('No imaging session with ID '.$imagingSessionId);
        }
        $plate=plate::getById($imagingSession['plateid']);
        $barcode=$plate['name'];
        $imagingSessionId=$imagingSession['id'];
        $projectId=$imagingSession['projectid'];

        $plateWell=platewell::getByProperties(array(
            'plateid'=>$plate['id'],
            'row'=>$row,
            'col'=>$col
        ));
        if(!$plateWell){
            throw new BadRequestException("Bad plate row/column number ($row/$col)");
        }
        $plateWell=$plateWell['rows'][0];
        $wellDrop=welldrop::getByProperties(array(
            'platewellid'=>$plateWell['id'],
            'dropnumber'=>$sub
        ));
        if(!$wellDrop){
            throw new BadRequestException('Bad plate subposition number');
        }
        $wellDrop=$wellDrop['rows'][0];

        $imageStore=rtrim(config::get('core_imagestore'),'/').'/';
        $imagePath=substr($barcode,0,2).'/'.substr($barcode,0,3).'/'.$barcode.'/';
        $imagePath=$imageStore.$imagePath.'imagingsession'.$imagingSessionId.'/';
        $thumbPath=$imagePath.'thumbs/';

        if(!file_exists($thumbPath) && !@mkdir($thumbPath, 0755, true)){
            throw new ServerException('Could not create directory for image storage');
        }

        $imageName=platewell::getWellLabel($row, $col).'_'.$sub.'.tmp';
        $imagePath.=$imageName;
        $thumbPath.=$imageName;

        if(!file_put_contents($imagePath, convert_uudecode($image['imageData'])) ){
            throw new ServerException('Could not write image to image store');
        }
        if(strlen(@file_get_contents($imagePath))<10){
            //probably a 2-byte garbage file caused by UUencode header/footer
            $imageBytes=static::stripUUencodingHeaderAndFooterLines($image['imageData']);
            $imageBytes=convert_uudecode($imageBytes);
            if(!@file_put_contents($imagePath, $imageBytes) ){
                throw new ServerException('Could not write image to image store');
            }
        }
        $imageProperties=@getimagesize($imagePath);
        if(!$imageProperties || !$imageProperties[0]){
            throw new ServerException('Could not determine size of image. Is it corrupt?');
        }
        $width=$imageProperties[0];
        $height=$imageProperties[1];
        $imageType=$imageProperties[2];
        if(IMAGETYPE_JPEG==$imageType){
            $newImagePath=substr($imagePath, 0, -4).'.jpg';
        } else if(IMAGETYPE_PNG==$imageType){
            $newImagePath=substr($imagePath, 0, -4).'.png';
        } else {
            throw new ServerException('Image must be PNG or JPEG');
        }

        if(isset($image['thumbData'])){
            if(!@file_put_contents($thumbPath, convert_uudecode($image['thumbData'])) ){
                throw new ServerException('Could not write thumbnail to image store');
            }
            if(strlen(@file_get_contents($thumbPath))<10){
                //probably a 2-byte garbage file caused by UUencode header/footer
                $thumbBytes=static::stripUUencodingHeaderAndFooterLines($image['thumbData']);
                $thumbBytes=convert_uudecode($thumbBytes);
                if(!@file_put_contents($thumbPath, $thumbBytes) ){
                    throw new ServerException('Could not write thumbnail to image store');
                }
            }
        } else {
            $thumbWidth=200;
            if(IMAGETYPE_JPEG===$imageType){
                $thumb=imagecreatefromjpeg($imagePath);
                $newThumb=imagescale($thumb, $thumbWidth);
                imagejpeg($newThumb, $thumbPath);
            } else {
                $thumb=imagecreatefrompng($imagePath);
                $newThumb=imagescale($thumb, $thumbWidth);
                imagepng($newThumb, $thumbPath);
            }
        }

        $thumbProperties=@getimagesize($thumbPath);
        if(!$thumbProperties || !$thumbProperties[0]){
            throw new ServerException('Could not determine size of thumbnail. Is it corrupt?');
        }
        if(IMAGETYPE_JPEG==$thumbProperties[2]){
            $newThumbPath=substr($thumbPath, 0, -4).'.jpg';
        } else if(IMAGETYPE_PNG==$thumbProperties[2]){
            $newThumbPath=substr($thumbPath, 0, -4).'.png';
        } else {
            throw new ServerException('Thumbnail must be PNG or JPEG');
        }
        if(!@rename($imagePath, $newImagePath) || !@rename($thumbPath, $newThumbPath)){
            throw new ServerException('Could not save image/thumb in final store');
        }

        $existing=dropimage::getByProperties(array(
            'imagingsessionid'=>$imagingSessionId,
            'welldropid'=>$wellDrop['id']
        ));
        if(empty($existing)){
            $created=dropimage::create(array(
                'name'=>$barcode.'_'.platewell::getWellLabel($row, $col).'.'.$sub.'_is'.$imagingSessionId,
                'pixelheight'=>$height,
                'pixelwidth'=>$width,
                'micronsperpixelx'=>$micronsPerPixel,
                'micronsperpixely'=>$micronsPerPixel,
                'projectid'=>$projectId,
                'welldropid'=>$wellDrop['id'],
                'imagingsessionid'=>$imagingSessionId,
                'imagestorepath'=>$imageStore,
                'imagepath'=>str_replace($imageStore,'',$newImagePath),
                'thumbnailpath'=>str_replace($imageStore,'',$newThumbPath),
            ))['created'];
        } else {
            $existingImageId=$existing['rows'][0]['id'];
            $created=dropimage::update($existingImageId, array(
                'pixelheight'=>$height,
                'pixelwidth'=>$width,
                'micronsperpixelx'=>$micronsPerPixel,
                'micronsperpixely'=>$micronsPerPixel,
            ))['updated'];
        }

        $image['receiversId']=$created['id'];

        if(isset($image['marks'])){
            foreach($image['marks'] as &$mark){
                $mark=static::createCrystalMark($mark, $image['receiversId']);
            }
        }
        Log::debug('Returning from ImagingManagement::createImage');
        return $image;
    }

    /**
     * @throws NotFoundException
     * @throws BadRequestException
     * @throws ForbiddenException
     * @throws ServerException
     */
    private static function createCrystalMark(array $crystalMark, int $dropImageId): array {
        Log::debug('In ImagingManagement::createCrystalMark');
        $dropImage=dropimage::getById($dropImageId);
        if(!$dropImage){
            throw new NotFoundException("No drop image with ID $dropImageId");
        }
        if(isset($crystalMark['x'])){
            $x=(int)$crystalMark['x'];
        } elseif(isset($crystalMark['pixelX'])){
            $x=(int)$crystalMark['pixelX'];
        } else{
            throw new BadRequestException('Crystal pixelX is required');
        }
        if(isset($crystalMark['y'])){
            $y=(int)$crystalMark['y'];
        } elseif(isset($crystalMark['pixelY'])){
            $y=(int)$crystalMark['pixelY'];
        } else{
            throw new BadRequestException('Crystal pixelY is required');
        }
        if(!isset($crystalMark['type']) || "point"!==$crystalMark['type']){
            throw new BadRequestException('Crystal mark type is required, and must be "point"');
        }
        $crystal=crystal::getByProperties(array(
            'dropimageid'=>$dropImageId,
            'pixelx'=>$x,
            'pixely'=>$y
        ));
        if(!empty($crystal)){
            $crystal=$crystal['rows'][0];
        } else if(isset($crystalMark['name'])){
            $crystal=crystal::getByName($crystalMark['name']);
            if(!empty($crystal)){
                crystal::update($crystal['id'],array(
                    'pixelx'=>$x,
                    'pixely'=>$y
                ));
            }
        }

        if(empty($crystal)){
            $numberInDrop=1;
            $crystals=crystal::getByProperties(array(
                'dropimageid'=>$dropImageId
            ));
            if(!empty($crystals)){
                $numberInDrop+=1*$crystals['total'];
            }
            $crystal=crystal::create(array(
                'dropimageid'=>$dropImageId,
                'welldropid'=>$dropImage['welldropid'],
                'projectid'=>$dropImage['projectid'],
                'numberindrop'=>$numberInDrop,
                'name'=>'dummy'.time(),
                'prefix'=>'dummy'.time(),
                'pixelx'=>$x,
                'pixely'=>$y
            ))['created'];
        }

        if(isset($crystalMark['name'])){
            crystal::update($crystal['id'],array(
                'name'=>$crystalMark['name']
            ));
        }

        $crystalMark['receiversId']=$crystal['id'];
        Log::debug('Returning from ImagingManagement::createCrystalMark');
        return $crystalMark;
    }

    /**
     * @throws ServerException
     */
    private static function stripUUencodingHeaderAndFooterLines(string $rawString): string {
        Log::debug('In ImagingManagement::stripUUencodingHeaderAndFooterLines');
        $parts=explode("\n", $rawString, 2);
        $headerlessString=$parts[1];
        $parts=explode("\nend", $headerlessString);
        Log::debug('Returning from ImagingManagement::stripUUencodingHeaderAndFooterLines');
        return $parts[0];
    }

}