/*
 * 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.tuples.DatasetInfo;
import compid.data.tuples.ProteinHit.GroupMemberStatus;
import compid.sequences.AminoSequence;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

/**
 * Protein Group Member, represents a protein. This class is immutable and,
 * as such, thread-safe. Holds the information of the originating data set,
 * but not of the group: this must be explicitely added to a ProtGroup.
 *
 * @author Lari Natri
 */
public final class ProtGroupMember implements Iterable<ProtGroupQuery>,
                                   Comparable<ProtGroupMember> {

    // NOTE: keep hashCode() and equals() derived from Object!

    private final String acc;
    private final double score;
    private final GroupMemberStatus type;
    private final int datasetId;
    private final AminoSequence seq;
    private final String name;
    private final SortedSet<ProtGroupQuery> queries;

    /**
     * Constructor.
     * @param accession Accession, must be non-null non-empty string
     * @param score Score given by the source software (Mascot/Paragon)
     * @param type Type of this protein group member in relation to it's group
     * @param dataset Dataset this protein originates in
     * @param protSeq Protein sequence, can be null
     * @param protName Name of the protein. If null, will be converted to an
     *                 empty string.
     * @param queries Set of matching queries for this dataset, can be null
     * @throws IllegalArgumentException Thrown, if accession.isEmpty()
     *         || dataset.getPrimaryKey() < 1
     * @throws NullPointerException Thrown, if accession==null || type==null
     *         || dataset==null
     */
    public ProtGroupMember(final String accession, final double score,
                           final GroupMemberStatus type, DatasetInfo dataset,
                           AminoSequence protSeq, String protName,
                           Set<ProtGroupQuery> queries)
                           throws IllegalArgumentException, NullPointerException
    {
        if (accession==null || type==null || dataset==null) {
            throw new NullPointerException();
        }
        if (accession.isEmpty() || dataset.getPrimaryKey()<1) {
            throw new IllegalArgumentException();
        }
        this.acc = accession;
        this.score = score;
        this.type = type;
        this.datasetId = dataset.getPrimaryKey();
        if (protName==null) {
            this.name = ""; //NOI18N
        } else {
            this.name = protName;
        }
        if (protSeq==null || protSeq.getLength()==0) {
            this.seq = null;
        } else {
            this.seq = protSeq;
        }
        if (queries==null || queries.isEmpty()) {
            this.queries = null;
        } else {
            // in descending natural order (by score)
            this.queries = Collections.unmodifiableSortedSet(new TreeSet<ProtGroupQuery>(queries).descendingSet());
        }
    }

    /**
     * Constructor. No queries.
     * @param accession Accession, must be non-null non-empty string
     * @param score Score given by the source software (Mascot/Paragon)
     * @param type Type of this protein group member in relation to it's group
     * @param dataset Dataset this protein originates in
     * @param protSeq Protein sequence, can be null
     * @param protName Name of the protein. If null, will be converted to an
     *                 empty string.
     * @throws IllegalArgumentException Thrown, if accession.isEmpty()
     *         || dataset.getPrimaryKey() < 1
     * @throws NullPointerException Thrown, if accession==null || type==null
     *         || dataset==null
     */
    public ProtGroupMember(final String accession, final double score,
                           final GroupMemberStatus type, DatasetInfo dataset,
                           AminoSequence protSeq, String protName)
                           throws IllegalArgumentException, NullPointerException
    {
        this(accession,score,type,dataset,protSeq,protName,null);
    }

    /**
     * Constructor. No protein sequence.
     * @param accession Accession, must be non-null non-empty string
     * @param score Score given by the source software (Mascot/Paragon)
     * @param type Type of this protein group member in relation to it's group
     * @param dataset Dataset this protein originates in
     * @param protName Name of the protein. If null, will be converted to an
     *                 empty string.
     * @param queries Set of matching queries for this dataset, can be null
     * @throws IllegalArgumentException Thrown, if accession.isEmpty()
     *         || dataset.getPrimaryKey() < 1
     * @throws NullPointerException Thrown, if accession==null || type==null
     *         || dataset==null
     */
    public ProtGroupMember(final String accession, final double score,
                           final GroupMemberStatus type, DatasetInfo dataset,
                           String protName, Set<ProtGroupQuery> queries)
                           throws IllegalArgumentException, NullPointerException
    {
        this(accession,score,type,dataset,null,protName,queries);
    }

    /**
     * Constructor. No protein sequence nor queries.
     * @param accession Accession, must be non-null non-empty string
     * @param score Score given by the source software (Mascot/Paragon)
     * @param type Type of this protein group member in relation to it's group
     * @param dataset Dataset this protein originates in
     * @param protName Name of the protein. If null, will be converted to an
     *                 empty string.
     * @throws IllegalArgumentException Thrown, if accession.isEmpty()
     *         || dataset.getPrimaryKey() < 1
     * @throws NullPointerException Thrown, if accession==null || type==null
     *         || dataset==null
     */
    public ProtGroupMember(final String accession, final double score,
                           final GroupMemberStatus type, DatasetInfo dataset,
                           String protName)
                           throws IllegalArgumentException, NullPointerException
    {
        this(accession,score,type,dataset,null,protName,null);
    }

    /**
     * Returns true if, and only if, peptide set is available for this protein
     * ie. getQueries()!=null.
     * @return true if sequence is available, false otherwise
     */
    public final boolean hasQueries() {
        return (this.queries!=null);
    }

    /**
     * Returns true if, and only if, protein sequence is available ie.
     * getSequence()!=null.
     * @return true if sequence is available, false otherwise
     */
    public final boolean hasSequence() {
        return (this.seq!=null);
    }

    /**
     * Returns the accession string for this protein.
     * @return Accession, guaranteed to be non-null
     */
    public final String getAccession() {
        return this.acc;
    }

    /**
     * Returns the name of the protein
     * @return protein name
     */
    public final String getName() {
        return this.name;
    }

    /**
     * Returns the score given to this protein hit by the source software.
     * Note that scores originating in different software are not comparable.
     * @return Score for this protein hit
     */
    public final double getScore() {
        return this.score;
    }

    /**
     * Returns the type of this protein group member in relation to it's group.
     * @return group member type (status)
     */
    public final GroupMemberStatus getType() {
        return this.type;
    }

    /**
     * Returns the id (primary key) for the dataset this protein originated.
     * @return dataset id, guaranteed to be > 0
     */
    public final int getDatasetId() {
        return this.datasetId;
    }

    /**
     * Returns the peptide set mathing this protein. The set is unmodifiable.
     * Set is sorted in descending order by score.
     * @return Set of queries or null <==> hasQueries()==false
     */
    public final SortedSet<ProtGroupQuery> getQueries() {
        return this.queries;
    }

    /**
     * Returns distinct peptides in this protein. Distinctness is determined
     * by comparing peptide sequences strictly == must have an exact match.
     * @return distinct peptides
     */
    public final Set<AminoSequence> getDistinctPeptideSequences() {
        LinkedHashSet<AminoSequence> peps = new LinkedHashSet<AminoSequence>();
        for (ProtGroupQuery q : this.getQueries()) {
            peps.add(q.getSequence());
        }
        return peps;
    }

    /**
     * Returns the amount of distinct peptides (by strict comparison of
     * peptide sequences) in this member protein.
     * @return amount of distinct peptides
     */
    public final int getDistinctPeptideCount() {
        return this.getDistinctPeptideSequences().size();
    }

    /**
     * Returns the amino acid sequence for this protein.
     * @return Sequence or null <==> hasSequence()==false
     */
    public final AminoSequence getSequence() {
        return this.seq;
    }

    /**
     * Returns an iterator for the peptide matches in this protein. If this
     * protein doesn't contain information about the queries, a null value
     * is returned. The iterator doesn't support remove operation. Peptides
     * will be iterated in descending order by score.
     * @return iterator for the queries or null <==> hasQueries()==false
     */
    @Override
    public Iterator<ProtGroupQuery> iterator() {
        if (!this.hasQueries()) {
            return null;
        }
        return this.queries.iterator();
    }

    @Override
    public int compareTo(ProtGroupMember o) {
        if (this==o) {
            return 0;
        }
        if (this.type==GroupMemberStatus.WINNER) {
            if (o.type.equals(GroupMemberStatus.WINNER)) {
                if (this.score<o.score) {
                    return -1;
                } else if (this.score>o.score) {
                    return 1;
                } else {
                    int hashDifference = this.hashCode() - o.hashCode();
                    if (hashDifference==0) { // last resort
                        System.out.println("DEBUG: ProtGroupMember comparator " +
                                "returned an inconsistent result");
                        return 1;
                    }
                    return hashDifference;
                }
            }
            return 2;
        } else {
            if (o.type.equals(GroupMemberStatus.WINNER)) {
                return -2;
            } else {
                if (this.score<o.score) {
                    return -1;
                } else if (this.score>o.score) {
                    return 1;
                } else { // order must be established even if scores are the same!
                    int hashDifference = this.hashCode() - o.hashCode();
                    if (hashDifference==0) { // last resort
                        System.out.println("DEBUG: ProtGroupMember comparator " +
                                "returned an inconsistent result");
                        return 1;
                    }
                    return hashDifference;
                }
            }
        }
    }




}
