<?php

use PHPUnit\Framework\TestCase;

class PlateImportTest extends TestCase {


    const USER_JSON='{
        "fullName":"Automated Test [[TIME]]",
        "email":"test.[[TIME]]@bogus.net"
    }';

    const PLATETYPE_JSON='{
        "name":"PLATETYPE_[[TIME]]",
        "rows":8,
        "columns":12,
        "subPositions":3
    }';

    const PLATE_JSON='{
            "barcode":"PLATE_[[TIME]]",
            "scoringSystemName":"[[SCORING_SYSTEM_NAME]]",
            "plateType":[[PLATETYPE_JSON]],
            "proteins":[[PROTEIN_CONSTRUCTS_JSON]],
            "owner":[[USER_JSON]]
    }';

    const IMAGER_JSON='{
        "name":"IMAGER_[[TIME]]",
        "manufacturerName":"Test",
        "nominalTemperature":20,
        "plateCapacity":100
    }';

    const IMAGING_SESSION_JSON='{
        "dateTimeUtc":"2021-12-31 23:59:59",
        "imagerName":"IMAGER_[[TIME]]",
        "lightType":"Visible",
        "images":[
            [[IMAGE_JSON]]
        ]
    
    }';

    const IMAGE_JSON='{
        "name":"A01.1",
        "scoreName":"Crystals",
        "rowNumber":1,
        "columnNumber":1,
        "subPositionNumber":1,
        "imageData":"[[ENCODED_IMAGE]]",
        "thumbData":"[[ENCODED_THUMB]]",
        "micronsPerPixel":2.15,
        "marks":[
            [[CRYSTAL_JSON]]
        ]
    }';

    const CRYSTAL_JSON='{
        "type":"point",
        "pixelX":100,
        "pixelY":200,
        "name":"xtal_[[TIME]]"
    }';

    const SCORING_SYSTEM_JSON='{
        "name":"SCORING_SYSTEM_[[TIME]]",
        "scores":[
            { "name":"Clear", "index":0, "hotkey":"0", "color":"00ffff" },
            { "name":"Crystals", "index":1, "hotkey":"9", "color":"ff0000" }
        ]
    }';

    const PROTEIN_CONSTRUCTS_JSON='[{
        "name":"PROTEIN_[[TIME]]",
        "proteinAcronym":"PRO1",
        "constructs":[
            {
                "name":"Construct 1",
                "sequences":[
                    "atcagtcgatcgtacgat",
                    "AGT CCA TGT",
                    "TWGWADWAA"
                ]
            }
        ]
    }]';

    const COOKIE_FILE='./cookie_file';

    protected function setUp():void {
        @unlink(self::COOKIE_FILE);
        try {
            database::connect();
            Log::init(Log::LOGLEVEL_DEBUG);
            session::init(new DummySession());
            PlateImport::setScopeUserToSession();
        } catch (ForbiddenException) {
            $this->fail('ForbiddenException on session::init');
        } catch (BadRequestException) {
            $this->fail('BadRequestException on session::init');
        } catch (NotFoundException) {
            $this->fail('NotFoundException on session::init');
        } catch (ServerException) {
            $this->fail('ServerException on session::init');
        }
        database::begin();
    }

    protected function tearDown():void {
        database::abort();
        session::destroy();
        Log::end();
        @unlink(self::COOKIE_FILE);
    }

    public function testGetVersion(){
        self::assertEquals(1, PlateImport::getVersion());
    }

    /**
     * @throws BadRequestException
     * @throws ForbiddenException
     * @throws NotFoundException
     * @throws ServerException
     */
    public function testCreateImager(){
        $now=time();
        $params=json_decode(str_replace('[[TIME]]', $now, self::IMAGER_JSON), true);
        $result=PlateImport::createImager($params);
        self::assertNotEmpty($result);
        self::assertArrayHasKey('receiversId', $result);
    }

    /**
     * @throws BadRequestException
     * @throws ForbiddenException
     * @throws NotFoundException
     * @throws ServerException
     */
    public function testCreateImagerAlreadyExists(){
        $now=time();
        $params=json_decode(str_replace('[[TIME]]', $now, self::IMAGER_JSON), true);
        $result=PlateImport::createImager($params);
        self::assertNotEmpty($result);
        self::assertArrayHasKey('receiversId', $result);
        $result=PlateImport::createImager($params);
        self::assertNotEmpty($result);
        self::assertArrayHasKey('receiversId', $result);
    }

    /**
     * @throws BadRequestException
     * @throws ForbiddenException
     * @throws NotFoundException
     * @throws ServerException
     */
    public function testCreatePlateType(){
        $now=time();
        $params=json_decode(str_replace('[[TIME]]', $now, self::PLATETYPE_JSON), true);
        $result=PlateImport::createPlateType($params);
        self::assertNotEmpty($result);
        self::assertEquals('123,RRR',$result['dropmapping']);
    }

    /**
     * @throws BadRequestException
     * @throws ForbiddenException
     * @throws NotFoundException
     * @throws ServerException
     */
    public function testCreatePlateTypeAlreadyExists(){
        $now=time();
        $params=json_decode(str_replace('[[TIME]]', $now, self::PLATETYPE_JSON), true);
        $result=PlateImport::createPlateType($params);
        self::assertNotNull($result);
        self::assertNotEmpty($result);
        self::assertEquals('123,RRR',$result['dropmapping']);
        $result=PlateImport::createPlateType($params);
        self::assertNotEmpty($result);
        self::assertEquals('123,RRR',$result['dropmapping']);
    }

    /**
     * @throws BadRequestException
     * @throws ForbiddenException
     * @throws NotFoundException
     * @throws ServerException
     */
    public function testCreatePlateTypeAlreadyExistsDimensionsMismatch(){
        $now=time();
        $params=json_decode(str_replace('[[TIME]]', $now, self::PLATETYPE_JSON), true);
        $result=PlateImport::createPlateType($params);
        self::assertNotNull($result);
        self::assertNotEmpty($result);
        self::assertEquals('123,RRR',$result['dropmapping']);
        $params['rows']=6;
        self::expectExceptionMessage(PlateImport::PLATETYPE_DIMENSIONS_MISMATCH_MESSAGE);
        PlateImport::createPlateType($params);
    }

    /**
     * @throws BadRequestException
     * @throws ForbiddenException
     * @throws NotFoundException
     * @throws ServerException
     */
    public function testCreateUser(){
        $now=time();
        $params=json_decode(str_replace('[[TIME]]', $now, self::USER_JSON), true);
        $result=PlateImport::createUser($params);
        self::assertNotEmpty($result);
    }

    /**
     * @throws BadRequestException
     * @throws ForbiddenException
     * @throws NotFoundException
     * @throws ServerException
     */
    public function testCreateUserAlreadyExists(){
        $now=time();
        $params=json_decode(str_replace('[[TIME]]', $now, self::USER_JSON), true);
        $result=PlateImport::createUser($params);
        self::assertNotEmpty($result);
        $result=PlateImport::createUser($params);
        self::assertNotEmpty($result);
    }

    /**
     * @throws BadRequestException
     * @throws ForbiddenException
     * @throws NotFoundException
     * @throws ServerException
     */
    public function testCreateUserBadEmail(){
        $now=time();
        $params=json_decode(str_replace('[[TIME]]', $now, self::USER_JSON), true);
        $params['email']='ThisIsNotAValidEmailAddress';
        self::expectExceptionMessage(PlateImport::USER_BAD_EMAIL_MESSAGE);
        PlateImport::createUser($params);
    }

    /**
     * @throws BadRequestException
     * @throws ForbiddenException
     * @throws NotFoundException
     * @throws ServerException
     */
    public function testCreateAndGetScoringSystem(){
        $now=time();
        $params=json_decode(str_replace('[[TIME]]', $now, self::SCORING_SYSTEM_JSON), true);
        $result=PlateImport::createScoringSystem($params);
        self::assertNotEmpty($result);
        self::assertArrayHasKey('name', $result);
        self::assertArrayHasKey('scores', $result);
        self::assertCount(2, $result['scores']);
    }

    /**
     * @throws BadRequestException
     * @throws ForbiddenException
     * @throws NotFoundException
     * @throws ServerException
     */
    public function testCreateProteinAndSequences(){
        $now=time();
        $params=json_decode(str_replace('[[TIME]]', $now, self::USER_JSON), true);
        $user=PlateImport::createUser($params);
		self::assertNotNull($user);
		self::assertArrayHasKey('receiversId',$user);
        $params=json_decode(str_replace('[[TIME]]', $now, self::PROTEIN_CONSTRUCTS_JSON), true);
        $params=$params[0];
        $result=PlateImport::createProteinAndConstructs($params, $user['receiversId']);
        self::assertNotNull($result);
        self::assertArrayHasKey('constructs', $result);
        self::assertArrayHasKey('receiversId', $result);
        self::assertCount(count($result['constructs']), $params['constructs'], 'Mismatch between sent and created construct count');
        session::becomeAdmin();
        self::assertArrayHasKey('receiversId', $result['constructs'][0]);
        $sequences=construct::getsequences($result['constructs'][0]['receiversId']);
        self::assertCount(count($params['constructs'][0]['sequences']), $sequences['rows'], 'Sequence count mismatch');
    }

    /**
     * @throws BadRequestException
     * @throws ForbiddenException
     * @throws NotFoundException
     * @throws ServerException
     */
    public function testCreateProteinNoSequences(){
        $now=time();
        $params=json_decode(str_replace('[[TIME]]', $now, self::USER_JSON), true);
        $user=PlateImport::createUser($params);
        $params=json_decode(str_replace('[[TIME]]', $now, self::PROTEIN_CONSTRUCTS_JSON), true);
        $params=$params[0];
        unset($params['constructs'][0]['sequences']);
        $result=PlateImport::createProteinAndConstructs($params, $user['receiversId']);
        self::assertNotNull($result);
        self::assertArrayHasKey('constructs', $result);
        self::assertArrayHasKey('receiversId', $result);
        self::assertCount(count($result['constructs']), $params['constructs'], 'Mismatch between sent and created construct count');
        self::assertArrayHasKey('receiversId', $result['constructs'][0]);
    }

    /**
     * @throws BadRequestException
     * @throws ForbiddenException
     * @throws NotFoundException
     * @throws ServerException
     */
    public function testCreateProteinAndSequencesBadSequence(){
        $now=time();
        $params=json_decode(str_replace('[[TIME]]', $now, self::USER_JSON), true);
        $user=PlateImport::createUser($params);
        $params=json_decode(str_replace('[[TIME]]', $now, self::PROTEIN_CONSTRUCTS_JSON), true);
        $params=$params[0];
        $params['constructs'][0]['sequences'][]='UUU';
        self::expectExceptionMessage(PlateImport::BAD_SEQUENCE_MESSAGE);
        PlateImport::createProteinAndConstructs($params, $user['receiversId']);
    }

    /**
     * @throws BadRequestException
     * @throws ForbiddenException
     * @throws NotFoundException
     * @throws ServerException
     */
    public function testCreateProteinAndSequencesAlreadyExist(){
        $now=time();
        $params = json_decode(str_replace('[[TIME]]', $now, self::USER_JSON), true);
        $user = PlateImport::createUser($params);
        $params = json_decode(str_replace('[[TIME]]', $now, self::PROTEIN_CONSTRUCTS_JSON), true);
        $params=$params[0];
        $result = PlateImport::createProteinAndConstructs($params, $user['receiversId']);
        self::assertCount(count($result['constructs']), $params['constructs'], 'Mismatch between sent and created construct count');
        $result = PlateImport::createProteinAndConstructs($params, $user['receiversId']);
        self::assertCount(count($result['constructs']), $params['constructs'], 'Mismatch between sent and created construct count');
    }

    /**
     * @throws BadRequestException
     * @throws ForbiddenException
     * @throws NotFoundException
     * @throws ServerException
     */
    public function testCreateProteinAndSequencesWithSequenceMismatch(){
        $now=time();
        $params = json_decode(str_replace('[[TIME]]', $now, self::USER_JSON), true);
        $user = PlateImport::createUser($params);
        $params = json_decode(str_replace('[[TIME]]', $now, self::PROTEIN_CONSTRUCTS_JSON), true);
        $params=$params[0];
        $result = PlateImport::createProteinAndConstructs($params, $user['receiversId']);
        self::assertCount(count($result['constructs']), $params['constructs'], 'Mismatch between sent and created construct count');
        $params['constructs'][0]['sequences'][0]='GGG';
        $params['constructs'][0]['sequences'][]='TTT';
        $result = PlateImport::createProteinAndConstructs($params, $user['receiversId']);
        self::assertCount(count($result['constructs']), $params['constructs'], 'Mismatch between sent and created construct count');
        $constructId=$result['constructs'][0]['receiversId'];
        $sequences=construct::getsequences($constructId);
        self::assertArrayHasKey('rows',$sequences);
        $sequences=$sequences['rows'];
        self::assertCount(count($result['constructs'][0]['sequences']), $sequences, 'Mismatch between sent and created sequence count');
        for($i=0;$i<count($sequences);$i++){
            $sent=preg_replace('/[^A-Z]/','',strtoupper($params['constructs'][0]['sequences'][$i]));
            $createdDnaSequence=preg_replace('/[^A-Z]/','',strtoupper($sequences[$i]['dnasequence']));
            $createdProteinSequence=preg_replace('/[^A-Z]/','',strtoupper($sequences[$i]['proteinsequence']));
            self::assertTrue($sent==$createdDnaSequence || $sent==$createdProteinSequence);
        }
    }

    /**
     * @throws BadRequestException
     * @throws ForbiddenException
     * @throws NotFoundException
     * @throws NotModifiedException
     * @throws ServerException
     */
    public function testCreatePlateNoProteinConstruct(){
        $now=time();
        $scoringSystem=json_decode(str_replace('[[TIME]]', $now, self::SCORING_SYSTEM_JSON), true);
        $scoringSystem=PlateImport::createScoringSystem($scoringSystem);
        $json=self::PLATE_JSON;
        $json=str_replace('[[SCORING_SYSTEM_NAME]]',$scoringSystem['name'], $json);
        $json=str_replace('[[PROTEIN_CONSTRUCTS_JSON]]','null', $json);
        $json=str_replace('[[PLATETYPE_JSON]]',static::PLATETYPE_JSON, $json);
        $json=str_replace('[[USER_JSON]]', static::USER_JSON, $json);
        $params = json_decode(str_replace('[[TIME]]', $now, $json), true);
        PlateImport::createPlate($params);
        $plate=plate::getByName($params['barcode']);
        self::assertNotNull($plate);
        self::assertEquals(baseproject::DEFAULTPROJECT, $plate['projectname']);
        $drops=plate::getwelldrops($plate['id']);
        self::assertArrayHasKey('rows', $drops);
        self::assertCount(288, $drops['rows'], 'Plate drop count mismatch');
    }

    /**
     * @throws BadRequestException
     * @throws ForbiddenException
     * @throws NotFoundException
     * @throws NotModifiedException
     * @throws ServerException
     */
    public function testCreatePlateSingleProteinConstruct(){
        $now=time();
        $scoringSystem=json_decode(str_replace('[[TIME]]', $now, self::SCORING_SYSTEM_JSON), true);
        $scoringSystem=PlateImport::createScoringSystem($scoringSystem);
        $json=self::PLATE_JSON;
        $json=str_replace('[[PROTEIN_CONSTRUCTS_JSON]]',static::PROTEIN_CONSTRUCTS_JSON, $json);
        $json=str_replace('[[SCORING_SYSTEM_NAME]]',$scoringSystem['name'], $json);
        $json=str_replace('[[PLATETYPE_JSON]]',static::PLATETYPE_JSON, $json);
        $json=str_replace('[[USER_JSON]]', static::USER_JSON, $json);
        $params = json_decode(str_replace('[[TIME]]', $now, $json), true);
        $result=PlateImport::createPlate($params);
        self::assertArrayHasKey('receiversId',$result, 'receiversId not set on plate');
        self::assertArrayHasKey('proteins',$result, 'proteins not set on plate');
        $plate=plate::getByName($params['barcode']);
        self::assertNotNull($plate);
        self::assertEquals($params['proteins'][0]['name'], $plate['projectname']);
        $drops=plate::getwelldrops($plate['id']);
        self::assertArrayHasKey('rows', $drops);
        self::assertCount(288, $drops['rows'], 'Plate drop count mismatch');
        $constructs=plate::getconstructs($plate['id']);
        self::assertArrayHasKey('rows', $constructs);
        self::assertEquals($result['proteins'][0]['constructs'][0]['receiversId'], $constructs['rows'][0]['id']);
        self::assertEquals($result['proteins'][0]['constructs'][0]['receiversId'], $drops['rows'][0]['constructid']);
        self::assertEquals($result['proteins'][0]['constructs'][0]['receiversId'], $drops['rows'][287]['constructid']);
    }

    /**
     * @throws BadRequestException
     * @throws ForbiddenException
     * @throws NotFoundException
     * @throws NotModifiedException
     * @throws ServerException
     */
    public function testCreatePlateSingleProteinConstructNoProteinAcronym(){
        $now=time();
        $scoringSystem=json_decode(str_replace('[[TIME]]', $now, self::SCORING_SYSTEM_JSON), true);
        $scoringSystem=PlateImport::createScoringSystem($scoringSystem);
        $json=self::PLATE_JSON;
        $json=str_replace('[[PROTEIN_CONSTRUCTS_JSON]]',static::PROTEIN_CONSTRUCTS_JSON, $json);
        $json=str_replace('[[SCORING_SYSTEM_NAME]]',$scoringSystem['name'], $json);
        $json=str_replace('[[PLATETYPE_JSON]]',static::PLATETYPE_JSON, $json);
        $json=str_replace('[[USER_JSON]]', static::USER_JSON, $json);
        $params = json_decode(str_replace('[[TIME]]', $now, $json), true);
        unset($params["proteins"][0]["proteinAcronym"]);
        $result=PlateImport::createPlate($params);
        if(isset($result['error'])){ echo $result['error']; }
        self::assertArrayNotHasKey('error',$result, 'Unexpected error response');

        self::assertArrayHasKey('receiversId',$result, 'receiversId not set on plate');
        self::assertArrayHasKey('proteins',$result, 'proteins not set on plate');
        $plate=plate::getByName($params['barcode']);
        self::assertNotNull($plate);
        self::assertEquals($params['proteins'][0]['name'], $plate['projectname']);
        $drops=plate::getwelldrops($plate['id']);
        self::assertArrayHasKey('rows', $drops);
        self::assertCount(288, $drops['rows'], 'Plate drop count mismatch');
        $constructs=plate::getconstructs($plate['id']);
        self::assertArrayHasKey('rows', $constructs);
        self::assertEquals($result['proteins'][0]['constructs'][0]['receiversId'], $constructs['rows'][0]['id']);
        self::assertEquals($result['proteins'][0]['constructs'][0]['receiversId'], $drops['rows'][0]['constructid']);
        self::assertEquals($result['proteins'][0]['constructs'][0]['receiversId'], $drops['rows'][287]['constructid']);
    }

    /**
     * @throws BadRequestException
     * @throws ForbiddenException
     * @throws NotFoundException
     * @throws NotModifiedException
     * @throws ServerException
     */
    public function testCreatePlateMultipleProteins(){
        $now=time();
        $scoringSystem=json_decode(str_replace('[[TIME]]', $now, self::SCORING_SYSTEM_JSON), true);
        $scoringSystem=PlateImport::createScoringSystem($scoringSystem);
        $json=self::PLATE_JSON;
        $json=str_replace('[[PROTEIN_CONSTRUCTS_JSON]]',static::PROTEIN_CONSTRUCTS_JSON, $json);
        $json=str_replace('[[SCORING_SYSTEM_NAME]]',$scoringSystem['name'], $json);
        $json=str_replace('[[PLATETYPE_JSON]]',static::PLATETYPE_JSON, $json);
        $json=str_replace('[[USER_JSON]]', static::USER_JSON, $json);
        $params = json_decode(str_replace('[[TIME]]', $now, $json), true);
        $proteins=json_decode(str_replace('[[TIME]]', $now, self::PROTEIN_CONSTRUCTS_JSON), true);
        $proteins[0]['name'].='_2';
        $params['proteins'][]=$proteins[0];
        self::expectExceptionMessage(PlateImport::ONLY_ONE_CONSTRUCT_PER_PLATE_MESSAGE);
        PlateImport::createPlate($params);
    }

    /**
     * @throws BadRequestException
     * @throws ForbiddenException
     * @throws NotFoundException
     * @throws NotModifiedException
     * @throws ServerException
     */
    public function testCreatePlateMultipleConstructs(){
        $now=time();
        $scoringSystem=json_decode(str_replace('[[TIME]]', $now, self::SCORING_SYSTEM_JSON), true);
        $scoringSystem=PlateImport::createScoringSystem($scoringSystem);
        $json=self::PLATE_JSON;
        $json=str_replace('[[PROTEIN_CONSTRUCTS_JSON]]',static::PROTEIN_CONSTRUCTS_JSON, $json);
        $json=str_replace('[[SCORING_SYSTEM_NAME]]',$scoringSystem['name'], $json);
        $json=str_replace('[[PLATETYPE_JSON]]',static::PLATETYPE_JSON, $json);
        $json=str_replace('[[USER_JSON]]', static::USER_JSON, $json);
        $params = json_decode(str_replace('[[TIME]]', $now, $json), true);
        $proteins=json_decode(str_replace('[[TIME]]', $now, self::PROTEIN_CONSTRUCTS_JSON), true);
        $constructs=$proteins[0]['constructs'];
        $constructs[0]['name'].='_2';
        $params['proteins'][0]['constructs']=$constructs[0];
        self::expectExceptionMessage(PlateImport::ONLY_ONE_CONSTRUCT_PER_PLATE_MESSAGE);
        PlateImport::createPlate($params);
    }

    /**
     * @throws BadRequestException
     * @throws ForbiddenException
     * @throws NotFoundException
     * @throws NotModifiedException
     * @throws ServerException
     * @throws Exception
     */
    public function testCreateImageAndInspectionWithPlate(){
        $now=time();
        $scoringSystem=json_decode(str_replace('[[TIME]]', $now, self::SCORING_SYSTEM_JSON), true);
        $scoringSystem=PlateImport::createScoringSystem($scoringSystem);
        $json=self::PLATE_JSON;
        $json=str_replace('[[PROTEIN_CONSTRUCTS_JSON]]',static::PROTEIN_CONSTRUCTS_JSON, $json);
        $json=str_replace('[[SCORING_SYSTEM_NAME]]',$scoringSystem['name'], $json);
        $json=str_replace('[[PLATETYPE_JSON]]',static::PLATETYPE_JSON, $json);
        $json=str_replace('[[USER_JSON]]', static::USER_JSON, $json);
        $params = json_decode(str_replace('[[TIME]]', $now, $json), true);

        $barcode = $params['barcode'];

        $params['imageBatches']=[];
        $json=str_replace('[[IMAGE_JSON]]', self::IMAGE_JSON, self::IMAGING_SESSION_JSON);
        $json=str_replace('[[CRYSTAL_JSON]]', self::CRYSTAL_JSON, $json);
        $json=str_replace('[[TIME]]', $now, $json);
        $testDir=__DIR__.'/../../';
        $imagingSession=json_decode($json, true);
        $imagingSession['images'][0]['imageData']=convert_uuencode(file_get_contents($testDir.'testVisibleImage.jpg'));
        $imagingSession['images'][0]['thumbData']=convert_uuencode(file_get_contents($testDir.'testVisibleThumb.jpg'));
        $params['imageBatches'][]=$imagingSession;

        $imager=json_decode(str_replace('[[TIME]]', $now, self::IMAGER_JSON), true);
        $imager=PlateImport::createImager($imager);
        self::assertArrayHasKey('name',$imager);
        self::assertArrayHasKey('receiversId',$imager);
        try {
            $plate = PlateImport::createPlate($params);
            $plateDirectory=rtrim(config::get('core_imagestore'),'/').'/'.substr($barcode,0,2).'/'.substr($barcode,0,3).'/'.$barcode.'/';
            self::assertArrayHasKey('imageBatches', $plate);
            self::assertArrayHasKey('receiversId', $plate['imageBatches'][0]);
            self::assertNotEmpty($plate['imageBatches'][0]);
            self::assertArrayHasKey('images', $plate['imageBatches'][0]);
            self::assertNotEmpty($plate['imageBatches'][0]['images']);
            self::assertArrayHasKey('receiversId', $plate['imageBatches'][0]['images'][0]);
            $imagingSessionId=$plate['imageBatches'][0]['receiversId'];
            self::assertFileExists($plateDirectory);
            self::assertFileExists($plateDirectory.'imagingsession'.$imagingSessionId);
            self::assertFileExists($plateDirectory.'imagingsession'.$imagingSessionId.'/A01_1.jpg');
            self::assertFileExists($plateDirectory.'imagingsession'.$imagingSessionId.'/thumbs');
            self::assertFileExists($plateDirectory.'imagingsession'.$imagingSessionId.'/thumbs/A01_1.jpg');

            $plateWell=platewell::getByProperties(array(
                'plateid'=>$plate['receiversId'],
                'row'=>1,
                'col'=>1
            ))['rows'][0];
            $wellDrop=welldrop::getByProperties(array(
                'platewellid'=>$plateWell['id'],
                'dropnumber'=>1
            ))['rows'][0];

            $image=dropimage::getByProperties(array(
                'imagingsessionid'=>$imagingSessionId,
                'welldropid'=>$wellDrop['id']
            ))['rows'][0];

            $crystals=crystal::getByProperties(array(
                'dropimageid'=>$image['id']
            ));
            self::assertNotEmpty($crystals);

        } finally {
            $this->removePlateDirectory($barcode);
        }
    }

    /**
     * @throws BadRequestException
     * @throws ForbiddenException
     * @throws NotFoundException
     * @throws NotModifiedException
     * @throws ServerException
     * @throws Exception
     */
    public function testCreateImageAndInspectionAfterPlate(){
        $now=time();
        $scoringSystem=json_decode(str_replace('[[TIME]]', $now, self::SCORING_SYSTEM_JSON), true);
        $scoringSystem=PlateImport::createScoringSystem($scoringSystem);
        $json=self::PLATE_JSON;
        $json=str_replace('[[PROTEIN_CONSTRUCTS_JSON]]',static::PROTEIN_CONSTRUCTS_JSON, $json);
        $json=str_replace('[[SCORING_SYSTEM_NAME]]',$scoringSystem['name'], $json);
        $json=str_replace('[[PLATETYPE_JSON]]',static::PLATETYPE_JSON, $json);
        $json=str_replace('[[USER_JSON]]', static::USER_JSON, $json);
        $params = json_decode(str_replace('[[TIME]]', $now, $json), true);
        $barcode = $params['barcode'];

        try {
            $plate = PlateImport::createPlate($params);
            self::assertArrayHasKey('receiversId',$plate);
            self::assertArrayNotHasKey('imagingSessions', $plate);

            $imager=json_decode(str_replace('[[TIME]]', $now, self::IMAGER_JSON), true);
            $imager=PlateImport::createImager($imager);
            self::assertArrayHasKey('name',$imager);
            self::assertArrayHasKey('receiversId',$imager);

            $json=str_replace('[[IMAGE_JSON]]', self::IMAGE_JSON, self::IMAGING_SESSION_JSON);
            $json=str_replace('[[CRYSTAL_JSON]]', self::CRYSTAL_JSON, $json);
            $json=str_replace('[[TIME]]', $now, $json);
            $imagingSession=json_decode($json, true);
            $testDir=__DIR__.'/../../';
            $imagingSession['images'][0]['imageData']=convert_uuencode(file_get_contents($testDir.'testVisibleImage.jpg'));
            $imagingSession['images'][0]['thumbData']=convert_uuencode(file_get_contents($testDir.'testVisibleThumb.jpg'));
            $imagingSession['plateBarcode']=$barcode;
            $imagingSession=PlateImport::createImageBatch($imagingSession);

            $plateDirectory=rtrim(config::get('core_imagestore'),'/').'/'.substr($barcode,0,2).'/'.substr($barcode,0,3).'/'.$barcode.'/';
            self::assertFileExists($plateDirectory);
            self::assertFileExists($plateDirectory.'imagingsession'.$imagingSession['receiversId']);
            self::assertFileExists($plateDirectory.'imagingsession'.$imagingSession['receiversId'].'/A01_1.jpg');
            self::assertFileExists($plateDirectory.'imagingsession'.$imagingSession['receiversId'].'/thumbs');
            self::assertFileExists($plateDirectory.'imagingsession'.$imagingSession['receiversId'].'/thumbs/A01_1.jpg');

        } finally {
            $this->removePlateDirectory($barcode);
        }
        self::assertArrayHasKey('receiversId', $imagingSession);
        self::assertArrayHasKey('images', $imagingSession);
        self::assertNotEmpty($imagingSession['images'][0]);
        self::assertArrayHasKey('receiversId', $imagingSession['images'][0]);
    }

    /**
     * @throws BadRequestException
     * @throws ForbiddenException
     * @throws NotFoundException
     * @throws NotModifiedException
     * @throws ServerException
     * @throws Exception
     */
    public function testCreateMultipleImagesInInspectionAfterPlate(){
        $now=time();
        $scoringSystem=json_decode(str_replace('[[TIME]]', $now, self::SCORING_SYSTEM_JSON), true);
        $scoringSystem=PlateImport::createScoringSystem($scoringSystem);
        $json=self::PLATE_JSON;
        $json=str_replace('[[PROTEIN_CONSTRUCTS_JSON]]',static::PROTEIN_CONSTRUCTS_JSON, $json);
        $json=str_replace('[[SCORING_SYSTEM_NAME]]',$scoringSystem['name'], $json);
        $json=str_replace('[[PLATETYPE_JSON]]',static::PLATETYPE_JSON, $json);
        $json=str_replace('[[USER_JSON]]', static::USER_JSON, $json);
        $params = json_decode(str_replace('[[TIME]]', $now, $json), true);
        $barcode = $params['barcode'];

        try {
            $plate = PlateImport::createPlate($params);
            self::assertArrayHasKey('receiversId',$plate);
            self::assertArrayNotHasKey('imagingSessions', $plate);

            $imager=json_decode(str_replace('[[TIME]]', $now, self::IMAGER_JSON), true);
            $imager=PlateImport::createImager($imager);
            self::assertArrayHasKey('name',$imager);
            self::assertArrayHasKey('receiversId',$imager);

            $json=str_replace('[[IMAGE_JSON]]', self::IMAGE_JSON, self::IMAGING_SESSION_JSON);
            $json=str_replace('[[CRYSTAL_JSON]]', self::CRYSTAL_JSON, $json);
            $json=str_replace('[[TIME]]', $now, $json);
            $imageBatch1=json_decode($json, true);
            $testDir=__DIR__.'/../../';
            $imageBatch1['images'][0]['imageData']=convert_uuencode(file_get_contents($testDir.'testVisibleImage.jpg'));
            $imageBatch1['images'][0]['thumbData']=convert_uuencode(file_get_contents($testDir.'testVisibleThumb.jpg'));
            $imageBatch1['plateBarcode']=$barcode;
            $imageBatch1=PlateImport::createImageBatch($imageBatch1);

            $imageBatch2=json_decode($json, true);
            $imageBatch2['images'][0]['imageData']=convert_uuencode(file_get_contents($testDir.'testVisibleImage.jpg'));
            $imageBatch2['images'][0]['thumbData']=convert_uuencode(file_get_contents($testDir.'testVisibleThumb.jpg'));
            $imageBatch2['images'][0]['rowNumber']=8;
            $imageBatch2['images'][0]['columnNumber']=12;
            $imageBatch2['images'][0]['subPositionNumber']=3;
            $imageBatch2['plateBarcode']=$barcode;
            $imageBatch2=PlateImport::createImageBatch($imageBatch2);

            self::assertEquals($imageBatch1['receiversId'], $imageBatch2['receiversId'], 'Imaging session IDs should match but do not');

            $plateDirectory=rtrim(config::get('core_imagestore'),'/').'/'.substr($barcode,0,2).'/'.substr($barcode,0,3).'/'.$barcode.'/';
            self::assertFileExists($plateDirectory);
            self::assertFileExists($plateDirectory.'imagingsession'.$imageBatch1['receiversId']);
            self::assertFileExists($plateDirectory.'imagingsession'.$imageBatch1['receiversId'].'/A01_1.jpg');
            self::assertFileExists($plateDirectory.'imagingsession'.$imageBatch1['receiversId'].'/H12_3.jpg');
            self::assertFileExists($plateDirectory.'imagingsession'.$imageBatch1['receiversId'].'/thumbs');
            self::assertFileExists($plateDirectory.'imagingsession'.$imageBatch1['receiversId'].'/thumbs/H12_3.jpg');

        } finally {
            $this->removePlateDirectory($barcode);
        }
        self::assertArrayHasKey('receiversId', $imageBatch1);
        self::assertArrayHasKey('images', $imageBatch1);
        self::assertNotEmpty($imageBatch1['images'][0]);
        self::assertArrayHasKey('receiversId', $imageBatch1['images'][0]);
    }

    /**
     * @param $iceBearInstance
     * @dataProvider getIceBearInstances
     * @runInSeparateProcess
     * @throws Exception
     */
    public function testCreatePlateThenImagesOverHttp($iceBearInstance){

        $iceBearRoot=rtrim($iceBearInstance['uri'], '/').'/';

        //Log into the IceBear instance
        $auth=json_decode(self::post($iceBearRoot.'api/Login', array(
            'username'=>$iceBearInstance['username'],
            'password'=>$iceBearInstance['password']
        )), true);
        self::assertArrayHasKey('sid', $auth);
        self::assertArrayHasKey('success', $auth);
        self::assertArrayHasKey('csrfToken', $auth);
        self::assertTrue($auth['success']);
        $sessionId=$auth['sid'];

        //Get prerequisite - scoring system
        $scoringSystems=json_decode(self::get($iceBearRoot.'api/crystalscoringsystem', null, '', $sessionId), true);
        self::assertArrayNotHasKey('error', $scoringSystems);
        self::assertArrayHasKey('rows', $scoringSystems);
        self::assertGreaterThanOrEqual(1, count($scoringSystems['rows']));
        $scoringSystemName=$scoringSystems['rows'][0]['name'];

        //Get prerequisite - imager
        $imagers=json_decode(self::get($iceBearRoot.'api/imager', null, '', $sessionId), true);
        self::assertArrayNotHasKey('error', $imagers);
        self::assertArrayHasKey('rows', $imagers);
        self::assertGreaterThanOrEqual(1, count($imagers['rows']));
        $imagerName=$imagers['rows'][0]['name'];

        //Log out of remote IceBear
        $auth=json_decode(self::post($iceBearRoot.'api/Logout', null,'', $sessionId), true);
        self::assertArrayHasKey('success', $auth);
        self::assertTrue($auth['success']);

        //Post to CreatePlate using Device API and API key
        $now=new DateTime();
        $barcode='TEST'.$now->format('Ymdhis');
        $testDir=__DIR__.'/../../';
        $imageData=convert_uuencode(file_get_contents($testDir.'testVisibleImage.jpg'));
        $thumbData=convert_uuencode(file_get_contents($testDir.'testVisibleThumb.jpg'));
        $json='{
              "barcode":"'.$barcode.'", "scoringSystemName":"'.$scoringSystemName.'",
              "plateType":{
                    "name":"3-drop plate", "rows":8, "columns":12, "subPositions":3
              },
              "owner":{
                    "fullName":"IceBear Test User", "email":"'.strtolower($iceBearInstance['username']).'@bogus.bogus"
              },
              "proteins":[
                    {
                          "name":"IceBear Test Protein", "proteinAcronym":"TEST", "constructs":[
                                { "name":"Complex 1", "sequences":["atcagtcgatcgtacgat","AGT CCA TGT","TWGWADWAA"] }
                          ]
                    }
              ],
              "imageBatches":[
                    {     
                            "plateBarcode":"'.$barcode.'", "dateTimeUtc":"'.$now->format('Y-m-d H:i:s').'", 
                            "imagerName":"'.$imagerName.'", "lightType":"Visible", "images":[
                                {
                                     "rowNumber":1, "columnNumber":1, "subPositionNumber":1, "imageData":"", 
                                     "thumbData":"", "name":"A01.1", "scoreName":"Crystals", "micronsPerPixel":"2.15",
                                     "marks":[
                                            { "type":"point", "pixelX":"100", "pixelY":"200" }
                                      ]
                                }
                          ]
                    }
              ]
        }';
        $params=json_decode($json, true);
        $params['imageBatches'][0]['images'][0]['imageData']=$imageData;
        $params['imageBatches'][0]['images'][0]['thumbData']=$thumbData;
        $json=json_encode($params);
        $result=self::post($iceBearRoot.'device/PlateImport/createPlate', $json, $iceBearInstance['apiKey']);
        self::assertNotEmpty($result, 'Response was empty');
        $result=json_decode($result, true);
        self::assertNotEmpty($result, 'Response was not valid JSON');
		self::assertArrayNotHasKey('error', $result, 'Error on calling PlateImport/createPlate: '.$result['error']);
		$plateId=$result['id'];

        //Log into the IceBear instance
        $auth=json_decode(self::post($iceBearRoot.'api/Login', array(
            'username'=>$iceBearInstance['username'],
            'password'=>$iceBearInstance['password']
        )), true);
        self::assertArrayHasKey('sid', $auth);
        self::assertArrayHasKey('success', $auth);
        self::assertArrayHasKey('csrfToken', $auth);
        self::assertTrue($auth['success']);
        $sessionId=$auth['sid'];

        //Get plate
        $plate=self::get($iceBearRoot.'api/plate/'.$plateId, null, '', $sessionId);
		$plate=json_decode($plate, true);
        self::assertNotNull($plate);
        self::assertArrayNotHasKey('error', $plate, $iceBearInstance['name']." - Error retrieving plate: ".$plate['error'].' ['.$plate['requestMethod'].' '.$plate['requestUri'].']');
        self::assertArrayHasKey('name', $plate);
		self::assertEquals($barcode, $plate['name']);

        //Log out of remote IceBear
        $auth=json_decode(self::post($iceBearRoot.'api/Logout', null,'', $sessionId), true);
        self::assertArrayHasKey('success', $auth);
        self::assertTrue($auth['success']);

    }


    /**
     * @param $iceBearInstance
     * @dataProvider getIceBearInstances
     * @runInSeparateProcess
     * @throws Exception
     */
    public function testCreatePlateThenImagesOverHttpNoProteinAcronym($iceBearInstance){

        $iceBearRoot=rtrim($iceBearInstance['uri'], '/').'/';

        //Log into the IceBear instance
        $auth=json_decode(self::post($iceBearRoot.'api/Login', array(
            'username'=>$iceBearInstance['username'],
            'password'=>$iceBearInstance['password']
        )), true);
        self::assertArrayHasKey('sid', $auth);
        self::assertArrayHasKey('success', $auth);
        self::assertArrayHasKey('csrfToken', $auth);
        self::assertTrue($auth['success']);
        $sessionId=$auth['sid'];

        //Get prerequisite - scoring system
        $scoringSystems=json_decode(self::get($iceBearRoot.'api/crystalscoringsystem', null, '', $sessionId), true);
        self::assertArrayNotHasKey('error', $scoringSystems);
        self::assertArrayHasKey('rows', $scoringSystems);
        self::assertGreaterThanOrEqual(1, count($scoringSystems['rows']));
        $scoringSystemName=$scoringSystems['rows'][0]['name'];

        //Get prerequisite - imager
        $imagers=json_decode(self::get($iceBearRoot.'api/imager', null, '', $sessionId), true);
        self::assertArrayNotHasKey('error', $imagers);
        self::assertArrayHasKey('rows', $imagers);
        self::assertGreaterThanOrEqual(1, count($imagers['rows']));
        $imagerName=$imagers['rows'][0]['name'];

        //Log out of remote IceBear
        $auth=json_decode(self::post($iceBearRoot.'api/Logout', null,'', $sessionId), true);
        self::assertArrayHasKey('success', $auth);
        self::assertTrue($auth['success']);

        //Post to CreatePlate using Device API and API key
        $now=new DateTime();
        $barcode='TEST'.$now->format('Ymdhis').'X';
        $testDir=__DIR__.'/../../';
        $imageData=convert_uuencode(file_get_contents($testDir.'testVisibleImage.jpg'));
        $thumbData=convert_uuencode(file_get_contents($testDir.'testVisibleImage.jpg'));
        $json='{
              "barcode":"'.$barcode.'", "scoringSystemName":"'.$scoringSystemName.'",
              "plateType":{
                    "name":"3-drop plate", "rows":8, "columns":12, "subPositions":3
              },
              "owner":{
                    "fullName":"IceBear Test User", "email":"'.strtolower($iceBearInstance['username']).'@bogus.bogus"
              },
              "proteins":[
                    {
                          "name":"IceBear Test No Acronym", "constructs":[
                                { "name":"Complex 1", "sequences":["atcagtcgatcgtacgat","AGT CCA TGT","TWGWADWAA"] }
                          ]
                    }
              ],
              "imageBatches":[
                    {     
                            "plateBarcode":"'.$barcode.'", "dateTimeUtc":"'.$now->format('Y-m-d H:i:s').'", 
                            "imagerName":"'.$imagerName.'", "lightType":"Visible", "images":[
                                {
                                     "rowNumber":1, "columnNumber":1, "subPositionNumber":1, "imageData":"", 
                                     "thumbData":"", "name":"A01.1", "scoreName":"Crystals", "micronsPerPixel":"2.15",
                                     "marks":[
                                            { "type":"point", "pixelX":"100", "pixelY":"200" }
                                      ]
                                }
                          ]
                    }
              ]
        }';
        $params=json_decode($json, true);
        $params['imageBatches'][0]['images'][0]['imageData']=$imageData;
        $params['imageBatches'][0]['images'][0]['thumbData']=$thumbData;
        $json=json_encode($params);

        $result=self::post($iceBearRoot.'device/PlateImport/createPlate', $json, $iceBearInstance['apiKey']);
        self::assertNotEmpty($result, 'Response was empty');

        $json=json_decode($result, true);
        self::assertNotEmpty($json, 'Response was not valid JSON: '.$result);
		self::assertArrayNotHasKey('error', $json, 'Error on calling PlateImport/createPlate: '.$json['error']);
		$plateId=$json['id'];

        //Log into the IceBear instance
        $auth=json_decode(self::post($iceBearRoot.'api/Login', array(
            'username'=>$iceBearInstance['username'],
            'password'=>$iceBearInstance['password']
        )), true);
        self::assertArrayHasKey('sid', $auth);
        self::assertArrayHasKey('success', $auth);
        self::assertArrayHasKey('csrfToken', $auth);
        self::assertTrue($auth['success']);
        $sessionId=$auth['sid'];

        //Get plate
        $plate=json_decode(self::get($iceBearRoot.'api/plate/'.$plateId, null, '', $sessionId), true);
        self::assertNotNull($plate);
		self::assertArrayNotHasKey('error', $plate, $iceBearInstance['name']." - Error retrieving plate: ".$plate['error'].' ['.$plate['requestMethod'].' '.$plate['requestUri'].']');
        self::assertArrayNotHasKey('total', $plate);
		self::assertEquals($barcode, $plate['name']);

        //Log out of remote IceBear
        $auth=json_decode(self::post($iceBearRoot.'api/Logout', null,'', $sessionId), true);
        self::assertArrayHasKey('success', $auth);
        self::assertTrue($auth['success']);
    }

    public function testWithWisJson(){
        $testDir=__DIR__.'/../../../../';
        $testConfig=@file_get_contents($testDir.'testconfig.json');
        self::assertNotEmpty($testConfig);
        $testConfig=json_decode($testConfig, true);
        self::assertNotEmpty($testConfig);
        $apiKey=null;
        $uri=null;
        foreach($testConfig['plateImportTestInstallations'] as $instance){
            if(false!==stripos($instance['uri'],'icebear.ejd')){
                $apiKey=$instance['apiKey'];
                $uri=rtrim($instance['uri'],'/').'/';
            }
        }
        self::assertNotEmpty($apiKey);
        $json=@file_get_contents($testDir.'WIS_CreatePlate.json');
        self::assertNotEmpty($json);
        $result=self::post($uri.'device/PlateImport/createPlate', $json, $apiKey);
        $result=json_decode($result, true);
        self::assertNotEmpty($result);
    }

	/**
	 * Removes the plate's directory in the image store. For clean-up post-test.
	 * @param $plateBarcode string The plate barcode
	 * @throws ServerException
	 * @throws BadRequestException
	 */
    private function removePlateDirectory(string $plateBarcode): void {
        $imageStore=rtrim(config::get('core_imagestore'), '/').'/';
        $plateDir=$imageStore.substr($plateBarcode,0,2).'/';
        $plateDir=$plateDir.substr($plateBarcode,0,3).'/';
        $plateDir.=$plateBarcode.'/';
        if(!file_exists($plateDir)){ return; }
        $contents=scandir($plateDir);
        foreach($contents as $imagingSessionDirectory){
            if('.'===$imagingSessionDirectory || '..'===$imagingSessionDirectory){ continue; }
            $thumbs=scandir($plateDir.$imagingSessionDirectory.'/thumbs/');
            foreach($thumbs as $thumb){
                if('.'===$thumb || '..'===$thumb){ continue; }
                unlink($plateDir.$imagingSessionDirectory.'/thumbs/'.$thumb);
            }
            rmdir($plateDir.$imagingSessionDirectory.'/thumbs/');
            $images=scandir($plateDir.$imagingSessionDirectory);
            foreach($images as $image){
                if('.'===$image || '..'===$image){ continue; }
                unlink($plateDir.$imagingSessionDirectory.'/'.$image);
            }
            rmdir($plateDir.$imagingSessionDirectory);
        }
        rmdir($plateDir);
    }

	/**
	 * @param $url
	 * @param array|string|null $json
	 * @param string $key
	 * @param string $sid
	 * @return bool|mixed|string
	 * @throws Exception
	 */
    private function post($url, array|string|null $json, string $key='', string $sid=''): mixed {
        return self::httpRequest('POST', $url, $json, $key, $sid);
    }

    /**
     * @param $url
     * @param array|string|null $json
     * @param string $key
     * @param string $sid
     * @return bool|mixed|string
     * @throws Exception
     */
    private function get($url, array|string|null $json, string $key='', string $sid=''): mixed {
        return self::httpRequest('GET', $url, $json, $key, $sid);
    }

	/**
	 * @param $method
	 * @param string $url
	 * @param string|array|null $request
	 * @param string $key
	 * @param string $sid
	 * @return bool|mixed|string
	 * @throws Exception
	 */
    private function httpRequest($method, string $url, string|array|null $request, string $key='', string $sid=''): mixed {

		$requestHeaders=[];
        $method=strtoupper($method);
        $ch = curl_init($url);
        $options = array(
            CURLOPT_COOKIEFILE => '',
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_RETURNTRANSFER  => true,
            CURLOPT_HEADER          => false,
            CURLOPT_CONNECTTIMEOUT  => 20,
            CURLOPT_TIMEOUT         => 20,
            CURLOPT_SSL_VERIFYHOST  => 0,
            CURLOPT_SSL_VERIFYPEER  => false,
            CURLOPT_UNRESTRICTED_AUTH => true
        );
        if(''!==$key){
            $requestHeaders[]="X-IceBear-API-Key: ".$key;
            //Device API should also accept API key in an Authorization: Bearer header
            //$requestHeaders[]="Authorization: Bearer ".$key;
        }
        if(''!==$sid){
            curl_setopt($ch, CURLOPT_COOKIEFILE, self::COOKIE_FILE);
            curl_setopt($ch, CURLOPT_COOKIEJAR, self::COOKIE_FILE);
            curl_setopt($ch, CURLOPT_COOKIE, 'PHPSESSID='.$sid);
        }
        if('POST'==$method){
            $options[CURLOPT_POST]=1;
            if(isset($request['rawPostBody'])) {
				$options[CURLOPT_POSTFIELDS] = $request['rawPostBody'];
				if (false !== json_decode($request['rawPostBody'])) {
					$requestHeaders[] = 'Content-Type: application/json';
				}
			} else {
				if(is_string($request) && false!==json_decode($request)){
					$requestHeaders[] = 'Content-Type: application/json';
				}
				$options[CURLOPT_POSTFIELDS]=$request;
            }
        } else if('GET'!==$method){
            throw new Exception('Only GET/POST supported');
        }
		$options[CURLOPT_HTTPHEADER] = $requestHeaders;
        curl_setopt_array($ch,$options);
        $data = curl_exec($ch);

        $redirectUrl=curl_getinfo($ch, CURLINFO_REDIRECT_URL);
        curl_close($ch);
        if(empty($redirectUrl)){
            return $data;
        } else {
            return $redirectUrl;
        }
    }

    public static $iceBearInstances;

	public static function getIceBearInstances(){
		if(!isset(static::$iceBearInstances)) {
			static::$iceBearInstances = [];
			$config = @file_get_contents(__DIR__ . '/../../../../testconfig.json');
			$config = json_decode($config, true);
			foreach ($config['plateImportTestInstallations'] as $instance) {
				static::$iceBearInstances[] = array($instance);
			}
		}
		return static::$iceBearInstances;
	}

}