/* GFFStreamFeature.java
 *
 * created: Tue Sep 14 1999
 *
 * This file is part of Artemis
 *
 * Copyright (C) 1999  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/uk/ac/sanger/pathogens/embl/GFFStreamFeature.java,v 1.4 2000/08/02 12:04:07 kmr Exp $
 */

package uk.ac.sanger.pathogens.embl;

import uk.ac.sanger.pathogens.*;

import java.io.*;

/**
 *  A StreamFeature that thinks it is a GFF feature.
 *
 *  @author Kim Rutherford
 *  @version $Id: GFFStreamFeature.java,v 1.4 2000/08/02 12:04:07 kmr Exp $
 **/

public class GFFStreamFeature
    extends SimpleDocumentFeature
    implements DocumentFeature, StreamFeature, ComparableFeature {

  /**
   *  Create a new GFFStreamFeature object.  The feature should be added
   *  to an Entry (with Entry.add ()).
   *  @param key The new feature key
   *  @param location The Location object for the new feature
   *  @param qualifiers The qualifiers for the new feature
   **/
  public GFFStreamFeature (final Key key,
                           final Location location,
                           final QualifierVector qualifiers) {
    super (null);
    try {
      setKey (key);
      setLocation (location);
      setQualifiers (qualifiers);
      if (getQualifierByName ("gff_group") == null) {
        // make up a group name
        setQualifier (new Qualifier ("gff_group",
                                     "feature_" + getNumericID ()));
      }
      if (getQualifierByName ("score") == null) {
        setQualifier (new Qualifier ("score", "."));
      }
      if (getQualifierByName ("gff_source") == null) {
        setQualifier (new Qualifier ("gff_source", "artemis"));
      }
      if (getQualifierByName ("gff_seqname") == null) {
        setQualifier (new Qualifier ("gff_seqname", "."));
      }
    } catch (EntryInformationException e) {
      // this should never happen because the feature will not be in an Entry
      throw new Error ("internal error - unexpected exception: " + e);
    } catch (ReadOnlyException e) {
      // this should never happen because the feature will not be in an Entry
      throw new Error ("internal error - unexpected exception: " + e);
    } catch (OutOfRangeException e) {
      // this should never happen because the feature will not be in an Entry
      throw new Error ("internal error - unexpected exception: " + e);
    }
  }

  /**
   *  Create a new GFFStreamFeature with the same key, location and
   *  qualifiers as the given feature.  The feature should be added to an
   *  Entry (with Entry.add ()).
   *  @param feature The feature to copy.
   **/
  public GFFStreamFeature (final Feature feature) {
    this (feature.getKey (), feature.getLocation (), feature.getQualifiers ());
  }

  /**
   *  Return the reference of a new copy of this Feature.
   **/
  public Feature copy () {
    final Feature return_value = new GFFStreamFeature (this);

//    System.out.println (return_value.getEntry ());

    return return_value;
  }

  /**
   *  Create a new GFFStreamFeature from the given line.  The String should be
   *  in gene finder format.
   **/
  private GFFStreamFeature (final String line)
      throws ReadFormatException {
    super (null);

    final StringVector line_bits = StringVector.getStrings (line, "\t", true);

    if (line_bits.size () < 8) {
      for ( int i = 0 ; i < line_bits.size () ; ++i) {
        System.err.println ("bit " + i + ": " + line_bits.elementAt (i));
      }

      //      System.err.println (line_bits.size ());
      throw new ReadFormatException ("invalid GFF line (not enough " +
                                     "fields): " +
                                     joinStringVector (line_bits));
    }

    final String start_base = line_bits.elementAt (3);
    final String end_base = line_bits.elementAt (4);

    final boolean complement_flag;

    if (line_bits.elementAt (6).equals ("+")) {
      complement_flag = false;
    } else {
      if (line_bits.elementAt (6).equals ("-")) {
        complement_flag = true;
      } else {
        throw new ReadFormatException ("invalid GFF line (7th field should " +
                                       "+ or -): " +
                                       joinStringVector (line_bits));
      }
    }

    try {
      final Qualifier gff_seqname =
        new Qualifier ("gff_seqname", line_bits.elementAt (0));

      setQualifier (gff_seqname);

      final Key key = new Key (line_bits.elementAt (2));

      setKey (key);

      final Qualifier note_qualifier;

      if (line_bits.size () >= 10) {
        note_qualifier = new Qualifier ("note", line_bits.elementAt (9));
      } else {
        note_qualifier = new Qualifier ("note", "");
      }

      setQualifier (note_qualifier);

      final Qualifier source_qualifier =
        new Qualifier ("gff_source", line_bits.elementAt (1));

      setQualifier (source_qualifier);

      final Qualifier score_qualifier =
        new Qualifier ("score", line_bits.elementAt (5));

      setQualifier (score_qualifier);

      final Qualifier codon_start_qualifier =
        new Qualifier ("codon_start", line_bits.elementAt (7));

      setQualifier (codon_start_qualifier);
      
      if (line_bits.size () >= 9) {
        final Qualifier gff_group =
          new Qualifier ("gff_group", line_bits.elementAt (8));

        setQualifier (gff_group);
      } else {
        // make up a number

        final Qualifier gff_group =
          new Qualifier ("gff_group", "feature_" + getNumericID ());

        setQualifier (gff_group);
      }

      if (complement_flag) {
        // XXX
        setLocation (new Location ("complement(" + start_base + ".." +
                                   end_base + ")"));
      } else {
        // XXX
        setLocation (new Location (start_base + ".." + end_base));
      }
    } catch (ReadOnlyException e) {
      throw new Error ("internal error - unexpected exception: " + e);
    } catch (EntryInformationException e) {
      throw new Error ("internal error - unexpected exception: " + e);
    } catch (OutOfRangeException e) {
      throw new Error ("internal error - unexpected exception: " + e);
    } catch (LocationParseException e) {
      throw new Error ("internal error - unexpected exception: " + e);
    }

    this.gff_line = line;
  }

  /**
   *  Helper method for the constructor - returns a String that is the
   *  concatenation of the Strings in the given StringVector.  The strings
   *  will be separated by four spaces
   **/
  private String joinStringVector (final StringVector string_vector) {
    final StringBuffer return_buffer = new StringBuffer ();

    for (int i = 0 ; i < string_vector.size () ; ++i) {
      if (i != 0) {
        return_buffer.append ("    ");
      }
      return_buffer.append (string_vector.elementAt (i));
    }

    return return_buffer.toString ();
  }

  /**
   *  Read and return a GFFStreamFeature from a stream.  A feature must be the
   *  next thing in the stream.
   *  @param stream the Feature is read from this stream
   *  @exception IOException thrown if there is a problem reading the Feature -
   *    most likely ReadFormatException.
   *  @exception InvalidRelationException Thrown if this Feature cannot contain
   *    the given Qualifier.
   *  @return null if in_stream is at the end of file when the method is
   *    called
   */
  protected static GFFStreamFeature readFromStream (LinePushBackReader stream)
      throws IOException, InvalidRelationException {

    String line = stream.readLine ();

    if (line == null) {
      return null;
    }

    try {
      final GFFStreamFeature new_feature = new GFFStreamFeature (line);

      return new_feature;
    } catch (ReadFormatException exception) {
      // re-throw the exception with the line number added

      final String new_error_string = exception.getMessage ();

      System.err.println (new_error_string);

      throw new ReadFormatException (new_error_string,
                                     stream.getLineNumber ());
    }
  }

  /**
   *  Read the details of a feature from an EMBL stream into the current
   *  object.
   *  @param entry_information The EntryInformation object of the Entry that
   *    will contain the Feature.
   *  @param in_stream the Feature is read from this stream
   *  @exception IOException thrown if there is a problem reading the Feature -
   *    most likely ReadFormatException if the stream does not contain GFF
   *    feature.
   **/
  public void setFromStream (final EntryInformation entry_information,
                             final LinePushBackReader in_stream)
      throws IOException, InvalidRelationException, ReadOnlyException {
    throw new ReadOnlyException ();
  }

  /**
   *  Write this Feature to the given stream.
   *  @param writer The stream to write to.
   *  @exception IOException thrown if there is an io problem while writing
   *    the Feature.
   **/
  public void writeToStream (final Writer writer)
      throws IOException {
    final RangeVector ranges = getLocation ().getRanges ();

    for (int i = 0 ; i < ranges.size () ; ++i) {
      final Range this_range = ranges.elementAt (i);
      Qualifier   seqname    = getQualifierByName ("gff_seqname");
      Qualifier   source     = getQualifierByName ("gff_source");
      Qualifier   score      = getQualifierByName ("score");
      Qualifier   group      = getQualifierByName ("gff_group");
      Qualifier   note       = getQualifierByName ("note");

      if (seqname == null) {
        seqname = new Qualifier ("gff_seqname", "");
      }

      if (source == null) {
        source = new Qualifier ("source", "");
      }

      if (group == null) {
        group = new Qualifier ("gff_group", "");
      }

      if (score == null) {
        score = new Qualifier ("score", "");
      }

      if (group == null || group.getValues () == null ||
          group.getValues ().elementAt (0).equals ("")) {
        final Qualifier gene = getQualifierByName ("gene");

        if (gene == null) {
          group = new Qualifier ("gff_group", "");
        } else {
          group = gene;
        }
      }

      if (note == null) {
        note = new Qualifier ("note", "");
      }

      String frame = ".";

      final Qualifier codon_start = getQualifierByName ("codon_start");

      if (codon_start != null && i == 0) {
        frame = codon_start.getValues ().elementAt (0);
      }

      writer.write (seqname.getValues ().elementAt (0) + "\t" +
                    source.getValues ().elementAt (0) + "\t" +
                    getKey () + "\t" +
                    this_range.getStart () + "\t" +
                    this_range.getEnd () + "\t" +
                    score.getValues () .elementAt (0)+ "\t" +
                    (getLocation ().isComplement () ? "-\t" : "+\t") +
                    frame + "\t" +
                    group.getValues ().elementAt (0) + "\t" +
                    note.getValues ().elementAt (0) + "\n");
    }
  }

  /**
   *  The DocumentEntry object that contains this Feature as passed to the
   *  constructor.
   **/
  private DocumentEntry entry;

  /**
   *  This is the line of GFF input that was read to get this GFFStreamFeature.
   **/
  private String gff_line = null;
}
