package analysis.transfacScan;

import analysis.promotersites.PromoterList;
import exceptions.HandledError;

import java.io.*;
import java.util.*;

/**
 * Scans for Transcription factors found within a given list of promoters
 */
public class PromoterScanThreadHandler {
    private PromoterList queries;
    private MatrixList matrices;
    private BindingSiteList transcriptionFactorSites;
    private double deficit = 0;


    /**
     *
     * @param queries
     * @param matrices
     * @param deficit chosen by user, this is the decrease from the maximum similarity score possible. The greater the
     *                deficit the lower the stringency of the search
     */
    public PromoterScanThreadHandler(PromoterList queries, MatrixList matrices, double deficit) {
        this.queries = queries;
        this.matrices = matrices;
        this.deficit = deficit;
        runScan();

    }

    /**
     * This constructor will be used to determine the cutOff scores for the individual Transcription Factors
     * It will generate a set of sequences that have a specified gc content and will return a specified number of promoters
     * that are a specified length.
     * @param matrix The matrix you want to add to this matrixList
     * @param queries The promoter queries you wish to scan
     *
     *
     */
    public PromoterScanThreadHandler(TranscriptionFactor matrix, PromoterList queries){
        this.queries = queries;
        this.matrices = new MatrixList();
        this.deficit = deficit;
        matrices.add(matrix);
        runScan();

    }




    private void runScan(){

        transcriptionFactorSites = new BindingSiteList();

        for (Promoter promoter : queries){
            for (TranscriptionFactor matrix : matrices) {
                findAndAddSites(matrix, promoter);
            }


        }
    }

    /**
     * this takes the matrix chosen by the user and the promoter DNA sequence and scans it to determine if there are
     * any similar areas as defined by the Matrix Similarity Score
     *
     * When scanning the negative strand the start of the site is in relation to the sequence on the negative strand.
     *
     * @param matrix
     * @param promoter
     * @return
     */
    private void findAndAddSites(TranscriptionFactor matrix, Promoter promoter) {

        List<byte[]> pentanucs = promoter.toPentanuc();

        int core = matrix.defineCoreStart();
        int length = pentanucs.size();
        TranscriptionFactor coreMatrix = matrix.getCore();
        boolean isNotAtEnd = false;
        double maxCutOff = matrix.getMaximumCoreCutOff();
        double maxMatrixCutOff = matrix.getMaxMatrixCutOff();
        double cutOff = maxCutOff - deficit;
        double matrixCutOff = maxMatrixCutOff - deficit;

        //System.out.println("cutOff is " + cutOff + "matrix cutoff is: " + matrixCutOff);

        for(int i = 0; i< length; i++ ){

            byte[] currentSeq = pentanucs.get(i);

            int offset = 0;
            double msScore = coreMatrix.matrixSimilarityScore(currentSeq);

            int b = i - matrix.defineCoreStart();

            int c = (length - i) - (matrix.sizeOfMatrix() - (matrix.defineCoreStart()+ 5));

            if ((b >= 0) && (c >= 0)){
                isNotAtEnd = true;
            }
            else {
                isNotAtEnd = false;
            }

            if((msScore >= cutOff) && isNotAtEnd){

                byte[] getWholeSite = getWholeSite(i, promoter, matrix);

                double wholeMSScore = matrix.matrixSimilarityScore(getWholeSite);

                    if(wholeMSScore >= matrixCutOff){
                        offset = i - matrix.defineCoreStart();

                        transcriptionFactorSites.addSite(new TFSite(promoter, matrix, offset, msScore, wholeMSScore, TFSite.POSITIVESTRAND));
                       // System.out.println("in scan cutoff is;" + cutOff);
                    }
            }
        }

        Promoter reverseProm = promoter.reverseComplement();
        List<byte[]> revPentanucs = reverseProm.toPentanuc();

        for(int i = 0; i< length; i++ ){

            byte[] currentSeq = revPentanucs.get(i);

            int offset = 0;
            double msScore = coreMatrix.matrixSimilarityScore(currentSeq);

            int b = i - matrix.defineCoreStart();

            int c = (length - i) - (matrix.sizeOfMatrix() - (matrix.defineCoreStart()+5));

            if ((b >= 0) && (c >= 0)){
                isNotAtEnd = true;
            }
            else {
                isNotAtEnd = false;
            }

            if((msScore >= cutOff) && isNotAtEnd){

                byte[] getWholeSite = getWholeSite(i, reverseProm, matrix);

                double wholeMSScore = matrix.matrixSimilarityScore(getWholeSite);

                if(wholeMSScore >= matrixCutOff){
                    int position = i - matrix.defineCoreStart();
                    offset = promoter.getLength() - position;


                    transcriptionFactorSites.addSite(new TFSite(promoter, matrix, offset, msScore, wholeMSScore, TFSite.NEGATIVESTRAND));
                }
            }

        }
    }



    /**
     * takes the core of a matrix, finds the whole matrix and extracts the same sequence from the promoter in question
     * @param current
     * @param promoter
     * @param matrix
     * @return
     */
    public byte[] getWholeSite(int current, Promoter promoter, TranscriptionFactor matrix){

        List<byte[]> pentanucs = promoter.toPentanuc();

        int offset = matrix.defineCoreStart();

        int endCore = offset + 5;

        int length = matrix.sizeOfMatrix();

        int size =length - endCore;

        byte[] start = new byte[offset];

        byte[] end = new byte[size];



        byte[] core = pentanucs.get(current);

        int j = 0;

        for (int i = offset; i > 0; i--){

            byte[] temp = pentanucs.get(current - i);
            start[j] = temp[0];

            j++;
        }
        j = 0;
        int l = length - endCore;

        for(int i = 0; i < l; i++){

            byte[] temp = pentanucs.get(current + i);

            end[j] = temp[0];

            j++;

        }

        byte[] wholeSiteStart = new byte[start.length + core.length];

        System.arraycopy(start, 0, wholeSiteStart, 0, start.length);
        System.arraycopy(core, 0, wholeSiteStart, start.length, core.length);

        byte[] wholeSite = new byte[length];

        System.arraycopy(wholeSiteStart, 0, wholeSite, 0, wholeSiteStart.length);
        System.arraycopy(end, 0, wholeSite, wholeSiteStart.length, end.length);

        return wholeSite;

    }

    /**
     * writing results to a file
     * will delete file is it already exists!
     */
    public void resultsToFile(File file){


        try{
            if(file.exists()){
                file.delete();
            }
            file.createNewFile();

            FileWriter fw = new FileWriter(file);
            BufferedWriter bw = new BufferedWriter(fw);

            for(Promoter promoter : queries){
                ArrayList<TFSite> sites = new ArrayList<TFSite>(transcriptionFactorSites.getSites(promoter));

                bw.write(">" + promoter.getName());
                bw.newLine();

                for(TFSite site : sites){
                    //System.out.println(site.getTFName() + site.getLowerPosition() + site.getUpperPosition() + site.getMatrixScore());


                         String content = site.getTFName() + "\t" + site.getLowerPosition() + "\t" + site.getMatrixScore();


                         bw.write(content);
                        bw.newLine();

                    //System.out.println(content);

                }
            }
            bw.close();
        }


            catch (IOException ex) {
               throw new HandledError("File Access Error", "Unable to Write Local File " + file, ex);
                }

    }


    public BindingSiteList getResult(){
        return transcriptionFactorSites;
    }

    public boolean isSites(TranscriptionFactor tf){
        boolean b = false;

        if ((transcriptionFactorSites.getTFSitesByTranscriptionFactor(tf))!=null){
            b = true;
        }


        return b;
    }

}
