/* EmblStreamSequence.java
 *
 * created: Mon Jun 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/EmblStreamSequence.java,v 1.9 2000/08/21 10:48:42 kmr Exp $
 */

package uk.ac.sanger.pathogens.embl;

import java.io.IOException;
import java.io.Writer;

/**
 *  This is a subclass of StreamSequence containing EMBL sequence.
 *
 *  @author Kim Rutherford
 *  @version $Id: EmblStreamSequence.java,v 1.9 2000/08/21 10:48:42 kmr Exp $
 **/

public class EmblStreamSequence extends StreamSequence {
  /**
   *  Create a new EmblStreamSequence object from a stream.  The next line to
   *  read from the stream should be the header line of the sequence.
   *  @param in_stream The stream to read from.  When the constructor returns
   *    the stream will at the next line after the sequence.
   **/
  public EmblStreamSequence (final LinePushBackReader in_stream)
      throws IOException {
    super (in_stream);
  }

  /**
   *  Make a new EmblStreamSequence containing the same sequence as the given
   *  Sequence.
   **/
  public EmblStreamSequence (final Sequence sequence) {
    setFromString (sequence.toString ());
  }

  /**
   *  Make a new EmblStreamSequence containing the same sequence as the given
   *  String. 
   *  @param sequence_string The String containing the sequence for the new
   *    EmblStreamSequence.
   **/
  public EmblStreamSequence (final String sequence_string){
    setFromString (sequence_string);
  }

  /**
   *  Return an identifier for this sequence.  This will most likely be a
   *  discription (eg. from a FASTA header).  Returns null if there is no
   *  identifier for the sequence.
   **/
  public String getIdentifier () {
    return null;
  }

  /**
   *  Return a new StreamSequence object that is a copy of this one.
   **/
  public StreamSequence copy () {
    return new EmblStreamSequence (this);
  }

  /**
   *  Return the sequence type (EMBL_FORMAT for this class).
   **/
  public int getFormatType () {
    return StreamSequenceFactory.EMBL_FORMAT;
  }

  /**
   *  Read the header for this sequence (if any).
   **/
  protected void readHeader (final LinePushBackReader in_stream)
      throws IOException {
    final String seq_header_line = in_stream.readLine ();

    setHeader (seq_header_line);
  }

  /**
   *  This method will read EMBL sequence from the stream in this object
   *  removing whitespace as it goes.
   **/
  protected void readSequence (final LinePushBackReader in_stream)
      throws IOException {

    final StringBuffer this_line_sequence_buffer = new StringBuffer (81);

    final String seq_header_line = getHeader ();

    final int header_base_count = getHeaderBaseCount (seq_header_line);

    final int buffer_capacity;

    if (header_base_count > 50000) {
      // add 100 for good luck
      buffer_capacity = header_base_count + 100;
    } else {
      // try to pick an initial that covers a large number of cases
      buffer_capacity = 50000;
    }

    // we buffer up the sequence bases then assign them to sequence once all
    // bases are read
    StringBuffer sequence_buffer = new StringBuffer (buffer_capacity);

    while (true) {
      // temporary storage for reading lines
      String line = in_stream.readLine ();

      if (line == null) {
        // we have reached the end of file - break out of loop
        break;
      }

      if (line.startsWith ("//") || !line.startsWith ("     ")) {
        // end of Sequence -
        in_stream.pushBack (line);
        break;
      }

      if (line.length () < 72) {
        throw new ReadFormatException ("line too short while reading " +
                                       "embl sequence data",
                                       in_stream.getLineNumber ());
      }

      this_line_sequence_buffer.setLength (0);
      this_line_sequence_buffer.ensureCapacity (60);

      // if the input file is a well formatted embl entry, then the bases
      // should be in 6 columns of 10 bases each (with a single space
      // separating them).  the first column starts at index 5 of the input
      // line.

      final char [] this_line_char_array = line.toCharArray ();

      for (int i = 0 ; i < 6 ; ++i) {
        final int first_base = 5 + i * 11;

        this_line_sequence_buffer.append (this_line_char_array,
                                          first_base,
                                          10);
      }

      // add the sequence from this line to the sequence we already have.

      String this_line_sequence =
        this_line_sequence_buffer.toString ();

      if (this_line_char_array[59] == ' ') {
        // call trim () because this line (the last line) ends in spaces.
        sequence_buffer.append (this_line_sequence.trim ().toLowerCase ());
      } else {
        sequence_buffer.append (this_line_sequence.toLowerCase ());
      }
    }

    setFromString (sequence_buffer.toString ());
  }

  /**
   *  This is used by writeToStream (), etc as temporary storage.  It is a
   *  class member rather than a local variable so that we don't need to
   *  allocate a object for each call.
   **/
  final private StringBuffer line_buffer = new StringBuffer (90);

  /**
   *  Write this Sequence to the given stream.
   *  @param writer The stream to write to.
   **/
  public void writeToStream (final Writer writer)
      throws IOException {

    final String sequence = toString ();

    // first count A,C,G,T and other bases

    final int SEQUENCE_LINE_BASE_COUNT = 60;

    writer.write ("SQ   Sequence " +
                  sequence.length () + " BP; " +
                  getACount () + " A; " +
                  getCCount () + " C; " +
                  getGCount () + " G; " +
                  getTCount () + " T; " +
                  getOtherCount () + " other;\n");

    for (int i = 0 ; i < sequence.length () ; i += SEQUENCE_LINE_BASE_COUNT) {

      // get the bases in chunks of at most 60

      final int this_line_length;

      if (sequence.length () - i < SEQUENCE_LINE_BASE_COUNT) {
        this_line_length = sequence.length () - i;
      } else {
        this_line_length = SEQUENCE_LINE_BASE_COUNT;
      }

      line_buffer.setLength (0);
      line_buffer.ensureCapacity (90);

      line_buffer.append ("    ");

      final int BLOCK_LENGTH = 10;

      final int BLOCK_COUNT = 6;

      for (int j = 0 ; j < this_line_length ; j += BLOCK_LENGTH) {
        final int this_block_length;

        line_buffer.append (' ');

        if (this_line_length - j < BLOCK_LENGTH) {
          this_block_length = this_line_length - j;
        } else {
          this_block_length = BLOCK_LENGTH;
        }

        line_buffer.append (sequence.substring (i + j,
                                                i + j + this_block_length));
      }

      // the base counter to write at the end of each line
      final int base_count = i + this_line_length;

      final String string_base_count = String.valueOf (base_count);

      // now pad the line with spaces
      final int count_width = string_base_count.length ();

      final int LINE_WIDTH = 80;

      final int buffer_line_length = line_buffer.length ();

      for (int char_index = 0 ;
           char_index < LINE_WIDTH - count_width - buffer_line_length ;
           ++char_index) {
        line_buffer.append (' ');
      }

      line_buffer.append (string_base_count).append ("\n");

      writer.write (line_buffer.toString ());

      if ((i / SEQUENCE_LINE_BASE_COUNT) % 100 == 0) {
        Thread.yield ();
      }
    }
  }

  /**
   *  Take a EMBL sequence header line and extract and return the base count.
   *  If the line cannot be parsed as a header line then -1 is returned.
   **/
  private int getHeaderBaseCount (final String line) {
    if (line.startsWith ("SQ   Sequence ")) {
      final String temp_line = line.substring (14);

      final int space_index = temp_line.indexOf (' ');

      if (space_index == -1) {
        return -1;
      }

      final String count_string = temp_line.substring (0, space_index);

      try {
        return Integer.parseInt (count_string);
      } catch (NumberFormatException e) {
        return -1;
      }
    } else {
      return -1;
    }
  }
}

