/*
 * Copyright 2009, 2010 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.importers;

import net.aavalla.utils.jobpool.parameters.FieldAnnotations;
import net.aavalla.utils.jobpool.parameters.validators.DoubleMinMaxValidator;
import net.aavalla.utils.jobpool.parameters.validators.IntegerMinMaxValidator;
import compid.data.*;
import compid.data.tuples.*;
import compid.importers.ImportException.*;
import compid.importers.MascotDatImporter.MascotDatImporterParams;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Date;
import matrix_science.msparser.*;

        

/**
 *
 * @author Lari Natri
 */
public final class MascotDatImporter extends DatasetFileImporter<MascotDatImporterParams> {
    
    public class MascotDatImporterParams extends DatasetFileImporterParams {

        private static final boolean DEF_MUDPIT = true;
        private static final boolean DEF_REQUIREBOLDRED = true;
        private static final double  DEF_MINPROB = 0.05d;
        private static final int     DEF_MAXHITS = 6000;
        private static final double  DEF_IGNIONSCOREBELOW = 0;
        private static final int     DEF_MINPEPLEN = 0;
        private static final double  DEF_SUBSETTHRESHOLD = 1.0d;

        @FieldAnnotations(
            name="Max Protein Groups",
            description="Maximum number of protein groups (ie winner protein hits) that will\n" +
                        "be imported beginning with best scoring ones. In addition to these,\n" +
                        "secondary proteins are imported for each group, if so desired.\n" +
                        "<i>See Mascot documentation for details.</i>",
            validator=IntegerMinMaxValidator.class,
            validatorSettings={"1", "20000"})
        private int     maxHits = DEF_MAXHITS;

        @FieldAnnotations(
            name="Require Bold Red",
            description="<i>See Mascot documentation for details.</i>")
        private boolean requireBoldRed = DEF_REQUIREBOLDRED;

        @FieldAnnotations(
            name="Mudpit scoring",
            description="Use mudpit scoring." +
                        "<i>See Mascot documentation for details.</i>")
        private boolean mudPit = DEF_MUDPIT;
        
        @FieldAnnotations(
            name="Winner Protein Probability Cutoff",
            description="Only proteins with probability less than this of being random\n" +
                        "matches will be included as winner proteins thus limiting the\n" +
                        "amount of imported protein groups also. Subset proteins for the\n" +
                        "groups are not limited by this. The smaller the number, the better\n" +
                        "quality matches will be gotten, <b>but</b> value 0.0 means that\n" +
                        "cutoff will be ignored: all protein groups will be imported unless\n" +
                        "constrained by other parameters. \n" +
                        "<i>See Mascot documentation for details.</i>",
            validator=DoubleMinMaxValidator.class,
            validatorSettings={"0.0","1.0"})
        private double  minProb = DEF_MINPROB;

        @FieldAnnotations(
            name="Ions Score Cutoff",
            description="Any peptides with a score less than this will be ignored. If the value \n" +
                        "is greater than 0 and less than 1 then this is assumed to be a \n" +
                        "probability rather than an absolute score. Affects the amount of proteins, \n" +
                        "but worse quality peptides are imported anyway as unassigned peptides, \n" +
                        "if <i>Include Unassigned Peptides</i> is chosen. Value 0.0 means that \n" +
                        "this cutoff parameter is not used.\n" +
                        "<i>See Mascot documentation for details.</i>",
            validator=DoubleMinMaxValidator.class,
            validatorSettings={"0.0","1000"})
        private double  ignIonScoreBelow = DEF_IGNIONSCOREBELOW;

        @FieldAnnotations(
            name="Subset Threshold",
            description="Sub protein in a group must have a score greater than or equal\n " +
                        "to [the main protein's score * (1 - subset threshold)] for it\n " +
                        "be imported. 0 means no sub proteins (only winner proteins\n " +
                        "from each group is imported). 1 means all subset proteins\n " +
                        "are included.",
            validator=DoubleMinMaxValidator.class,
            validatorSettings={"0.0","1.0"})
        private double subsetsThreshold = DEF_SUBSETTHRESHOLD;

        @FieldAnnotations(
            name="Min Peptide Length",
            description="Ignores peptides with sequence length below the given value when\n" +
                        "grouping proteins. Non-passing peptides will be imported as\n" +
                        "unassigned peptides, if so desired.\n" +
                        "<i>See Mascot documentation for details.</i>",
            validator=IntegerMinMaxValidator.class,
            validatorSettings={"0",FieldAnnotations.NULL})
        private int     minPepLen = DEF_MINPEPLEN;

        @FieldAnnotations(
            name="Include Complete Group Members",
            description="Includes such 'subproteins' that have exactly the same set \n" +
                        "of peptides as the group's winner protein. <b>If this and <i>Include \n" +
                        "Subset Group Members</i> are both unchecked, only winner \n" +
                        "proteins (one per group) are included.</b>")
        private boolean includeCompleteSubProts = true;

        @FieldAnnotations(
            name="Include Subset Group Members",
            description="Imports such 'subproteins' that have a subset of the peptides \n" +
                        "assigned to group's winner protein. <b>If this and <i>Include Complete \n" +
                        "Group Members</i> are both unchecked, only winner proteins are \n" +
                        "included.</b>")
        private boolean includeSubsetSubProts = true;

        @FieldAnnotations(
            name="Include Unassigned Peptides",
            description="Import also peptides which aren't assigned to any proteins. \n" +
                        "This should be chosen if one wishes to create peptide reports \n" +
                        "for all peptides. All queries are imported in any case.")
        private boolean includeUnassigned = true;

        public boolean getMudPit() { return this.mudPit; }
        public boolean getRequireBoldRed() { return this.requireBoldRed; }
        public double  getMinProb() { return this.minProb; }
        public int getMaxHits() { return this.maxHits; }
        public double getIgnIonScoreBelow() { return this.ignIonScoreBelow; }
        public int getMinPepLen() { return this.minPepLen; }
        public boolean getIncCompleteSubProts() { return this.includeCompleteSubProts; }
        public boolean getIncSubsetSubProts() { return this.includeSubsetSubProts; }

        public MascotDatImporterParams() {
            this.setDescription("Imports a dataset in Mascot's DAT format.");
        }

    }

    
    private static final String DESCRIPTION = "Dataset importer for Mascot (dat)";
    private static final String FILE_EXT = ".dat"; // NOI18N
    private static final String LOCUSPREFIX = "LOCUS:"; // NOI18N

    private static final int     DEF_BASEFLAGS
            = ms_mascotresults.MSRES_DUPE_DEFAULT | ms_mascotresults.MSRES_GROUP_PROTEINS | ms_mascotresults.MSRES_SHOW_SUBSETS;

    public MascotDatImporter() {
        super();
        this.setName(DESCRIPTION);
    }

    @Override
    protected MascotDatImporterParams getNonfinalEditableParameters() {
        return new MascotDatImporterParams();
    }

    
    @Override
    public String getFileNameExtension() { return FILE_EXT; }
    
    
    @Override
    public FullDataset getAsDataSet(final MascotDatImporterParams pars)
            throws ImportException
    {
        final DecimalFormat twoDig = new DecimalFormat("#############0.00");
        StringBuilder paramsB = new StringBuilder();
        paramsB.append("Max Groups = ").append(pars.maxHits);
        paramsB.append("; Bold Red = ").append(pars.requireBoldRed);
        paramsB.append("; Mudpit = ").append(pars.mudPit);
        paramsB.append("; Winner Prob Cutoff = ").append(twoDig.format(pars.minProb));
        paramsB.append("; Ignore IonsScore below = ").append(twoDig.format(pars.ignIonScoreBelow));
        paramsB.append("; Subset threshold = ").append(twoDig.format(pars.subsetsThreshold));
        paramsB.append("; Min Peptide length = ").append(pars.minPepLen);
        paramsB.append("; Complete Sub Proteins = ").append(pars.includeCompleteSubProts);
        paramsB.append("; Subset Sub Proteins = ").append(pars.includeSubsetSubProts);
        paramsB.append("; Unassigned Peptides = ").append(pars.includeUnassigned);
        
        compid.data.FullDataset cpFullDataSet = new FullDataset();
        compid.data.tuples.DatasetInfo cpDSInfo
                = new DatasetInfo(pars.getName(),pars.getDescr(),pars.getDB(),pars.getFile().getName(),
                                  DatasetInfo.AlgType.MASCOT,paramsB.toString(), new Date());
        cpFullDataSet.addDataSetInfo(cpDSInfo);

        int locusPrefixLength = LOCUSPREFIX.length();

        try {
            if (!pars.getFile().exists())
                throw new ImportException(ExceptionType.FILENOTFOUND,
                                          pars.getFile().getAbsolutePath());
            if (!pars.getFile().canRead())
                throw new ImportException(ExceptionType.FILENOTREADABLE,
                                          pars.getFile().getAbsolutePath());
            
            ms_mascotresfile msFile = new ms_mascotresfile(pars.getFile().getPath());
            
            if (!msFile.isValid())
                throw new ImportException(ExceptionType.MASC_FILENOTVALID);
            if (!msFile.isMSMS())
                throw new ImportException(ExceptionType.MASC_NOMSMSDATA);

            int flags = DEF_BASEFLAGS |
                    (pars.getMudPit() ? ms_mascotresults.MSRES_MUDPIT_PROTEIN_SCORE : 0) |
                    (pars.getRequireBoldRed() ? ms_mascotresults.MSRES_REQUIRE_BOLD_RED : 0);
            
            ms_mascotresults msResults = new ms_peptidesummary(msFile,
                                             flags, pars.getMinProb(),
                                             pars.getMaxHits(), null,
                                             pars.getIgnIonScoreBelow(),
                                             pars.getMinPepLen(), null);

            msResults.setSubsetsThreshold(pars.subsetsThreshold);
            
            if((msFile.getLastError()) > 0) {
                throw new ImportException("Error: " + msFile.getLastErrorString());
            }

            // for debugging
            System.out.println("DEBUG: DAT parser: " +
                    "queries = " + msFile.getNumQueries() +
                    ", prothits = " + msResults.getNumberOfHits()+" ==> going to loop");


            compid.data.tuples.ProteinHit.GroupMemberStatus groupMemberStatus;
            compid.data.tuples.Peptide cpPeptide;
            compid.data.tuples.PeptideHit cpPeptideHit;
            compid.data.tuples.ProtHitPeptHitJoin cpProtPepHit;
            compid.data.tuples.ProteinHit cpProteinHit;
            compid.data.tuples.Protein cpProtein;
            compid.data.tuples.Query cpQuery;
            
            ArrayList<ms_protein> msProtsInThisGroup;

//            ms_protein msProt;
            ms_peptide msPep;
            ms_inputquery msQuery;
            int i, p, pepCount, queryNr, subsetNr, msGrouping;
            boolean isMainProt;

            int debug_winners = 0;
            int debug_subcomplete = 0;
            int debug_subsubset = 0;
            int debug_subother = 0;
            
            // loop through protein hits
            for(int currMainHit=1; currMainHit<=msResults.getNumberOfHits(); ++currMainHit) {
                msProtsInThisGroup = new ArrayList<ms_protein>();
                msProtsInThisGroup.add(msResults.getHit(currMainHit)); // add 'main' protein hit
                if (pars.getIncCompleteSubProts()) { // get complete sub proteins
                    subsetNr = 1; // '1 is the main prot, 2 the first (best?) sub prot
                    while(true) { // get subset proteins
                        ms_protein msProt = msResults.getNextSimilarProtein(currMainHit, subsetNr++);
                        if (msProt==null) {
                            break;
                        } else {
                            msProtsInThisGroup.add(msProt);
                        }
                    }
                }
                if (pars.getIncSubsetSubProts()) { // get subset sub proteins
                    subsetNr = 1; // '1 is the main prot, 2 the first (best?) sub prot
                    while(true) { // get subset proteins
                        ms_protein msProt = msResults.getNextSubsetProtein(currMainHit, subsetNr++);
                        if (msProt==null) {
                            break;
                        } else {
                            msProtsInThisGroup.add(msProt);
                        }
                    }
                }


                isMainProt = true; // first is main
                for (ms_protein msProt : msProtsInThisGroup) {

                    String accession = msProt.getAccession();

                    if (isMainProt) {
                        groupMemberStatus = ProteinHit.GroupMemberStatus.WINNER;
                        isMainProt = false; // remaining are sub proteins
                        ++debug_winners;
                    } else {
                        msGrouping = msProt.getGrouping();
                        if (msGrouping==ms_protein.GROUP_COMPLETE) {
                            groupMemberStatus = ProteinHit.GroupMemberStatus.SUBPEPCOMPLETE;
                            ++debug_subcomplete;
                        } else if (msGrouping==ms_protein.GROUP_SUBSET) {
                            groupMemberStatus = ProteinHit.GroupMemberStatus.SUBPEPSUBSET;
                            ++debug_subsubset;
                        } else {
                            groupMemberStatus = ProteinHit.GroupMemberStatus.SUBUNKNOWN;
                            ++debug_subother;
                        }
                    }

                    cpProtein =
                            new Protein(accession,
                                        msResults.getProteinDescription(accession),
                                        null);

                    cpProteinHit = new ProteinHit(cpProtein,cpDSInfo,currMainHit,groupMemberStatus,msProt.getScore());
                    pepCount = msProt.getNumPeptides();

                    // loop through peptides associated with this protein hit
                    for(i=1; i <= pepCount; i++) {
                        queryNr = msProt.getPeptideQuery(i);
                        p       = msProt.getPeptideP(i);
                        if((p != -1) && (queryNr != -1)
                                     && (msProt.getPeptideDuplicate(i)!=
                                         ms_protein.DUPE_DuplicateSameQuery)) {
                            msPep = msResults.getPeptide(queryNr,p);
                            msQuery = new ms_inputquery(msFile,queryNr);
                            String scanTitle = msQuery.getStringTitle(true);
                            if (scanTitle.toUpperCase().startsWith(LOCUSPREFIX)
                                    && scanTitle.length()>locusPrefixLength) {
                                scanTitle=scanTitle.substring(locusPrefixLength);
                            }
                            if(msPep != null) {
                                //System.out.println("DEBUG: Q3_GetObserved:"+msPep.getObserved()+" Q4_MrExperimental:"+msPep.getMrExperimental()+" Q5_GetCharge:"+msPep.getCharge()+" PH3_GetMrCalc:"+msPep.getMrCalc());
                                cpPeptide = new Peptide(msPep.getPeptideStr().trim());
                                cpQuery = new Query(cpDSInfo, scanTitle.trim(),
                                                    msPep.getObserved(),
                                                    msPep.getCharge());
                                cpPeptideHit = new PeptideHit(cpPeptide,cpQuery,
                                                              msPep.getIonsScore());
                                cpProtPepHit = new ProtHitPeptHitJoin(cpProteinHit,
                                                                      cpPeptideHit);

                                // following adds all
                                cpFullDataSet
                                        .addProteinHitPeptideHitJoin(cpProtPepHit);
                            }
                            msQuery.delete();
                            msPep.delete();
                        }

                    } // for peptides


                    if (pepCount==0) cpFullDataSet.addProteinHit(cpProteinHit); // not really possible
                    msProt.delete(); // clear memory
                }
            } // for proteins


            // get peptides unassigned to any proteins (if they exist)

            msResults.createUnassignedList(ms_mascotresults.SCORE);

            for(i=1; i<=msResults.getNumberOfUnassigned(); i++) {
                msPep = msResults.getUnassigned(i);
                queryNr = msPep.getQuery();
                msQuery = new ms_inputquery(msFile,queryNr);
                String scanTitle = msQuery.getStringTitle(true);
                if (scanTitle.toUpperCase().startsWith(LOCUSPREFIX)
                        && scanTitle.length()>locusPrefixLength) {
                    scanTitle=scanTitle.substring(locusPrefixLength);
                }

                cpQuery = new Query(cpDSInfo,scanTitle.trim(),
                                    msPep.getObserved(),
                                    msPep.getCharge());
                if (msPep.getPeptideStr().trim().equals("") || pars.includeUnassigned==false) { // queryStr=="": query resulting in no pephit //NOI18N
                    cpFullDataSet.addQuery(cpQuery);
                } else { // pep hit exists and unassigned peps are included
                    cpPeptide = new Peptide(msPep.getPeptideStr().trim());
                    cpPeptideHit = new PeptideHit(cpPeptide,cpQuery,
                                                  msPep.getIonsScore());
                    // following adds all created in this block
                    cpFullDataSet.addPeptideHit(cpPeptideHit);
                }
                msQuery.delete();
                msPep.delete();
            }

            System.out.println("DEBUG: DAT importer: " +
                    cpFullDataSet.getQueries().size() + " queries, "  +
                    cpFullDataSet.getPeptides().size() + " peptides, " +
                    cpFullDataSet.getPeptideHits().size() + " peptide hits, " +
                    cpFullDataSet.getProteins().size() + " proteins, " +
                    cpFullDataSet.getProteinHits().size() + " protein hits.");
            System.out.println("DEBUG: DAT importer: prots w/poss dupes: " +
                    debug_winners + " winners, " + debug_subcomplete +
                    " complete, " + debug_subsubset + " subset, " + 
                    debug_subother + " other");

            msResults.delete();
            msResults = null;
            msFile.delete();
            msFile = null;
        } catch (Exception ex) {
            throw new ImportException(ex.toString());
        }


        
        return cpFullDataSet;
    }
}
