/* EntryGroupInfoDisplay.java
 *
 * created: Fri Mar 12 1999
 *
 * This file is part of Artemis
 *
 * Copyright (C) 1998,1999,2000  Genome Research Limited
 *
 * 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 2
 * 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * $Header: /nfs/disk222/yeastpub/Repository/powmap/diana/components/EntryGroupInfoDisplay.java,v 1.22 2000/09/18 09:54:07 kmr Exp $
 **/

package diana.components;

import uk.ac.sanger.pathogens.embl.Key;
import uk.ac.sanger.pathogens.embl.Qualifier;
import uk.ac.sanger.pathogens.embl.InvalidRelationException;
import diana.*;
import diana.sequence.*;

import java.util.Hashtable;
import java.util.Enumeration;
import java.awt.event.*;
import java.awt.Frame;

/**
 *  This component will show general information about an EntryGroup.  It will
 *  show the sequence length, GC content and a summary of the active entries.
 *
 *  @author Kim Rutherford
 *  @version $Id: EntryGroupInfoDisplay.java,v 1.22 2000/09/18 09:54:07 kmr Exp $
 **/

public class EntryGroupInfoDisplay
    implements FeatureChangeListener, EntryChangeListener,
               EntryGroupChangeListener, SequenceChangeListener {
  /**
   *  Create a new EntryGroupInfoDisplay object to summarize all features on
   *  both strands of the given EntryGroup.
   *  @param parent_frame The reference of the parent Frame.
   *  @param entry_group The EntryGroup to view.
   **/
  public EntryGroupInfoDisplay (final Frame parent_frame,
                                final EntryGroup entry_group) {
    this (parent_frame, entry_group, BOTH);
  }

  /**
   *  Create a new EntryGroupInfoDisplay object to summarize the given
   *  EntryGroup.
   *  @param parent_frame The reference of the parent Frame.
   *  @param entry_group The EntryGroup to view.
   *  @param strand_flag Indicates which strand to summarize.  BOTH means
   *    summarize all features on both strands.  FORWARD means summarize
   *    forward strand only.  REVERSE means summarize reverse strand only.
   **/
  public EntryGroupInfoDisplay (final Frame parent_frame,
                                EntryGroup entry_group,
                                final int strand_flag) {
    this.strand_flag = strand_flag;

    if (strand_flag == FORWARD) {
      final FeaturePredicate forward_feature_predicate =
        new FeaturePredicate () {
          public boolean testPredicate (final Feature test_feature) {
            return test_feature.isForwardFeature ();
          }
        };

      final FilteredEntryGroup filtered_entry_group =
        new FilteredEntryGroup (entry_group, forward_feature_predicate,
                                "Forward Strand");

      entry_group = filtered_entry_group;
    } else {
      if (strand_flag == REVERSE) {
        final FeaturePredicate reverse_feature_predicate =
          new FeaturePredicate () {
            public boolean testPredicate (final Feature test_feature) {
              return !test_feature.isForwardFeature ();
            }
          };

        final FilteredEntryGroup filtered_entry_group =
          new FilteredEntryGroup (entry_group, reverse_feature_predicate,
                                  "Reverse Strand");

        entry_group = filtered_entry_group;
      } else {
        if (strand_flag != BOTH) {
          throw new Error ("internal error - illegal argument");
        }
      }
    }

    if (entry_group instanceof FilteredEntryGroup) {
      final FilteredEntryGroup filtered_entry_group =
        (FilteredEntryGroup) entry_group;

      final String filter_name = filtered_entry_group.getFilterName ();

      if (filter_name == null) {
        file_viewer_name =
          "Artemis Overview Of: " + parent_frame.getTitle ();
      } else {
        file_viewer_name =
          "Artemis Overview Of: " + parent_frame.getTitle () +
          " (" + filter_name + ")";
      }
    } else {
      file_viewer_name = "Artemis Overview";
    }

    file_viewer = new FileViewer (file_viewer_name);

    this.entry_group = entry_group;

    updateView ();

    entry_group.addEntryChangeListener (this);
    entry_group.addFeatureChangeListener (this);
    entry_group.addEntryGroupChangeListener (this);
    entry_group.getBases ().addSequenceChangeListener (this,
                                                       Bases.MAX_PRIORITY);

    file_viewer.addWindowListener (new WindowAdapter () {
      public void windowClosed (WindowEvent event) {
        stopListening ();
      }
    });
  }

  /**
   *  Use the forward strand when summarizing.
   **/
  final static public int FORWARD = Bases.FORWARD;

  /**
   *  Use the reverse strand when summarizing.
   **/
  final static public int REVERSE = Bases.REVERSE;

  /**
   *  Summarize both strands.
   **/
  final static public int BOTH = 3;

  /**
   *  Remove this object as a selection change listener.
   **/
  private void stopListening () {
    entry_group.removeEntryChangeListener (this);
    entry_group.removeEntryGroupChangeListener (this);
    entry_group.removeFeatureChangeListener (this);
    entry_group.getBases ().removeSequenceChangeListener (this);
  }

  /**
   *  Implementation of the FeatureChangeListener interface.   We listen to
   *  FeatureChange events so that we can update the display if qualifiers
   *  change.
   **/
  public void featureChanged (final FeatureChangeEvent event) {
    updateView ();
  }

  /**
   *  Implementation of the EntryGroupChangeListener interface.  We listen to
   *  EntryGroupChange events so that we can update the display if entries
   *  are added, deleted, activated or deactivated.
   **/
  public void entryGroupChanged (final EntryGroupChangeEvent event) {
    updateView ();
  }

  /**
   *  Implementation of the EntryChangeListener interface.  We listen to
   *  EntryChange events so that we can update the display if features are
   *  added or deleted.
   **/
  public void entryChanged (final EntryChangeEvent event) {
    updateView ();
  }

  /**
   *  Implementation of the SequenceChangeListener interface.  We listen to
   *  SequenceChange events so that we can update the display if the sequence
   *  changes.
   **/
  public void sequenceChanged (final SequenceChangeEvent event) {
    updateView ();
  }

  /**
   *  Update the FileViewer component to reflect the current state of the
   *  EntryGroup.
   **/
  private void updateView () {
    final StringBuffer buffer = new StringBuffer ();

    buffer.append (file_viewer_name + "\n\n");

    buffer.append ("Number of bases: " +
                   entry_group.getSequenceLength () + "\n");
    buffer.append ("Number of features in the active entries: " +
                   entry_group.getAllFeaturesCount () + "\n");

    final Bases entry_group_bases = entry_group.getBases ();

    int pseudo_gene_count = 0;
    int gene_count = 0;
    int coding_bases_count = 0;
    int transcript_bases_count = 0;
    int coding_feature_count = 0;
    int spliced_gene_count = 0;
    int exon_count = 0;

    final Hashtable table = new Hashtable ();

    final FeatureEnumeration feature_enumerator = entry_group.features ();

    while (feature_enumerator.hasMoreFeatures ()) {
      final Feature this_feature = feature_enumerator.nextFeature ();

      final Key key = this_feature.getKey ();
      final String key_string = key.toString ();

      try {
        String colour = this_feature.getValueOfQualifier ("colour");

        if (colour == null || colour.length () == 0) {
          colour = "no colour";
        }

        if (table.containsKey (key_string)) {
          final Hashtable colour_table = (Hashtable) table.get (key_string);

          final Integer colour_value = (Integer) colour_table.get (colour);

          if (colour_value == null) {
              colour_table.put (colour, new Integer (1));
          } else {
            final int old_value = ((Integer) colour_value).intValue ();

            colour_table.put (colour, new Integer (old_value + 1));
          }
        } else {
          final Hashtable colour_table = new Hashtable ();
          colour_table.put (colour, new Integer (1));
          table.put (key_string, colour_table);
        }
      } catch (InvalidRelationException e) {
        throw new Error ("internal error - unexpected exception: " + e);
      }

      if (this_feature.getKey ().equals (Key.CDS)) {
        final Qualifier pseudo_qualifier;

        try {
          pseudo_qualifier = this_feature.getQualifierByName ("pseudo");
        } catch (InvalidRelationException e) {
          throw new Error ("internal error - unexpected exception: " + e);
        }

        if (pseudo_qualifier == null) {
          ++gene_count;

          final FeatureSegmentVector segments = this_feature.getSegments ();

          if (segments.size () > 1) {
            ++spliced_gene_count;
          }

          exon_count += segments.size ();
        } else {
          ++pseudo_gene_count;
        }
      }

      if (this_feature.isCDS ()) {
        ++coding_feature_count;
        coding_bases_count += this_feature.getBaseCount ();
        transcript_bases_count += this_feature.getTranscriptBasesCount ();
      }
    }

    if (gene_count > 0) {
      buffer.append ("Gene density: " +
                     entry_group.getSequenceLength () / gene_count +
                     " bases per gene\n");
      buffer.append ("Average gene length: " +
                     coding_bases_count / gene_count +
                     " bases per gene\n");

      buffer.append ("Number of non-spliced genes: " +
                     (gene_count - spliced_gene_count) + "\n");
      buffer.append ("Number of spliced genes: " + spliced_gene_count + "\n");
      buffer.append ("Number of pseudo genes: " + pseudo_gene_count + "\n");
    }

    buffer.append ("Protein coding (CDS) features: " + coding_feature_count + "\n");
    buffer.append ("Protein coding (CDS) bases: " + coding_bases_count + "\n");

    buffer.append ("Protein coding percentage: " +
                   1000L * coding_bases_count /
                   entry_group.getSequenceLength () / 10.0 + "\n");
    buffer.append ("Coding percentage (including introns): " +
                   1000L * transcript_bases_count /
                   entry_group.getSequenceLength () / 10.0 + "\n");

    final Strand strand;

    if (strand_flag == FORWARD || strand_flag == BOTH) {
      strand = entry_group.getBases ().getForwardStrand ();
    } else {
      strand = entry_group.getBases ().getReverseStrand ();
    }

    final String base_summary =
      SelectionViewer.getBaseSummary (strand.getStrandBases ());

    buffer.append ("\n").append (base_summary);

    buffer.append ("\nSummary of the active entries:\n");

    final Enumeration e = table.keys ();

    while (e.hasMoreElements()) {
      final String this_key = (String) e.nextElement();
      final Hashtable colour_table = (Hashtable) table.get (this_key);

      buffer.append (this_key + ": ");

      final StringBuffer colour_string = new StringBuffer ();

      final Enumeration colour_enum = colour_table.keys ();

      int total = 0;

      while (colour_enum.hasMoreElements()) {
        final String this_colour = (String) colour_enum.nextElement();

        final int colour_count =
          ((Integer) colour_table.get (this_colour)).intValue ();

        total += colour_count;

        final String end_string;

        if (this_colour.equals ("no colour")) {
          end_string = "no colour";
        } else {
          end_string = "colour: " + this_colour;
        }

        if (colour_count == 1) {
          colour_string.append ("  one has " + end_string + "\n");
        } else {
          colour_string.append ("  " + colour_count + " have " +
                                end_string + "\n");
        }
      }

      buffer.append (total + "\n");

      buffer.append (colour_string);
    }

    file_viewer.setText (buffer.toString ());
  }

  /**
   *  This is the EntryGroup object that we are viewing.
   **/
  private EntryGroup entry_group;

  /**
   *  The strand indicator that was passed to the constructor.
   **/
  private int strand_flag;

  /**
   *  The name used for the FileViewer.
   **/
  private String file_viewer_name;

  /**
   *  The FileViewer object that is displaying the EntryGroup.
   **/
  private FileViewer file_viewer;
}
