/*
 * Copyright 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.report.protgroup;

import compid.data.DAOException;
import compid.data.DAOFactory;
import compid.data.tuples.DatasetInfo;
import compid.data.tuples.ProteinHit.GroupMemberStatus;
import compid.report.ReportModel;
import compid.report.ReportPageModel;
import compid.report.Reporter;
import compid.report.protgroup.ProtGroupParams.ClusteringMethod;
import net.aavalla.utils.jobpool.JobPriority;
import net.aavalla.utils.jobpool.parameters.SyncEnumSet;
import net.aavalla.utils.general.StrUtils;
import net.aavalla.utils.gui.twovenn.VennParameters;
import net.aavalla.utils.jobpool.JobCategory;
import compid.report.VennReportPageModel;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.TreeSet;

/**
 *
 * @author Lari Natri
 */
public final class ProtGroupReporter extends Reporter<ProtGroupParams> {

    public static final DecimalFormat twoDigitFormat = new DecimalFormat("###############0.00");

    public ProtGroupReporter() {
        super();
        this.addCategory(JobCategory.MEMORY);
        this.addCategory(JobCategory.DB_READ);
        this.addConflictingCategory(JobCategory.DB_WRITE);
        this.setPriority(JobPriority.LOWER);
        this.setName("Protein Groups Reporter");
    }
    
    @Override
    protected final ProtGroupParams getNonfinalEditableParameters() {
        return new ProtGroupParams();
    }

    @Override
    protected final ReportModel createReport(ProtGroupParams p)
            throws DAOException
    {
        // Check whether right dataset exists or only the left
        boolean onlyLeft;
        if (p.right==null || p.right.getCollection()==null || p.right.getCollection().isEmpty()) {
            onlyLeft = true; // right datasets should be ignored
        } else {
            onlyLeft = false;
        }

        // Set those group statuses to be retrieved, which are going to be
        // displayed or considered (taken into account) in clustering.
        SyncEnumSet<GroupMemberStatus> statusesUnion = p.cutoffStatuses1.clone();
        statusesUnion.addAll(p.cutoffStatuses2);

        // Create a set containing dataset information tuples about both
        // dataset collections, left and right.
        HashSet<DatasetInfo> allDatasets = new HashSet<DatasetInfo>(p.left.getCollection());
        if (!onlyLeft) { // include right datasets too
            allDatasets.addAll(p.right.getCollection());
        }

        // Retrieve groups from the database
        ProtGroupCollection coll = DAOFactory.getInstance().getQueryHandler()
                .getProtGroupsInDatasets(allDatasets, statusesUnion,
                                         p.method.equals(ClusteringMethod.Sequence),
                                         true, p.ignoreOriginalGrouping,
                                         p.paragonScoreType);


        System.out.println("DEBUG: ProtGroups from database: " + coll.size());

        int groupsBeforeClustering = coll.size();


        coll.clusterize(p); // Perform the actual clustering



        System.out.println("DEBUG: ProtGroups after clustering: "+ coll.size());



        String dsLegend;
        String reportSubtitle;
        String uniqueLeftPageTitle;
        String summaryRowTitle;
        String groupingTypeUI = p.method.toString();

        if (onlyLeft) {
            dsLegend = p.left.getName();
            reportSubtitle = "Protein Groups by "+groupingTypeUI;
            summaryRowTitle = p.left.getName();
            uniqueLeftPageTitle = "Protein Groups list";
        } else {
            dsLegend = "X = {"+p.left.getName()+"}, Y= {"+p.right.getName()+"}";
            reportSubtitle = "Common Protein Groups by "+groupingTypeUI;
            summaryRowTitle = "X="+p.left.getName()+", Y="+p.right.getName();
            uniqueLeftPageTitle = "Unique in X";
        }

        ReportModel report = new ReportModel(p.reportTitle,reportSubtitle+": "+dsLegend,p);
        ReportPageModel summaryPage = new ReportPageModel("Summary", dsLegend);
        ReportPageModel commonGroups = new ReportPageModel("Common", dsLegend);
        ReportPageModel uniqueLGroups = new ReportPageModel(uniqueLeftPageTitle, dsLegend);
        ReportPageModel uniqueRGroups = new ReportPageModel("Unique in Y", dsLegend);

        int uniqueRowColumns = 8;
        if (p.displayProtNames) {
            ++uniqueRowColumns;
        }
        int commonRowColumns = uniqueRowColumns * 2;
        HashSet<Integer> leftDsIds = new HashSet<Integer>(p.left.size(),1);
        for (DatasetInfo dsi : p.left.getCollection()) {
            if (dsi.getPrimaryKey()<0) {
                throw new IllegalArgumentException("Dataset must have it's " +
                        "primary key available.");
            }
            leftDsIds.add(dsi.getPrimaryKey());
        }
        HashSet<Integer> rightDsIds;
        if (onlyLeft) {
            rightDsIds = new HashSet<Integer>(0,1);
        } else {
            rightDsIds = new HashSet<Integer>(p.right.size(),1);
            for (DatasetInfo dsi : p.right.getCollection()) {
                if (dsi.getPrimaryKey()<0) {
                    throw new IllegalArgumentException("Dataset must have it's " +
                            "primary key available.");
                }
                rightDsIds.add(dsi.getPrimaryKey());
            }
        }

        uniqueLGroups.addColumn("Original Groups Count");
        uniqueLGroups.addColumn("Member Protein Count");
        uniqueLGroups.addColumn("Accessions");
        if (p.displayProtNames) uniqueLGroups.addColumn("Names");
        uniqueLGroups.addColumn("Scores");
        uniqueLGroups.addColumn("Original Member Statuses");
        uniqueLGroups.addColumn("Peptide Counts");
        uniqueLGroups.addColumn("Query Counts");
        uniqueLGroups.addColumn("Query Scores");
        uniqueLGroups.addColumn("Queries");


        if (!onlyLeft) {
            uniqueRGroups.addColumn("Original Groups Count");
            uniqueRGroups.addColumn("Member Protein Count");
            uniqueRGroups.addColumn("Accessions");
            if (p.displayProtNames) uniqueRGroups.addColumn("Names");
            uniqueRGroups.addColumn("Scores");
            uniqueRGroups.addColumn("Original Member Statuses");
            uniqueRGroups.addColumn("Peptide Counts");
            uniqueRGroups.addColumn("Query Counts");
            uniqueRGroups.addColumn("Query Scores");
            uniqueRGroups.addColumn("Queries");

            commonGroups.addColumn("X: Original Groups Count");
            commonGroups.addColumn("X: Member Protein Count");
            commonGroups.addColumn("X: Accessions");
            if (p.displayProtNames) commonGroups.addColumn("X: Names");
            commonGroups.addColumn("X: Scores");
            commonGroups.addColumn("X: Original Member Statuses");
            commonGroups.addColumn("X: Peptide Counts");
            commonGroups.addColumn("X: Query Counts");
            commonGroups.addColumn("X: Query Scores");
            commonGroups.addColumn("X: Queries");
            commonGroups.addColumn("Y: Original Groups Count");
            commonGroups.addColumn("Y: Member Protein Count");
            commonGroups.addColumn("Y: Accessions");
            if (p.displayProtNames) commonGroups.addColumn("Y: Names");
            commonGroups.addColumn("Y: Scores");
            commonGroups.addColumn("Y: Original Member Statuses");
            commonGroups.addColumn("Y: Peptide Counts");
            commonGroups.addColumn("Y: Query Counts");
            commonGroups.addColumn("Y: Query Scores");
            commonGroups.addColumn("Y: Queries");
        }

        int commonCount = 0;
        int uniqueLCount = 0;
        int uniqueRCount = 0;

        HashSet<String> accsWithoutSequence = null;
        if (p.method.equals(ClusteringMethod.Sequence)) {
            accsWithoutSequence = new HashSet<String>(128);
        }

        synchronized(coll) {
            for (ProtGroup pg : coll) {
                if (p.method.equals(ClusteringMethod.Sequence)) {
                    for (ProtGroupMember prot : pg.getMembers()) {
                        if (!prot.hasSequence()) {
                            accsWithoutSequence.add(prot.getAccession());
                        }
                    }
                }
                if (onlyLeft) {
                    ++uniqueLCount;
                    List<Object> row = new ArrayList<Object>(uniqueRowColumns);
                    this.addToGroupRow(row,pg.getSubGroup(statusesUnion, leftDsIds),p.displayProtNames);
                    uniqueLGroups.addRow(row);
                } else { // unique & common
                    boolean hasFromLeft = pg.hasMembersFromAny(p.left.getCollection());
                    boolean hasFromRight = pg.hasMembersFromAny(p.right.getCollection());
                    if (hasFromLeft && hasFromRight) { // common
                        ++commonCount;
                        List<Object> row = new ArrayList<Object>(commonRowColumns);
                        this.addToGroupRow(row,pg.getSubGroup(statusesUnion, leftDsIds),p.displayProtNames);
                        this.addToGroupRow(row,pg.getSubGroup(statusesUnion, rightDsIds),p.displayProtNames);
                        commonGroups.addRow(row);
                    } else if (hasFromLeft) { // unique in left
                        ++uniqueLCount;
                        List<Object> row = new ArrayList<Object>(uniqueRowColumns);
                        this.addToGroupRow(row,pg.getSubGroup(statusesUnion, leftDsIds),p.displayProtNames);
                        uniqueLGroups.addRow(row);
                    } else { // unique in right
                        ++uniqueRCount;
                        List<Object> row = new ArrayList<Object>(uniqueRowColumns);
                        this.addToGroupRow(row,pg.getSubGroup(statusesUnion, rightDsIds),p.displayProtNames);
                        uniqueRGroups.addRow(row);
                    }

                }
            }
        }

        if (!onlyLeft) {
            VennParameters vParam = new VennParameters();
            vParam.setaTotal(uniqueLCount+commonCount);
            vParam.setbTotal(uniqueRCount+commonCount);
            vParam.setAbIntersect(commonCount);
            vParam.setaLabel("X-(X\u2229Y)");
            vParam.setAbLabel("X\u2229Y");
            vParam.setbLabel("Y-(X\u2229Y)");
            vParam.setMinFontSize(12);
            vParam.setMaxFontSize(72);
            vParam.setEmbeddedLabelType(VennParameters.LabelType.PREFIXANDNUMBER);
            vParam.setExternalLabelType(VennParameters.LabelType.NONE);
            vParam.setAntialiase(true);
            vParam.setLineThickness(3);
            VennReportPageModel vPage = new VennReportPageModel("Diagram",dsLegend,vParam);
            vPage.setPageIsFinal();
            report.addPage(vPage);
        }

        List<Object> summRow = null;
        summaryPage.addColumn("Key");
        summaryPage.addColumn("Value");
        summaryPage.addColumn("Description");
        if (onlyLeft) {
            summRow = new ArrayList<Object>(3);
            summRow.add("Protein Groups");
            summRow.add(uniqueLCount);
            summRow.add("Amount of protein groups; clustered with selected method (see parameters)");
            summaryPage.addRow(summRow);
            summRow = new ArrayList<Object>(3);
            summRow.add("Groups before clustering");
            summRow.add(groupsBeforeClustering);
            summRow.add("Amount of 'original' groups before applying the clustering algorithm.");
            summaryPage.addRow(summRow);
            StringBuilder dsParameters = new StringBuilder();
            for (DatasetInfo dsi : p.left.getCollection()) {
                if (dsParameters.length()>0) dsParameters.append(";; ");
                dsParameters.append(dsi.getName()).append(": ").append(dsi.getParameters());
            }
            summRow = new ArrayList<Object>(3);
            summRow.add("Dataset Parameters");
            summRow.add(dsParameters.toString());
            summRow.add("Known parameters used when creating the datasets");
            summaryPage.addRow(summRow);
        } else {
            summRow = new ArrayList<Object>(3);
            summRow.add("X, Y: Common Groups");
            summRow.add(commonCount);
            summRow.add("Amount of common protein groups for given datasets " +
                    "(intersect); clustered with selected method (see parameters)");
            summaryPage.addRow(summRow);
            summRow = new ArrayList<Object>(3);
            summRow.add("X: Unique Groups");
            summRow.add(uniqueLCount);
            summRow.add("Amount of unique protein groups found in X ie. groups " +
                    "not found in Y");
            summaryPage.addRow(summRow);
            summRow = new ArrayList<Object>(3);
            summRow.add("Y: Unique Groups");
            summRow.add(uniqueRCount);
            summRow.add("Amount of unique protein groups found in Y ie. groups " +
                    "not found in X");
            summaryPage.addRow(summRow);
            summRow = new ArrayList<Object>(3);
            summRow.add("X, Y: All Groups");
            summRow.add(commonCount+uniqueLCount+uniqueRCount);
            summRow.add("Amount of groups found in X and Y summed together " +
                    "(common + unique in X + unique in Y)");
            summaryPage.addRow(summRow);
            summRow = new ArrayList<Object>(3);
            summRow.add("X, Y: All Groups before clustering");
            summRow.add(groupsBeforeClustering);
            summRow.add("Amount of 'original' groups in X and Y before applying the clustering algorithm.");
            summaryPage.addRow(summRow);

            StringBuilder dsParameters = new StringBuilder();
            for (DatasetInfo dsi : p.left.getCollection()) {
                if (dsParameters.length()>0) dsParameters.append(";; ");
                dsParameters.append(dsi.getName()+": "+dsi.getParameters());
            }
            summRow = new ArrayList<Object>(3);
            summRow.add("X: Parameters");
            summRow.add(dsParameters.toString());
            summRow.add("Known parameters used when creating the X datasets");
            summaryPage.addRow(summRow);
            dsParameters = new StringBuilder();
            for (DatasetInfo dsi : p.right.getCollection()) {
                if (dsParameters.length()>0) dsParameters.append(";; ");
                dsParameters.append(dsi.getName()+": "+dsi.getParameters());
            }
            summRow = new ArrayList<Object>(3);
            summRow.add("Y: Parameters");
            summRow.add(dsParameters.toString());
            summRow.add("Known parameters used when creating the Y datasets");
            summaryPage.addRow(summRow);
        }

        if (p.method.equals(ClusteringMethod.Sequence) && accsWithoutSequence.size()>0) {
            summRow = new ArrayList<Object>(3);
            summRow.add("X, Y: Sequenceless proteins");
            summRow.add(accsWithoutSequence.size());
            summRow.add("Proteins without sequence. These proteins were not " +
                    "taken into account when grouping proteins. Import sequences " +
                    "from a correct FASTA file before creating Sequence-based protein " +
                    "group reports.");
            summaryPage.addRow(summRow);
        }



        summaryPage.setPageIsFinal();
        report.addPage(summaryPage);

        int uniqueSortColIdx = 3;
        int commonSortColIdx = 3;
        if (p.displayProtNames) {
            ++uniqueSortColIdx;
            ++commonSortColIdx;
        }

        uniqueLGroups.setPageIsFinal();
        uniqueLGroups.sort(uniqueSortColIdx, false);

        if (onlyLeft) {
            report.addPage(uniqueLGroups);
        } else {
            commonGroups.setPageIsFinal();
            commonGroups.sort(uniqueSortColIdx, false);
            report.addPage(commonGroups);
            report.addPage(uniqueLGroups);
            uniqueRGroups.setPageIsFinal();
            uniqueRGroups.sort(uniqueSortColIdx, false);
            report.addPage(uniqueRGroups);
        }

        return report;
    }

    private void addToGroupRow(List<Object> row, ProtGroup pg, boolean includeName) {
        row.add(pg.getOriginalGroupCount());
        row.add(pg.getMembers().size());
        int memberCount = pg.getMembers().size();
        String[] accs = new String[memberCount];
        String[] names = new String[memberCount];
        String[] scores = new String[memberCount];
        String[] origStatuses = new String[memberCount];
        String[] pepCounts = new String[memberCount];
        String[] queryCounts = new String[memberCount];
        String[] queryScores = new String[memberCount];
        String[] queries = new String[memberCount];
        int i = 0;
        for (ProtGroupMember prot : (new TreeSet<ProtGroupMember>(pg.getMembers())).descendingSet()) {
            accs[i] = prot.getAccession();
            if (includeName) names[i] = prot.getName();
            scores[i] = String.valueOf(twoDigitFormat.format(prot.getScore()));
            origStatuses[i] = prot.getType().toString();
            pepCounts[i] = String.valueOf(prot.getDistinctPeptideCount());
            queryCounts[i] = String.valueOf(prot.getQueries().size());
            StringBuilder queryScoresB = new StringBuilder();
            StringBuilder queriesB = new StringBuilder();
            int pepi = 0;
            for (ProtGroupQuery q : prot) {
                if (pepi==0) {
                    scores[i]+=" "+q.getAlgorithm().toShortString();
                } else {
                    queryScoresB.append("; "); //NOI18N
                    queriesB.append("; "); //NOI18N
                }
                queryScoresB.append(twoDigitFormat.format(q.getScore()));
                queryScoresB.append(" "+q.getAlgorithm().toShortString());
                queriesB.append(q.getScanTitle());
                ++pepi;
            }
            queryScores[i] = queryScoresB.toString();
            queries[i] = queriesB.toString();
            ++i;
        }
        row.add(StrUtils.catStrings(accs,"; ")); //NOI18N
        if (includeName) row.add(StrUtils.catStrings(names,"; ")); //NOI18N
        row.add(StrUtils.catStrings(scores,"; ")); //NOI18N
        row.add(StrUtils.catStrings(origStatuses,"; ")); //NOI18N
        row.add(StrUtils.catStrings(pepCounts, "; ")); // NOI18N
        row.add(StrUtils.catStrings(queryCounts,"; ")); //NOI18N
        row.add(StrUtils.catStrings(queryScores,";; ")); //NOI18N
        row.add(StrUtils.catStrings(queries,";; ")); //NOI18N
    }

}
