/* START ALIGNMENT SETTINGS */

if (!_HY_NUC_CODON_HAVE_SCORE_MATRIX || Type(scoreMatrix)!="Matrix" || Rows (scoreMatrix) != 64)
{
	LoadFunctionLibrary ("chooseGeneticCode");
	LoadFunctionLibrary ("SeqAlignShared");
	alignOptions ["SEQ_ALIGN_CHARACTER_MAP"]="ACGT";
	LoadFunctionLibrary ("GrabBag");
	protScoreMatrix = alignOptions["SEQ_ALIGN_SCORE_MATRIX"];
	no_tp = alignOptions["SEQ_ALIGN_NO_TP"];

	alignOptions = {};

 	protLetters = "ARNDCQEGHILKMFPSTWYV";
   _cdnaln_cdnScoreMatrix = pSM2cSM(protScoreMatrix, protLetters);


	alignOptions ["SEQ_ALIGN_SCORE_MATRIX"] = 	_cdnaln_cdnScoreMatrix;
	maxScore = Max (protScoreMatrix,0);
	minScore = Min (protScoreMatrix,0);

	alignOptions ["SEQ_ALIGN_GAP_OPEN"]		= 	2*Max(maxScore,-minScore);
	alignOptions ["SEQ_ALIGN_GAP_OPEN2"]	= 	2*Max(maxScore,-minScore);
	alignOptions ["SEQ_ALIGN_GAP_EXTEND"]	= 	1;
	alignOptions ["SEQ_ALIGN_GAP_EXTEND2"]	= 	1;
	alignOptions ["SEQ_ALIGN_FRAMESHIFT"]	= 	Max(maxScore,-minScore);
	alignOptions ["SEQ_ALIGN_CODON_ALIGN"]	= 	1;
	alignOptions ["SEQ_ALIGN_CHARACTER_MAP"]=  "ACGT";
	alignOptions ["SEQ_ALIGN_NO_TP"]        =  no_tp;

    _cdnaln_partialScoreMatrices = cSM2partialSMs(_cdnaln_cdnScoreMatrix,0);

    alignOptions ["SEQ_ALIGN_PARTIAL_3x1_SCORES"] = _cdnaln_partialScoreMatrices["3x1"];
    alignOptions ["SEQ_ALIGN_PARTIAL_3x2_SCORES"] = _cdnaln_partialScoreMatrices["3x2"];
    alignOptions ["SEQ_ALIGN_PARTIAL_3x4_SCORES"] = _cdnaln_partialScoreMatrices["3x4"];
    alignOptions ["SEQ_ALIGN_PARTIAL_3x5_SCORES"] = _cdnaln_partialScoreMatrices["3x5"];


}

function Uppercase( _str )
{
    _upstr = _str && 1;
    _upstr * 0;
    return _upstr;
}

function _cdn_alignment_cleanup (reference, query, offset_nuc) {
    too_short = 0;
    too_long  = 0;
    span      = 0; // how many nucleotides in the reference were covered by non-gaps
    _seqL     = Abs (reference);

    ref_cleaned = ""; ref_cleaned * 128;
    qry_cleaned = ""; qry_cleaned * 128;

    _codon_in_reference = 0;

	for ( _rcidx = 0; _rcidx < _seqL; _rcidx += 1 ) {
	    _del1 = reference [_rcidx] != (reference [_rcidx]&&1);
	    if (_del1) {
	        too_short += 1;
	        _codon_in_reference += 1;
	        ref_cleaned * (reference [_rcidx]&&1);
	        qry_cleaned * (query [_rcidx]&&1);
	    } else {
	        _del1 = query [_rcidx] != (query [_rcidx]&&1);
	        if (_del1) {
	            if (_seqL-_rcidx < 3 && _codon_in_reference % 3 == 0) {
	                break;
	            }
	            too_long += 1;
	        } else {
                ref_cleaned * (reference [_rcidx]&&1);
                qry_cleaned * (query [_rcidx]&&1);
                span += 1;
                _codon_in_reference +=1;
	        }
	    }
	}

	ref_cleaned * 0; qry_cleaned * 0;

	return {"REF": ref_cleaned, "QRY": qry_cleaned, "TOO_SHORT" : too_short, "TOO_LONG": too_long, "SPAN": span};
}


// -------------------------------------------------------------------------- //


function cSM2partialSMs(_cdnScoreMatrix, penalties)
{
    m3x5  =  { 65, 640 };
    m3x4  =  { 65, 256 };
    m3x2  =  { 65,  48 };
    m3x1  =  { 65,  12 };

    // minor penalties to make mismatch not entirely free

    if (Rows(penalties)*Columns(penalties) == 4) {
        p3x1 = penalties [0];
        p3x2 = penalties [1];
        p3x4 = penalties [2];
        p3x5 = penalties [3];
    } else {
        p3x5 = 0;
        p3x4 = 0;
        p3x2 = 0;
        p3x1 = 0;
    }


    for ( thisCodon = 0; thisCodon < 64; thisCodon += 1 ) {
        for ( d1 = 0; d1 < 4; d1 += 1 ) {
            max100 = -1e100;
            max010 = -1e100;
            max001 = -1e100;

            for ( d2 = 0; d2 < 4; d2 += 1 ) {
                partialCodon = 4 * d1 + d2;
                max110 = -1e100;
                max101 = -1e100;
                max011 = -1e100;

                for ( d3 = 0; d3 < 4; d3 += 1 ) {
                    thisCodon2 = 4 * partialCodon + d3;
                    thisScore = _cdnScoreMatrix[ thisCodon ][ thisCodon2 ];

                    // this is the trivial and stupid way of doing it, but it should work
                    m3x5[ thisCodon ][ 10 * thisCodon2 + 0 ] = thisScore - p3x5;
                    m3x5[ thisCodon ][ 10 * thisCodon2 + 1 ] = thisScore - p3x5;
                    m3x5[ thisCodon ][ 10 * thisCodon2 + 2 ] = thisScore - p3x5;
                    m3x5[ thisCodon ][ 10 * thisCodon2 + 3 ] = thisScore - p3x5;
                    m3x5[ thisCodon ][ 10 * thisCodon2 + 4 ] = thisScore - p3x5;
                    m3x5[ thisCodon ][ 10 * thisCodon2 + 5 ] = thisScore - p3x5;
                    m3x5[ thisCodon ][ 10 * thisCodon2 + 6 ] = thisScore - p3x5;
                    m3x5[ thisCodon ][ 10 * thisCodon2 + 7 ] = thisScore - p3x5;
                    m3x5[ thisCodon ][ 10 * thisCodon2 + 8 ] = thisScore - p3x5;
                    m3x5[ thisCodon ][ 10 * thisCodon2 + 9 ] = thisScore - p3x5;

                    m3x4[ thisCodon ][ 4 * thisCodon2 + 0 ] = thisScore - p3x4;
                    m3x4[ thisCodon ][ 4 * thisCodon2 + 1 ] = thisScore - p3x4;
                    m3x4[ thisCodon ][ 4 * thisCodon2 + 2 ] = thisScore - p3x4;
                    m3x4[ thisCodon ][ 4 * thisCodon2 + 3 ] = thisScore - p3x4;

                    // d1 is 1
                    max100 = Max( max100, _cdnScoreMatrix[ thisCodon ][ 16 * d1 + 4 * d2 + d3 ] );
                    max010 = Max( max010, _cdnScoreMatrix[ thisCodon ][ 16 * d2 + 4 * d1 + d3 ] );
                    max001 = Max( max001, _cdnScoreMatrix[ thisCodon ][ 16 * d2 + 4 * d3 + d1 ] );

                    // d1 and d2 are 1
                    max110 = Max( max110, _cdnScoreMatrix[ thisCodon ][ 16 * d1 + 4 * d2 + d3 ] );
                    max101 = Max( max101, _cdnScoreMatrix[ thisCodon ][ 16 * d1 + 4 * d3 + d2 ] );
                    max011 = Max( max011, _cdnScoreMatrix[ thisCodon ][ 16 * d3 + 4 * d1 + d2 ] );
                }

                m3x2[ thisCodon ][ 3 * partialCodon + 0 ] = max110 - p3x2;
                m3x2[ thisCodon ][ 3 * partialCodon + 1 ] = max101 - p3x2;
                m3x2[ thisCodon ][ 3 * partialCodon + 2 ] = max011 - p3x2;
            }

            m3x1[ thisCodon ][ 3 * d1 + 0 ] = max100 - p3x1;
            m3x1[ thisCodon ][ 3 * d1 + 1 ] = max010 - p3x1;
            m3x1[ thisCodon ][ 3 * d1 + 2 ] = max001 - p3x1;
        }
    }


    return { "3x1": m3x1, "3x2": m3x2, "3x4": m3x4, "3x5": m3x5 };
}

function pSM2cSM(_scorematrix, _letters)
{
    LoadFunctionLibrary( "GrabBag" );

    _cdnScoreMatrix  = { 65,65 };
    _mapping      = mapStrings( _hyphyAAOrdering, _letters );

    for ( _k = 0; _k < 64; _k += 1 ) {
        _mappedK = _mapping[ _Genetic_Code[ _k ] ];
        if ( _mappedK >= 0) {
            for ( _k2 = _k; _k2 < 64; _k2 += 1 ) {
                _mappedK2 = _mapping[ _Genetic_Code[ _k2 ] ];
                if ( _mappedK2 >= 0 ) {
                    _aScore = _scorematrix[ _mappedK ][ _mappedK2 ];
                    if ( _mappedK == _mappedK2 && _k2 > _k ) {
                        _aScore = _aScore - 1;
                    }
                } else {
                    // stop codons don't match anything
                    _aScore = -1e4;
                }
                _cdnScoreMatrix[ _k ][ _k2 ] = _aScore;
                _cdnScoreMatrix[ _k2 ][ _k ] = _aScore;
            }
        } else {
            for ( _k2 = _k; _k2 < 64; _k2 += 1 ) {
                _mappedK2 = _mapping[ _Genetic_Code[ _k2 ] ];
                if ( _mappedK2 < 0 ) {
                    // don't penalize stop codons matching themselves
                    _cdnScoreMatrix[ _k ][ _k2 ] = 0;
                    _cdnScoreMatrix[ _k2 ][ _k ] = 0;
                } else {
                    _cdnScoreMatrix[ _k ][ _k2 ] = -1e4;
                    _cdnScoreMatrix[ _k2 ][ _k ] = -1e4;
                }
            }
        }
    }

    return _cdnScoreMatrix;
}
