<?php
/**
 * Imports a group of one or more plates specified according to the ShipLink JSON Schema.
 * 
 */

class GenericImporterV0_2_0 {
    
    private static $currentPlate;
    private static $currentPlateOwner;
    private static $currentScoringSystem;
    private static $drives;
    
    /**
     * Parses a JSON string and imports the data into IceBear.
     * Caller must connect to the database and initialize a session with admin rights.
     * 
     * @param string $jsonString
     * @param int $logLevel
     * @throws Exception
     */
    public static function import(string $jsonString, int $logLevel=Log::LOGLEVEL_INFO): void {
        if(!Log::isInited()){
            //Caller should have initialized the log. If not, dump it to stdout.
            Log::init($logLevel);
        }
        Log::write(Log::LOGLEVEL_DEBUG, 'Beginning database transaction...');
        database::begin();
        Log::write(Log::LOGLEVEL_DEBUG, 'Begun.');
        try {
            
            Log::write(Log::LOGLEVEL_INFO, 'Getting list of mounted drives...');
            static::getDrives();
            Log::write(Log::LOGLEVEL_INFO, '...done.');
            
            
            Log::write(Log::LOGLEVEL_INFO, 'Checking for existence of default project...');
            $defaultProject=project::getByName(baseproject::DEFAULTPROJECT);
            Log::write(Log::LOGLEVEL_INFO, '...Exists.');
            
            $json=json_decode($jsonString, true);
            if(!$json){ throw new Exception('Could not parse JSON'); }
            
            //TODO Validate against schema
            //TODO check version
            
            if($json['plates']){
                Log::write(Log::LOGLEVEL_INFO, 'Found plate group, importing...');
                static::importPlateGroup($json['plates']);
                Log::write(Log::LOGLEVEL_INFO, 'Plate group imported.');
            } else if($json['plateType']){
                Log::write(Log::LOGLEVEL_INFO, 'Found single plate, importing...');
                static::importPlate($json);
                Log::write(Log::LOGLEVEL_INFO, 'Plate imported.');
            } else {
                throw new Exception('Importer only handles plates and plate groups');
            }
            Log::write(Log::LOGLEVEL_DEBUG, 'Committing database transaction...');
            database::commit();
            Log::write(Log::LOGLEVEL_DEBUG, 'Committed.');
        } catch (Exception $e){
            Log::write(Log::LOGLEVEL_ERROR, 'An exception was thrown.');
            Log::write(Log::LOGLEVEL_DEBUG, 'Aborting database transaction...');
            database::abort();
            Log::write(Log::LOGLEVEL_DEBUG, 'Aborted.');
            Log::write(Log::LOGLEVEL_ERROR, $e->getMessage());
            $trace=$e->getTrace();
            $prettytrace=array();
            $prettytrace[]=$e->getFile().' line '.$e->getLine();
            foreach($trace as $t){
                $traceline='';
                $line=(isset($t['line'])) ? $t['line'] : '--';
                $file=(isset($t['file'])) ? str_replace(config::getWwwRoot(), '', (str_replace('\\','/',$t['file']))) : '--';
                if (array_key_exists('class',$t)){
                    $traceline=sprintf("%s:%s %s::%s",
                        $file,
                        $line,
                        $t['class'],
                        $t['function']
                        );
                } else {
                    $traceline=sprintf("%s:%s %s",
                        $file,
                        $line,
                        $t['function']
                        );
                }
                $prettytrace[]=$traceline;
            }
            Log::write(Log::LOGLEVEL_ERROR, 'Exception was thrown, details follow:');
            Log::write(Log::LOGLEVEL_ERROR, $e->getMessage());
            Log::write(Log::LOGLEVEL_ERROR, 'Thrown at '.$prettytrace[0]);
            foreach($prettytrace as $t){
                Log::write(Log::LOGLEVEL_ERROR, $t);
            }
        
        }
    }
        
    
    private static function importPlateGroup($plates){
        $numPlates=count($plates);
        Log::write(Log::LOGLEVEL_INFO, 'Found '.$numPlates.' plates');
        $count=1;
        foreach($plates as $p){
            Log::write(Log::LOGLEVEL_INFO, 'Importing plate '.$count.' of '.$numPlates.'...');
            static::importPlate($p);
            Log::write(Log::LOGLEVEL_INFO, 'Imported plate '.$count.' of '.$numPlates.'.');
	    $count++;
        }
    }

	/**
	 * @throws NotFoundException
	 * @throws NotModifiedException
	 * @throws BadRequestException
	 * @throws ForbiddenException
	 * @throws ServerException
	 * @throws Exception
	 */
	private static function importPlate($plate): void {
        Log::write(Log::LOGLEVEL_DEBUG, 'In importPlate');
        static::$currentPlate=null;
        static::$currentPlateOwner=null;
        
        $barcode=$plate['barcode'];
        $scoringSystemName=$plate['scoringSystemName'];
        $plateType=$plate['plateType'];
        $owner=$plate['owner'];
        $interestingDrops=null;
        $inspections=null;
        $proteinConstruct=null;
        if(isset($plate['plateInspections'])){ $inspections=$plate['plateInspections']; }
        if(isset($plate['interestingDrops'])){ $interestingDrops=$plate['interestingDrops']; }
        if(isset($plate['proteinConstruct'])){ $proteinConstruct=$plate['proteinConstruct']; }
        if(empty($barcode)){
            throw new Exception('Plate has no barcode');
        }
        if(empty($scoringSystemName)){
            throw new Exception('Plate with barcode '.$barcode.' has no scoring system name');
        }
        if(empty($plateType)){
            throw new Exception('Plate with barcode '.$barcode.' has no plate type');
        }
        if(empty($owner)){
            throw new Exception('Plate with barcode '.$barcode.' has no owner');
        }
        $scoringSystem=crystalscoringsystem::getByName($scoringSystemName);
        static::$currentScoringSystem=$scoringSystem;
        if(!$scoringSystem){
            throw new Exception('No crystal scoring system called '.$scoringSystemName.' in destination LIMS');
        }
         
        $plateType=static::importPlateType($plateType);
	$platetype=platetype::getById($plateType['id']);

        $owner=static::importUser($owner);
        static::$currentPlateOwner=$owner;
        $defaultProject=project::getByName(baseproject::DEFAULTPROJECT);
        $projectId=$defaultProject['id'];

        $limsPlate=plate::getByName($barcode);
        if(!$limsPlate){
            Log::write(Log::LOGLEVEL_INFO, 'No plate with barcode '.$barcode.' in LIMS, creating...');
            $limsPlate=plate::create(array(
                'name'=>$barcode,
                'platetypeid'=>$plateType['id'],
                'crystalscoringsystemid'=>$scoringSystem['id'],
                'ownerid'=>$owner['id'],
                'projectid'=>$projectId
            ));
            $limsPlate=$limsPlate['created'];
            Log::write(Log::LOGLEVEL_INFO, 'Plate created.');
            
            if($proteinConstruct){
                $limsConstruct=static::importProteinConstruct($proteinConstruct);
                plate::update($limsPlate['id'],array(
                    'constructid'=>$limsConstruct['id']
                ));
            }
            
        } else {
            Log::write(Log::LOGLEVEL_INFO, 'Plate exists.');
        }
        $wellDrops=plate::getwelldrops($limsPlate['id']);
        $limsPlate['welldrops']=$wellDrops['rows'];
        static::$currentPlate=$limsPlate;
        if($inspections){
            //TODO
            Log::write(Log::LOGLEVEL_ERROR, 'Importing plate inspections is not implemented.');
        } else {
            Log::write(Log::LOGLEVEL_INFO, 'No plate inspections defined.');
        }
        if($interestingDrops){
            Log::write(Log::LOGLEVEL_INFO, 'Plate has '.count($plate['interestingDrops']).' interesting drops. Updating them...');
            foreach ($interestingDrops as $drop){
                static::updateInterestingDrop($drop);
            }
            Log::write(Log::LOGLEVEL_INFO, 'All interesting drops updated.');
        } else {
            Log::write(Log::LOGLEVEL_INFO, 'No interesting drops defined.');
        }
        
        Log::write(Log::LOGLEVEL_DEBUG, 'Returning from importPlate');
	}

	/**
	 * @throws BadRequestException
	 * @throws ForbiddenException
	 * @throws ServerException
	 * @throws NotFoundException
	 */
	private static function importPlateType($plateType){
        Log::write(Log::LOGLEVEL_DEBUG, 'In importPlateType');
        $name=$plateType['name'];
        $rows=1*$plateType['rows'];
        $cols=1*$plateType['columns'];
        $drops=1*$plateType['subPositions'];

	if($rows<=0 || $cols<=0 || $drops <=0){
		throw new Exception('Bad plate type geometry - rows, columns, subPositions must all be integers above zero');
	}
        
        $limsPlateType=platetype::getByName($name);
        if(!$limsPlateType){
            Log::write(Log::LOGLEVEL_INFO, 'Plate type does not exist, creating...');
            $defaultDropMapping='';
            for($i=1;$i<=(int)$drops;$i++){
                $defaultDropMapping.=$i;
            }
            $defaultDropMapping.=',';
            for($i=1;$i<=(int)$drops;$i++){
                $defaultDropMapping.='R';
            }
            $limsPlateType=platetype::create(array(
                'name'=>$name,
                'rows'=>$rows,
                'cols'=>$cols,
                'subs'=>$drops,
                'dropmapping'=>$defaultDropMapping,
                'projectid'=>project::getSharedProjectId()
            ));
            $limsPlateType=$limsPlateType['created'];
            Log::write(Log::LOGLEVEL_INFO, 'Plate type created');
        } else {
            Log::write(Log::LOGLEVEL_INFO, 'Plate type exists');
            $limsLayout=$limsPlateType['rows'].'x'.$limsPlateType['cols'].'x'.$limsPlateType['subs'];
            $sourceLayout=$rows.'x'.$cols.'x'.$drops;
            if($limsLayout!=$sourceLayout){
                throw new Exception('Plate type '.$name.' layout mismatch. Source: '.$sourceLayout.', destination '.$limsLayout);
            }
        }
        Log::write(Log::LOGLEVEL_DEBUG, 'Returning from importPlateType');
        return $limsPlateType;
    }

	/**
	 * @throws BadRequestException
	 * @throws ForbiddenException
	 * @throws ServerException
	 * @throws NotFoundException
	 */
	private static function importUser($user){
        Log::write(Log::LOGLEVEL_DEBUG, 'In importUser');
        $username=$user['username'];
        $fullName=$user['name'];
        $email=$user['emailAddress'];
        Log::write(Log::LOGLEVEL_DEBUG, 'Username: '.$username);
        Log::write(Log::LOGLEVEL_DEBUG, 'Full name: '.$fullName);
        Log::write(Log::LOGLEVEL_DEBUG, 'Email: '.$email);
        
        if(empty($username) || empty($fullName) || empty($email)){
            throw new Exception('User: Username, full name and email address are required.');
        }
        $limsUser=user::getByName($username);
        if(!$limsUser){
            Log::write(Log::LOGLEVEL_INFO, 'User does not exist, creating...');
            $limsUser=user::create(array(
                'name'=>$username,
                'fullname'=>$fullName,
                'email'=>$email,
                'password'=>'USELESSPASSWORD',
                'isactive'=>0
            ));
            Log::write(Log::LOGLEVEL_INFO, 'User created');
            $limsUser=$limsUser['created'];
        } else {
            Log::write(Log::LOGLEVEL_INFO, 'User exists');
            if($limsUser['email']!=$email){
                throw new Exception('User '.$username.' email address mismatch. Source: '.$email.', destination: '.$limsUser['email']);
            }
        }
        Log::write(Log::LOGLEVEL_DEBUG, 'returning from importUser');
        return $limsUser;
    }

	/**
	 * @throws NotFoundException
	 * @throws ServerException
	 * @throws ForbiddenException
	 * @throws BadRequestException
	 * @throws Exception
	 */
	private static function updateInterestingDrop(array $drop): void {
        $limsDrop=null;
        $row=$drop['rowNumber'];
        $col=$drop['columnNumber'];
        $dropNumber=$drop['dropNumber'];
	$row=(int)$row;
	$col=(int)$col;
	$dropNumber=(int)$dropNumber;

	if(!$row){ 
		$rowLabels=platetype::$rowLabels;
		$row=array_search($drop['rowNumber'], $rowLabels); 
	}


        foreach(static::$currentPlate['welldrops'] as $wd){
            if($row==$wd['row'] && $col==$wd['col'] && $dropNumber==$wd['dropnumber']){
                $limsDrop=$wd;
                break;
            }
        }
        if(!$limsDrop){ throw new Exception('No well drop matching row '.$row.', column '.$col.', drop '.$dropNumber); }
        $limsDropImages=welldrop::gettimecourseimages($limsDrop['id']);
        if(!$limsDropImages){
            $dummyInspection=static::getDummyPlateInspection();
            $limsDropImages=welldrop::gettimecourseimages($limsDrop['id']);
        }
        $store='';
        $overviewImage=str_replace('\\', '/', $drop['overviewImage']);
        $overviewThumb=str_replace('\\', '/', $drop['overviewImageThumbnail']);
        foreach(static::$drives as $p){
            if(0===stripos($p, $overviewImage)){
                $store=$p;
            }
        }
        if(''==$store){
            //Use the first two significant parts of path as "image store", e.g., //SHARE/directory/ or C:/directory/
            Log::write(Log::LOGLEVEL_WARN, 'Image store not found, using first part of overview image path');
            $parts=explode('/',$overviewImage);
            $nonEmptyParts=0;
            $store='';
            foreach($parts as $p){
                $store.=$p.'/';
                if(''!=$p){ $nonEmptyParts++; }
                if($nonEmptyParts==1){ break; }
            }
        }
        
        Log::write(Log::LOGLEVEL_DEBUG, 'Image store path: '.$store);
        $imagePath=str_replace($store, '', $overviewImage);
        Log::write(Log::LOGLEVEL_DEBUG, 'Image: '.$imagePath);
        $thumbPath=str_replace($store, '', $overviewThumb);
        Log::write(Log::LOGLEVEL_DEBUG, 'Thumb: '.$thumbPath);
        
        if(!$limsDropImages){
            Log::write(Log::LOGLEVEL_DEBUG, 'No drop image in LIMS, creating it...');
            
            //insert drop image at dummy inspection
            $inspectionId=$dummyInspection['id'];
            $wellDropId=$limsDrop['id'];
            $dimensions=@getimagesize($overviewImage);
            if(!$dimensions){
                Log::write(Log::LOGLEVEL_ERROR, 'No dimensions for '.$overviewImage);
                throw new Exception('Could not get dimensions for image. Either it does not exist or it is corrupt.');
            }
            $width=$dimensions[0];
            $height=$dimensions[1];
            $micronsPerPixel=-1;
            $imageName=static::$currentPlate['name'].'_'.platewell::getWellLabel($row, $col).'.'.$dropNumber.'_is'.$inspectionId;
            $limsDropImage=dropimage::create(array(
                'name'=>$imageName,
                'projectid'=>static::$currentPlate['projectid'],
                'imagingsessionid'=>$inspectionId,
                'welldropid'=>$wellDropId,
                'pixelwidth'=>$width,
                'pixelheight'=>$height,
                'micronsperpixelx'=>$micronsPerPixel,
                'micronsperpixely'=>$micronsPerPixel,
                'imagestorepath'=>$store,
                'imagepath'=>$imagePath,
                'thumbnailpath'=>$thumbPath
            ));
            $limsDropImages=welldrop::gettimecourseimages($limsDrop['id']);
            Log::write(Log::LOGLEVEL_DEBUG, '..created LIMS drop image. ID is '.$limsDropImage['created']['id']);
        }

        $limsDropImage=end($limsDropImages['rows']);
        
        if(isset($drop['latestScoreName'])){
            $limsScoreName=static::$currentScoringSystem['name'].'_'.$drop['latestScoreName'];
            $limsScore=crystalscore::getByName($limsScoreName);
            if(!$limsScore){
                Log::write(Log::LOGLEVEL_WARN,'No crystal score called '.$limsScoreName.' in destination LIMS');
            } else if($limsScore['crystalscoringsystemid']!=static::$currentScoringSystem['id']){
                throw new Exception('Destination LIMS has scoring system '.static::$currentScoringSystem['name'].' and score '.$limsScoreName.', but they appear unrelated.');
            } else {
	        dropimage::update($limsDropImage['id'], array(
                    'latestcrystalscoreid'=>$limsScore['id']
	        ));
	    }
        } else if(isset($drop['latestScoreValue'])){
	    //Invalid according to schema. Assume this is the score index.
	    Log::write(Log::LOGLEVEL_WARN,'Score should be specified by name (Crystals, etc). Assuming latestScoreValue is score index.');
	    if(is_numeric($drop['latestScoreValue'])){
	        $limsScore=database::queryGetOne(
		    'SELECT id FROM crystalscore WHERE scoreindex=:index AND crystalscoringsystemid=:cssid',
		    array(':cssid'=>static::$currentScoringSystem['id'] , ':index'=>$drop['latestScoreValue'])
	       );
               if(!$limsScore){
                    throw new Exception('No crystal score with scoreindex '.$drop['latestScoreValue'].' and crystalscoringsystemid '.static::$currentScoringSystem['id'].' in destination LIMS');
               }
               dropimage::update($limsDropImage['id'], array(
                   'latestcrystalscoreid'=>$limsScore['id']
               ));
	    }
	}

        if(isset($drop['crystals'])){
            $defaultCrystalNumber=1;
            foreach ($drop['crystals'] as $c){
		$sampleName='';
		if(!isset($c['numberinDrop'])){
			//Invalid according to schema, but whatever.
			Log::write(Log::LOGLEVEL_WARN,'Crystal numberInDrop not explicitly set. Relying on array ordering!');
			$c['numberInDrop']=$defaultCrystalNumber;
		}
		$defaultCrystalNumber++;
		if(!isset($c['sampleName'])){
			//Invalid according to schema, but whatever.
			$rowLetter=platetype::$rowLabels[$limsDrop['row']];
			$colNumber=str_pad($limsDrop['col'], 2, "0", STR_PAD_LEFT);
			$dropNumber=$limsDrop['dropnumber'];
			$sampleName=static::$currentPlate['name'].$rowLetter.$colNumber.'d'.$dropNumber.'c'.$c['numberInDrop'];
		} else {
			Log::write(Log::LOGLEVEL_DEBUG,'sampleName was set in JSON');
			$sampleName=$c['sampleName'];
		}
		Log::write(Log::LOGLEVEL_DEBUG,'sampleName = "' .$sampleName. '"');
		$proteinAcronym='';
		if($limsDrop['constructid']){
			$limsConstruct=construct::getbyId($limsDrop['constructid']);
			$proteinAcronym=$limsConstruct['proteinacronym'];
		}
                $limsCrystals=crystal::getByProperty('prefix',$sampleName);
		if(empty($limsCrystals)){
			Log::write(Log::LOGLEVEL_DEBUG,'No crystals with prefix "' .$sampleName. '"');
	                $limsCrystals=crystal::getByProperty('prefix',$proteinAcronym.'_'.$sampleName);
		}
		if(!empty($limsCrystals)){
  		    Log::write(Log::LOGLEVEL_DEBUG,'Found crystal with prefix  "'.$proteinAcronym. '_' .$sampleName. '"');
 		    $limsCrystal=$limsCrystals['rows'][0];
                    if($limsCrystal['numberindrop']!=$c['numberInDrop']){
                        throw new Exception('Crystal with name '.$c['sampleName'].' exists in LIMS but crystal numbers do not match');
                    }
                    //Otherwise ignore it.
                } else {
  		    Log::write(Log::LOGLEVEL_DEBUG,'No crystal with prefix  "'.$proteinAcronym. '_' .$sampleName. '"');
                    if($c['sendersImage']['marks']['x']){
			//Invalid according to schema. Should be a one-item array. Whatever.
                    	$x=$c['sendersImage']['marks']['x'];
                    	$y=$c['sendersImage']['marks']['y'];
		    } else {
                    	$x=$c['sendersImage']['marks'][0]['x'];
                    	$y=$c['sendersImage']['marks'][0]['y'];
		    }
                    $limsCrystal=crystal::create(array(
                        'welldropid'=>$limsDrop['id'],
                        'dropimageid'=>$limsDropImage['id'],
                        'name'=>$sampleName,
                        'prefix'=>$sampleName,
                        'projectid'=>static::$currentPlate['projectid'],
                        'numberindrop'=>$c['numberInDrop'],
                        'pixelx'=>(int)$x,
                        'pixely'=>(int)$y
                    ));
                    $limsCrystal=$limsCrystal['created'];
                    $cellInfoFields=array("spaceGroup","unitCellA","unitCellB","unitCellC","unitCellAlpha","unitCellBeta","unitCellGamma");
                    $updateParameters=array('name'=>$sampleName);
                    foreach($cellInfoFields as $ci){
                        if(isset($c[$ci])){
                            $updateParameters[strtolower($ci)]=$c[$ci];
                        }
                    }
                    crystal::update($limsCrystal['id'],$updateParameters);
                }
            }
            
        }
    }

	/**
	 * @throws BadRequestException
	 * @throws NotFoundException
	 * @throws ForbiddenException
	 * @throws ServerException
	 */
	private static function getDummyPlateInspection(){
        $limsPlate=static::$currentPlate;
        $barcode=$limsPlate['name'];
        $dummyInspection=imagingsession::getByName($barcode.'_dummy');
        if(!$dummyInspection){
            $dummyImager=imager::getByName('+20 Microscope');
            if(!$dummyImager){
                throw new Exception('No imager called +20 Microscope');
            }
            $dummyInspection=imagingsession::create(array(
                'name'=>$barcode.'_dummy',
                'manufacturerdatabaseid'=>0,
                'plateid'=>$limsPlate['id'],
                'imagerid'=>$dummyImager['id'],
                'imageddatetime'=>gmdate('Y-m-d H:i:s')
            ));
            $dummyInspection=$dummyInspection['created'];
            }
        return $dummyInspection;
    }

	/**
	 * @throws NotFoundException
	 * @throws ServerException
	 * @throws ForbiddenException
	 * @throws BadRequestException
	 */
	private static function importProteinConstruct($proteinConstruct){
        
        /**
         * TODO REWORK
         * Protein may exist without construct, need to create construct under protein.
         */
        
        Log::write(Log::LOGLEVEL_DEBUG, 'in importProteinConstruct');
        $proteinName=$proteinConstruct['proteinName'];
        $proteinAcronym=$proteinConstruct['proteinAcronym'];
        $constructName=$proteinConstruct['constructName'];
        $protein=protein::getByName($proteinName);
        $createdProtein=false;
        $createdConstruct=false;

	if(empty($constructName)){
		//Technically this violates the schema. Be nice and call it Construct 1.
		$constructName='Construct 1';
	}
        
        if($protein){
            Log::write(Log::LOGLEVEL_INFO, 'Protein '.$proteinName.' exists');
            if($proteinAcronym!=$protein['proteinacronym']){
                throw new Exception('Protein acronym mismatch. Source: '.$proteinAcronym.', destination '.$protein['proteinacronym']);
            }
        } else {
            $projectOwner=static::$currentPlateOwner;
            $existingProject=null;
			$existingProject=project::getByName($proteinName);
            if($existingProject){
                throw new Exception($proteinName.': Protein does not exist but there is a project with that name.');
            }
            
            //First create the project
            Log::write(Log::LOGLEVEL_INFO, 'Creating project: '.$proteinName);
            $project=project::create(array(
                'name'=>$proteinName,
                'description'=>$proteinName.' project',
                'owner'=>$projectOwner['id']
            ));
            $project=$project['created'];
            $projectId=$project['id'];
            Log::write(Log::LOGLEVEL_INFO, 'Created');
            
            //Project creation may have wiped out our admin status
            session::set('isAdmin',true);
            
            //Then create the protein
            Log::write(Log::LOGLEVEL_INFO, 'Creating protein: '.$proteinName);
            $protein=protein::create(array(
                'name'=>$proteinName,
                'description'=>$proteinName,
                'proteinacronym'=>$proteinAcronym,
                'projectid'=>$projectId
            ));
            $protein=$protein['created'];
            Log::write(Log::LOGLEVEL_INFO, 'Created protein and default construct');
            $createdProtein=true;
        }

        $constructs=construct::getByProperty('proteinid', $protein['id']);
        $constructs=$constructs['rows'];
        if($createdProtein){
            Log::write(Log::LOGLEVEL_INFO, 'Renaming default construct');
            $construct=$constructs[0];    
            $construct=construct::update($construct['id'], array(
                'name'=>$constructName,
                'description'=>$constructName.' construct of '.$proteinName
            ));
            $construct=$construct['updated'];
        }

        foreach($constructs as $c){
            if($c['name']==$constructName){
                $construct=$c;
                break;
            }
        }
        
        if(!$createdProtein){
            if($construct){
                Log::write(Log::LOGLEVEL_INFO, 'Construct '.$constructName.' exists in '.$proteinName);
                Log::write(Log::LOGLEVEL_INFO, 'Not checking sequences');
            } else {
                Log::write(Log::LOGLEVEL_ERROR, 'Construct '.$constructName.' does not exist in '.$proteinName);
                $construct=construct::create(array(
                    'name'=>$constructName,
                    'proteinid'=>$protein['id'],
                    'description'=>$constructName.' construct of '.$proteinName
                ));
                $construct=$construct['created'];
                $createdConstruct=true;
            }
        }
        if($createdProtein || $createdConstruct){
            Log::write(Log::LOGLEVEL_INFO, 'Construct '.$constructName.' created, checking for sequences...');
            //Then add the sequence(s)
            if(isset($proteinConstruct['dnaSequences']) && 0<count($proteinConstruct['dnaSequences'])){
                Log::write(Log::LOGLEVEL_INFO, 'Creating sequences');
                $count=1;
                foreach($proteinConstruct['dnaSequences'] as $seq){
                    sequence::create(array(
                        'name'=>'Sequence '.$count,
                        'dnasequence'=>$seq,
                        'constructid'=>$construct['id']
                    ));
                    $count++;
                }
                Log::write(Log::LOGLEVEL_INFO, 'Created');
            } else {
                Log::write(Log::LOGLEVEL_INFO, '...No sequences found.');
            }
            
        }
        Log::write(Log::LOGLEVEL_DEBUG, 'returning from importProteinConstruct');
        return $construct;
    }

    private static function getDrives(): void {
        if(!static::$drives){
            static::$drives=array();
            try {
                $drives=diskusage::getAll();
		if(isset($drive['rows'])){
                    foreach($drives['rows'] as $d){
                        static::$drives[]=str_replace('\\', '/', $d['mountpoint']);
                    }
		}
            } catch(ServerException $e){
                Log::write(Log::LOGLEVEL_WARN, $e->getMessage());
            }
        }
	}
    
}
