/*
 * Copyright 2009 Lari Natri <lari.natri@gmail.com>
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 * 
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 * 
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */


package compid.tools;


import compid.importers.*;
import compid.data.DAOException;
import compid.data.tuples.ProtSeq;
import net.aavalla.utils.jobpool.ParameterizedJob;
import net.aavalla.utils.jobpool.parameters.FieldAnnotations;
import net.aavalla.utils.jobpool.parameters.ParameterCollection;
import net.aavalla.utils.general.FileUtils;
import compid.importers.ImportException.ExceptionType;
import net.aavalla.utils.jobpool.parameters.InputFile;
import net.aavalla.utils.jobpool.parameters.validators.InputFileValidator;
import net.aavalla.utils.jobpool.parameters.OutputFile;
import net.aavalla.utils.jobpool.parameters.validators.OutputFileValidator;
import net.aavalla.utils.jobpool.parameters.validators.StringValidator;
import compid.tools.FASTACompactor.FASTACompactorParams;
import net.aavalla.utils.general.FastaUtils;
import net.aavalla.utils.general.FastaUtils.FastaLineType;
import net.aavalla.utils.general.StrUtils;
import net.aavalla.utils.jobpool.JobCategory;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

/**
 * Job class to compact FASTA protein sequence databases
 * @author Lari Natri
 */
public class FASTACompactor extends ParameterizedJob<Object,FASTACompactorParams> {

    public class FASTACompactorParams extends ParameterCollection {

        @FieldAnnotations(
            name="Input file",
            description="Protein sequence database in FASTA format",
            validator=InputFileValidator.class)
        private InputFile sourceFile;

        @FieldAnnotations(
            name="Output file",
            description="Output file (FASTA), will be overwritten",
            validator=OutputFileValidator.class)
        private OutputFile destinationFile;

        @FieldAnnotations(
            name="Match string",
            description="Output file will contain only sequences which contain " +
                        "this string in their description. \nCase is ignored.",
            validator=StringValidator.class,
            validatorSettings={"1","128",FieldAnnotations.NULL})
        private String matchString = "[Homo sapiens]";

        public FASTACompactorParams() {
            this.setDescription("Copies a subset of sequences from a protein sequence<br/>" +
                                "database given as input.");
            this.setHelpID("compactor");
        }

        public File getSourceFile() { return this.sourceFile; }
        public File getDestinationFile() { return this.destinationFile; }
        public String getMatchString() { return this.matchString; }

    } // class

    private static final double MINAARATIOINSEQUENCE = 0.95d;
    private static final int MAXERRONEUSSEQUENCES = 500;

    public FASTACompactor() {
        super();
        this.addCategory(JobCategory.FILESYSTEM);
        this.setName("FASTA database compactor");
    }


    @Override
    public FASTACompactorParams getNonfinalEditableParameters() {
        return new FASTACompactorParams();
    }

    @Override
    public Object work(FASTACompactorParams params) {

        FileReader fileReader = null;
        BufferedReader bufReader = null;
        FileWriter fileWriter = null;
        BufferedWriter bufWriter = null;
        int erroneusSequencesCount = 0;

        if (!params.getSourceFile().exists())
            throw new ImportException(ExceptionType.FILENOTFOUND,
                                      params.getSourceFile().toString());
        if (!params.getSourceFile().canRead())
            throw new ImportException(ExceptionType.FILENOTREADABLE,
                                      params.getSourceFile().toString());

        FileUtils.EOLStyle eolStyle = FileUtils.EOLStyle.UNKNOWN;
        try {
            eolStyle = FileUtils.getFileEOLStyle(params.getSourceFile());
        } catch (FileNotFoundException ex) {
            throw new ImportException(ExceptionType.FILENOTFOUND,
                                      params.getSourceFile().toString());
        } catch (IOException ioex) {
            throw new ImportException(ExceptionType.FILENOTREADABLE,
                                      params.getSourceFile().toString());
        }
        if (eolStyle.equals(FileUtils.EOLStyle.UNKNOWN)) {
            eolStyle = FileUtils.EOLStyle.SYSTEM;
        }

        long fileSize = params.getSourceFile().length();
        long curChars = 0;

        String matchUpper = params.getMatchString().toUpperCase();

        int matchingSequencesCount = 0;

        try {
            fileReader = new FileReader(params.getSourceFile());
            bufReader = new BufferedReader(fileReader,
                                           FASTAImporter.FILEBUFFERSIZE);
            fileWriter = new FileWriter(params.getDestinationFile());
            bufWriter = new BufferedWriter(fileWriter,
                                           FASTAImporter.FILEBUFFERSIZE);
            String nextFullHeaderLine;

            do { // read file until first header is found
                nextFullHeaderLine = bufReader.readLine();
                if (nextFullHeaderLine==null) { // EOF reached
                    throw new ImportException(ExceptionType.FASTA_INCORRECTFORMAT,
                            "File "+params.getSourceFile().getName()+" is not in " +
                            "FASTA format. (Didn't find any headers.)");
                }
                curChars+=nextFullHeaderLine.length();
                nextFullHeaderLine = nextFullHeaderLine.trim();
            } while (FastaUtils.getFastaLineType(nextFullHeaderLine)!=FastaLineType.HEADER);

            while (nextFullHeaderLine!=null) {
                this.reportProgress((int)Math.round(100*curChars/(double)fileSize));

                boolean entryComplete = false;
                StringBuilder sequenceBuilder = new StringBuilder();
                String fullHeaderLine = nextFullHeaderLine;

                while (!entryComplete) { // read lines until entry is complete
                    String line = bufReader.readLine();
                    if (line==null) { // EOF reached
                        entryComplete = true;
                        nextFullHeaderLine = null; // to signify EOF
                    } else { // a line was read
                        curChars+=line.length();
                        line = line.trim();
                        switch(FastaUtils.getFastaLineType(line)) {
                            case HEADER:
                                nextFullHeaderLine = line;
                                entryComplete = true;
                                break;
                            case SEQUENCE:
                                sequenceBuilder.append(line);
                                break;
                            case COMMENT:
                            case OTHER:
                            default:
                                break;
                        }
                    } // else
                }

                if (!fullHeaderLine.toUpperCase().contains(matchUpper)) {
                    continue; // abort if string is not found
                } else {
                    ++matchingSequencesCount;
                }

                ProtSeq protSeq = new ProtSeq(sequenceBuilder.toString(), true);
                double ratioOfAA = ((double)protSeq.getLength()) / ((double)sequenceBuilder.length());
                if (ratioOfAA < MINAARATIOINSEQUENCE) { // too many non-AA symbols in sequence
                    System.out.println("DEBUG: sequence has "+(sequenceBuilder.length()-protSeq.getLength())+" non-AA symbols (they were removed)");
                    System.out.println("       remaining size: "+protSeq.getLength());

                    ++erroneusSequencesCount;
                    if (erroneusSequencesCount > MAXERRONEUSSEQUENCES) {
                        throw new ImportException(ExceptionType.FASTA_INCORRECTFORMAT,
                                "File "+params.getSourceFile().getName()+" is not in " +
                                "FASTA format. (Too many erroneus sequences.)");
                    }
                }

                bufWriter.write(fullHeaderLine + eolStyle.EOL());
                String[] splitOrigSeq = StrUtils.slice(sequenceBuilder.toString(), 70);
                for (int i=0; i < splitOrigSeq.length; ++i) {
                    bufWriter.write(splitOrigSeq[i] + eolStyle.EOL());
                }


            } // end: while there is something to read in the FASTA file

            System.out.println("DEBUG: There were "+erroneusSequencesCount+" erroneus sequences");
            return "Output file has "+matchingSequencesCount+" matching sequences.";


        } catch (FileNotFoundException ex) {
            throw new ImportException(ExceptionType.FILENOTFOUND,
                                      params.getSourceFile().getAbsolutePath());
        } catch (IOException ex) {
            throw new ImportException(ex);
        } catch (DAOException ex) {
            throw new ImportException(ex);
        } finally {
            try {
                bufWriter.close();
                fileWriter.close();
                bufReader.close();
                fileReader.close();
            } catch (Exception ex) {} // never mind this
        }

    }


}
