/*
 *                  BioJava development code
 *
 * This code may be freely distributed and modified under the
 * terms of the GNU Lesser General Public Licence.  This should
 * be distributed with the code.  If you do not have a copy,
 * see:
 *
 *      http://www.gnu.org/copyleft/lesser.html
 *
 * Copyright for this code is held jointly by the individual
 * authors.  These should be listed in @author doc comments.
 *
 * For more information on the BioJava project and its aims,
 * or to join the biojava-l mailing list, visit the home page
 * at:
 *
 *      http://www.biojava.org/
 * 
 * Created on Jan 29, 2006
 *
 */
package org.biojava.nbio.structure.align.pairwise;


import org.biojava.nbio.structure.align.helper.AligMatEl;
import org.biojava.nbio.structure.align.helper.GapArray;
import org.biojava.nbio.structure.align.helper.IndexPair;

import java.util.ArrayList;
import java.util.List;

/** a class to perform Gotoh algorithm
 *
 * @author Andreas Prlic (Java), Peter Lackner (original C code)
 * @since 10:56:53 AM
 * @version %I% %G%
 */
public class Gotoh {
    public static int ALIGFACTOR = 1000; // constant to shift floats to ints

    Alignable a;

    int k,openPen,elgPen,rowDim,colDim,openVal,elgVal;

    AligMatEl currentCell;
    GapArray currentGap;



    public Gotoh(Alignable alignable) {
        super();
        a = alignable;

        align();

    }



    private void align() {

        rowDim = a.getRows()+1;
        colDim = a.getCols()+1;

        openPen = Math.round(ALIGFACTOR * a.getGapOpenCol());
        elgPen  = Math.round(ALIGFACTOR * a.getGapExtCol());

        GapArray[] gapCol = new GapArray[colDim];
        GapArray[] gapRow = new GapArray[rowDim];

        // init the arrays
        for ( int i = 0 ; i< colDim;i++){
            gapCol[i] = new GapArray();
        }
        for ( int j = 0 ; j< rowDim;j++){
            gapRow[j] = new GapArray();
        }
        currentGap = new GapArray();

        AligMatEl[][]aligmat = a.getAligMat();
        int lastValue = aligmat[rowDim-1][colDim-1].getValue();

//      first cell
        aligmat[0][0].setValue(0);
        gapCol[0].setValue(0);
        gapCol[0].setIndex(0);
        gapRow[0].setValue(0);
        gapRow[0].setIndex(0);

        // set row 0 margin
        for(int j=1;j<colDim;j++) {
            aligmat[0][j].setValue(0);
            gapCol[j].setValue(-(openPen+elgPen));
            gapCol[j].setIndex(0);
        }


        for(int rowCounter=1;rowCounter<rowDim;rowCounter++){

            // set column 0 margin
            aligmat[rowCounter][0].setValue(0);
            gapRow[rowCounter].setValue(-(openPen+elgPen));
            gapRow[rowCounter].setIndex(0);

            for(int colCounter=1;colCounter<colDim;colCounter++) {

                currentCell = aligmat[rowCounter][colCounter];

                // no gap 
                currentCell.setValue( aligmat[rowCounter-1][colCounter-1].getValue() + currentCell.getValue());
                currentCell.setRow((short)(rowCounter-1));
                currentCell.setCol((short)(colCounter-1));

                // scan column j for gap, gap in seqB
                openVal = aligmat[rowCounter-1][colCounter].getValue() - (openPen+elgPen);
                elgVal  = gapCol[colCounter].getValue()-elgPen;

                currentGap = new GapArray();

                if ( openVal >= elgVal){
                    currentGap.setValue(openVal);
                    currentGap.setIndex(rowCounter-1);
                } else {
                    currentGap.setValue(elgVal);
                    currentGap.setIndex(gapCol[colCounter].index);
                }
                gapCol[colCounter] = currentGap;

                if (currentGap.getValue() > currentCell.getValue()){
                    if ( currentGap.getIndex() >= rowDim)
                        System.err.println("col gap at" + rowCounter + " " + colCounter + " to " + currentGap.getIndex());
                    currentCell.setValue( currentGap.getValue());
                    currentCell.setRow((short)currentGap.getIndex());
                    currentCell.setCol((short)colCounter);
                }

//              scan row i for gap, gap in row
                openVal = aligmat[rowCounter][colCounter-1].getValue()-(openPen+elgPen);
                elgVal  = gapRow[rowCounter].getValue() - elgPen;

                currentGap = new GapArray();

                if (openVal >= elgVal){
                    currentGap.setValue(openVal);
                    currentGap.setIndex(colCounter-1);
                } else {
                    currentGap.setValue(elgVal);
                    currentGap.setIndex(gapRow[rowCounter].getIndex());
                }
                gapRow[rowCounter] = currentGap;


                if ( currentGap.getValue() > currentCell.getValue() ) {
                    if ( currentGap.getIndex() >= colDim)
                        System.err.println("row gap at" + rowCounter + " " + colCounter + " to " + currentGap.getIndex());
                    currentCell.setValue(currentGap.getValue());
                    currentCell.setRow((short)rowCounter);
                    currentCell.setCol((short)currentGap.getIndex());
                }

                aligmat[rowCounter][colCounter]=currentCell;
            }

        }


        // last cell in alignment matrix
        // do not penalize end gaps
        int rowCount = rowDim -1;
        int colCount = colDim -1;
        currentCell = aligmat[rowCount][colCount];

        // no gap
        currentCell.setValue(aligmat[rowCount-1][colCount-1].getValue() + lastValue);
        currentCell.setRow((short)(rowCount-1));
        currentCell.setCol((short)(colCount-1));

        // scan last column j for gap, gap in seqB
        // do not penalyze gaps
        for (int k=1;k<=rowCount;k++) {
            int val = aligmat[rowCount-k][colCount].getValue();
            if ( val>currentCell.getValue()){
                currentCell.setValue(val);
                //System.out.println("setting row to " + (rowCount ) );
                currentCell.setRow((short)(rowCount-k ));
                currentCell.setCol((short)(colCount  ));
            }
        }

        // scan row i for gap, gap in SeqA 
        // do not penalyze gaps 
        for (int k=1;k<=colCount;k++) {
            int val = aligmat[rowCount][colCount-k].getValue();
            if ( val > currentCell.getValue()){
                currentCell.setValue(val);
                currentCell.setRow((short) (rowCount ) );
                currentCell.setCol((short)(colCount-k  ));
            }
        }
        a.setScore(aligmat[rowDim-1][colDim-1].getValue() / (float)ALIGFACTOR);
        setPath();

    }

    private void setPath(){

        // dmyersturnbull: TODO fix exception handling and logging

        int  n;
        IndexPair[] backId = new IndexPair[a.getRows()+1+a.getCols()+1];
        List<IndexPair> path = new ArrayList<IndexPair>();

        backId[0] = new IndexPair((short)(a.getRows()),(short)(a.getCols()));

        // backtrace, get backId indices, the indices in diagonal store in path


        int pathsize = 0;

        AligMatEl[][] aligmat = a.getAligMat();


        n=1;
        while ( (backId[n-1].getRow()>=1) &&
                (backId[n-1].getCol()>=1)
                )
        {
            // get backtrace index
            int x = backId[n-1].getRow();
            int y = backId[n-1].getCol();

            try {

                AligMatEl el = null ;
                try {
                    el =aligmat[x][y];
                } catch(Exception e){


                    e.printStackTrace();
                    for (int f=0; f< n;f++){
                        System.out.println(backId[f]);
                    }

                }

                if ( el == null)
                    System.out.println("el = null! x:"+ x + " y " + y);
                backId[n] = el;
            } catch (Exception e){
                e.printStackTrace();
                System.out.println("x " + x);
                System.out.println("y " + y);
                System.out.println(backId[n-2]);
                System.exit(0);
            }
            // get diagonal indeces into path
            if (((backId[n-1].getRow() - backId[n].getRow()) == 1)
                    && (( backId[n-1].getCol() - backId[n].getCol()) == 1)) {
                path.add(backId[n-1]);
                pathsize++;

            }
            n++;
        }

        // path is reverse order..
        // switch order
        IndexPair[] newpath = new IndexPair[pathsize];
        for (int i = 0 ; i < pathsize; i++){
            IndexPair o = (IndexPair)path.get(pathsize-1-i);
            IndexPair np = new IndexPair((short)(o.getRow()-1),(short)(o.getCol()-1));
            newpath[i] = np;
        }

        a.setPath(newpath);
        a.setPathSize(pathsize);


    }
}
