<?php class sequence extends protein { //Extend protein, which extends baseobject - same restrictions on canCreate
	
	protected static array $fields=array(
			'name'=>validator::REQUIRED,
			'constructid'=>array(validator::REQUIRED, validator::INTEGER),
			'dnasequence'=>array(validator::DNASEQUENCE),
			'proteinsequence'=>array(validator::PROTEINSEQUENCE),
	);
	
	protected static array $helpTexts=array(
			'name'=>'A unique name for this protein',
			'constructid'=>'The database ID of the parent construct',
			'dnasequence'=>'The DNA sequence',
			'proteinsequence'=>'The protein sequence',
	);
	
	public static function create(array $request=[]): array {
		if(!isset($request['constructid'])){ throw new BadRequestException('No parent construct specified'); }
		$con=construct::getById($request['constructid']); //NotFoundException if user can't read it
		if(!session::isAdmin()){
			$project=project::getById($con['projectid']); //NotFoundException if user can't read it
			if(!$project){
				throw new NotFoundException('Project does not exist, or you do not have permission to see it');
			}
			if($project['owner']!=session::getUserId()){
				throw new ForbiddenException('You must own the project to create proteins and constructs in it');
			}
		}
		$request['projectid']=$con['projectid'];
		static::checkSequences($request);
		if(empty($request['proteinsequence'])){
		    $request['proteinsequence']=static::dnaToProtein($request['dnasequence']);
		}
		$created=baseobject::createByClassName($request,'sequence');
		return array(
				'type'=>'sequence',
				'created'=>static::getById($created['created']['id'])
		);
	}

	/**
	 * @param int $id
	 * @param array $request
	 * @return array
	 * @throws BadRequestException
	 * @throws ForbiddenException
	 * @throws NotFoundException
	 * @throws ServerException
	 */
	public static function update(int $id, array $request=[]): array {
		static::checkSequences($request);
		return parent::update($id,$request);
	}

	/**
	 * @param int $id
	 * @param array $request
	 * @return array|null
	 * @throws BadRequestException
	 * @throws ServerException
	 */
	public static function getsequences(int $id,array $request=[]): ?array {
		return sequence::getByProperty('constructid',$id,$request);
	}
	
	/**
	 * Strips whitespace from DNA and protein sequence in request and, if both are non-empty, verifies that they match.
	 * @param array|null $request The request array
	 * @throws BadRequestException if sequences do not match
	 */
	private static function checkSequences(?array $request): void {
		if(!isset($request['dnasequence'])){ $request['dnasequence']=''; }
		if(!isset($request['proteinsequence'])){ $request['proteinsequence']=''; }
		$request['dnasequence']=strtoupper(preg_replace('/\s/', '', $request['dnasequence']));
		$request['proteinsequence']=strtoupper(preg_replace('/\s/', '', $request['proteinsequence']));
		if(""!=$request['dnasequence'] && ""!=$request['proteinsequence']){
			$generatedProtein=static::dnaToProtein($request['dnasequence']);
			if(!$generatedProtein){
				throw new BadRequestException('Could not convert DNA sequence to protein sequence. Bad DNA sequence?');
			} else if($generatedProtein!==$request['proteinsequence']){
				throw new BadRequestException('DNA sequence and protein sequence do not match.');
			}
		}
	}

	/**
	 * Given a DNA sequence, returns the protein sequence, or false on failure.
	 * @param string $seq The DNA sequence
	 * @return string|bool
	 */
	public static function dnaToProtein(string $seq): string|bool {
		//Strip all whitespace and explode into 3-character segments (codons)
		$seq=strtoupper(preg_replace('/\s/', '', $seq));
		$dnaParts=array();
		if(strlen($seq)%3 !=0){ return false; }
		if(preg_match('/[^ACGT]/', $seq)){ return false; }
		preg_match_all('/.{1,3}/',$seq,$dnaParts);
 		if(!isset($dnaParts[0]) || 0==count($dnaParts[0])){ return false; }
 		//Smash them all together separated by underscore, then to a straight search-replace
		$seq=implode('_', $dnaParts[0]);
		$codons=array_keys(static::$codonToAmino);
		for($i=0;$i<count($codons);$i++){
			$codons[$i]='/'.$codons[$i].'/';
		}
		$aminos=array_values(static::$codonToAmino);
		$seq=preg_replace($codons, $aminos, $seq);
		//Any remaining adjacent letters mean it failed, otherwise strip the underscores and return
		if(preg_match('/[ACGT][ACGT]/', $seq)){ return false; }
		return preg_replace('/_/', '', $seq);
	}
	
	private static array $codonToAmino=array(
		"ATT"=>"I", "ATC"=>"I", "ATA"=>"I", "CTT"=>"L", "CTC"=>"L", "CTA"=>"L", "CTG"=>"L", "TTA"=>"L",
		"TTG"=>"L", "GTT"=>"V", "GTC"=>"V", "GTA"=>"V", "GTG"=>"V", "TTT"=>"F", "TTC"=>"F", "ATG"=>"M",
		"TGT"=>"C", "TGC"=>"C", "GCT"=>"A", "GCC"=>"A", "GCA"=>"A", "GCG"=>"A", "GGT"=>"G", "GGC"=>"G",
		"GGA"=>"G", "GGG"=>"G", "CCT"=>"P", "CCC"=>"P", "CCA"=>"P", "CCG"=>"P", "ACT"=>"T", "ACC"=>"T",
		"ACA"=>"T", "ACG"=>"T", "TCT"=>"S", "TCC"=>"S", "TCA"=>"S", "TCG"=>"S", "AGT"=>"S", "AGC"=>"S",
		"TAT"=>"Y", "TAC"=>"Y", "TGG"=>"W", "CAA"=>"Q", "CAG"=>"Q", "AAT"=>"N", "AAC"=>"N", "CAT"=>"H",
		"CAC"=>"H", "GAA"=>"E", "GAG"=>"E", "GAT"=>"D", "GAC"=>"D", "AAA"=>"K", "AAG"=>"K", "CGT"=>"R",
		"CGC"=>"R", "CGA"=>"R", "CGG"=>"R", "AGA"=>"R", "AGG"=>"R", "TAA"=>"*", "TAG"=>"*", "TGA"=>"*",
	);
	
}