/*
 *This library is free software; you can redistribute it and/or
 *modify it under the terms of the GNU Lesser General Public
 *License as published by the Free Software Foundation; either
 *version 2.1 of the License, or (at your option) any later version.
 *
 *This library 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
 *Lesser General Public License for more details.
 *
 *You should have received a copy of the GNU Lesser General Public
 *License along with this library; if not, write to the Free Software
 *Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

/* 
 * This software was written by Julian Selley (j.selley@man.ac.uk)
 * whilst at the University of Manchester as a Research Assistant. 
 *
 * The initial code base is copyright by the University of Manchester. 
 * Modifications to the initial code base are copyright of their 
 * respective authors, or their employers as appropriate. Authorship 
 * of the modifications may be determined from the ChangeLog placed 
 * at the end of this file
 */

package uk.ac.man.bioinf.io.parsers; // Package name inserted by JPack

import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import uk.ac.man.bioinf.debug.Debug;
import uk.ac.man.bioinf.io.AlignmentOutputParser;
import uk.ac.man.bioinf.io.ParserExceptionHandler;
import uk.ac.man.bioinf.io.SequenceWriterException;
import uk.ac.man.bioinf.sequence.Sequences;
import uk.ac.man.bioinf.sequence.alignment.DefaultSequenceAlignment;
import uk.ac.man.bioinf.sequence.alignment.Gap;
import uk.ac.man.bioinf.sequence.alignment.GappedSequence;
import uk.ac.man.bioinf.sequence.alignment.SequenceAlignment;
import uk.ac.man.bioinf.sequence.event.SequenceVetoException;
import uk.ac.man.bioinf.sequence.identifier.Identifier;
import uk.ac.man.bioinf.sequence.identifier.NoIdentifier;
import uk.ac.man.bioinf.sequence.types.ProteinSequenceType;
import uk.ac.man.bioinf.util.IntArrayList;
import uk.ac.man.bioinf.io.InvalidSequenceTypeParserException;


/**
 * A filter that reads an alignment in PIR/NBRF protein sequence
 * format and returns a sequence alignment object. This parser has no
 * state attached to it, and may be re-entered (ie it is thread safe).
 *
 *
 * Created: Fri May 26 10:18:02 2000
 *
 * @author J Selley
 * @version $Id: PIRProteinAlignmentParser.java,v 1.15 2001/04/11 17:04:43 lord Exp $
 */

public class PIRProteinAlignmentParser 
  extends AbstractProteinAlignmentInputParser
  implements AlignmentOutputParser
{
  
  /**
   * This function parses an input reader which contains a (set of)
   * PIR/NBRF sequence(s), and returns an alignment containing
   * them. It only reads the protein version of PIR/NBRF format. Any
   * exceptions thrown up (except the IOException) are packaged up and
   * sent to an exception handler.
   *
   * @param r the input reader
   * @param eh the exception handler
   * @return the sequence alignment
   * @exception IOException if there is an error in the reader
   */
  public SequenceAlignment parse(Reader r, ParserExceptionHandler eh)
    throws IOException
  {
    return parse( new NoIdentifier(), r, eh );
  }
  
  
  public SequenceAlignment parse( Identifier ident, Reader r, ParserExceptionHandler eh )
    throws IOException
  {
    List seqObjs = new ArrayList();
    IntArrayList offsets = new IntArrayList();
    GappedSequence[] sequences;
    String currentLine, resourceString = "";
    String seq = "";
    BufferedReader reader = new BufferedReader(r);

    while ((currentLine = reader.readLine()) != null) {
      // remove whitespace
      StringTokenizer st = new StringTokenizer(currentLine);
      String newCurrentLine = "";
      while (st.hasMoreTokens()){
	newCurrentLine += st.nextToken();
      }
      
      currentLine = newCurrentLine;

      if (currentLine.startsWith(">P1;")) {
	// reset the sequence string
	seq = "";

	// initiate the resource string
	resourceString = currentLine.substring(4);

	// get the comment line
	currentLine = reader.readLine();
	// add comment line to resource string
	if (currentLine != "")
	  resourceString += " [" + currentLine + "]";

	continue;
      } else if (currentLine.endsWith("*")) {
	// remove the asterisk and add the rest of sequence to seq
	seq += currentLine.substring(0, (currentLine.length() - 1));

	// store the sequence if it isn't empty
	if (seq != "") {
	  // remove leading gaps
	  GappedSequence s = toSequence(seq, resourceString, eh);
	  try {
	    offsets.add(Sequences.chompLeadingGaps(s));
	  } catch (SequenceVetoException sve) {
	    /* it should be impossible to get a veto here as we have just
	     * created the GappedSequence and have no added any listeners
	     * to it!
	     */
	    if(Debug.debug) 
	      Debug.both(this, 
			 "ASSERT: veto where there should not be one!", 
			 sve);
	  }
	  /* if there is a sequence string, convert it to a gapped
	   * sequence and add it to what will become the alignment.
	   */
	  seqObjs.add(s);
	}
      } else {
	seq += currentLine;
      }
    }

    sequences = new GappedSequence[seqObjs.size()];
    System.arraycopy(seqObjs.toArray(), 0, sequences, 0, sequences.length);
    
    return new DefaultSequenceAlignment(sequences,
					ProteinSequenceType.getInstance(), 
					offsets.toArray(), ident );
  }

  /**
   * This method parses a sequence alignment and writes it out to the
   * supplied writer. It formats the output to 80 characters wide.
   * <p>
   * <b>N.B.</b>, this method does nothing with the writer other than
   * write output to it - <i>i.e.</i> it does not close the writer.
   *
   * @param sa the sequence alignment to be written.
   * @param writer the writer to which the sequence is to be written
   * @param eh the exception handler to which exceptions are parsed.
   */
  public Writer write(SequenceAlignment sa, Writer writer, 
		    ParserExceptionHandler eh) 
  {
    /* check that the sequence alignment is a protein sequence
     * alignment - throw an exception if it isn't
     */
    if (sa.getSequenceType() != ProteinSequenceType.getInstance())
      eh.handleException(new InvalidSequenceTypeParserException
	(ProteinSequenceType.getInstance()));
    
    
    // cycle through each sequence in the alignment...
    for (int i = 0; i < sa.getNumberSequences(); i++) {
      GappedSequence seq = sa.getSequenceAt(i + 1);  // the sequence of interest
      
      // convert the sequence to a string remembering the inset
      String inset = "";
      for (int j = 0; j < sa.getInset(i + 1); j++)
	inset += "" + Gap.GAP.toChar() + "";
      String seqString = inset + Sequences.getGappedSequenceAsString(seq);
      // get the description and source of the sequence if exists
      String description = seq.getIdentifier().getTitle();
      String source = seq.getIdentifier().getSource().getTitle();
      // set the description and source if not already set
      if (description == "")
	description = "Unknown protein";
      if ((source == "") || (source == "No Title"))
	source = "Unknown source";
      
      // write the sequence
      try {
	// print the header info for the sequence
	writer.write(">P1;" + description + "\n");
	writer.write("From: " + source + "\n");
	
	// print the sequence
	for( int j = 0; j < seqString.length(); j++ ){
	  writer.write( seqString.charAt( j ) );
	  // 80 chars per line
	  if( ((j + 1) % 80) == 0 ){
	    writer.write( "\n" );
	  }
	}
	// write a star to complete the format
	writer.write("*\n");
      } catch (IOException e) {
	eh.handleException(new SequenceWriterException());
	if (Debug.debug)
	  Debug.both(this, "PIR Parser (writing): IOException caught when " +
		     "writing the sequence " + seq + " " + 
		     description + "!", e);
      }
    }
    return writer;
  }

  /**
   * Returns that this is a PIR/NBRF protein sequence parser.
   *
   * @return the description of this parser
   */
  public String getDescription() 
  {
    return "PIR/NBRF protein sequence parser";
  }
} // PIRProteinAlignmentParser



/*
 * ChangeLog
 * $Log: PIRProteinAlignmentParser.java,v $
 * Revision 1.15  2001/04/11 17:04:43  lord
 * Added License agreements to all code
 *
 * Revision 1.14  2001/01/31 18:02:52  lord
 * Now returns the writer, rather than void. Largely because it can.
 *
 * Revision 1.13  2001/01/26 17:12:40  lord
 * Re-written parser. Fixed two bugs. Now prints 80 char lines (rather
 * than 79) and no longer mixes off the last line.
 *
 * Revision 1.12  2001/01/23 17:59:57  lord
 * Changed exception handling
 *
 * Revision 1.11  2000/12/18 12:13:53  jns
 * o getting rid of system.out.println to avoid noisy output out of debug
 * mode
 *
 * Revision 1.10  2000/11/02 17:38:53  jns
 * o dealing with a pending: moved the code to create the sequence to the
 * location that was more logical - i.e., when you have got to the end of
 * each sequence, which in PIR is easy because it is denoted by an '*'.
 *
 * Revision 1.9  2000/09/11 13:19:07  lord
 * Added identifier support
 *
 * Revision 1.8  2000/08/21 17:28:41  jns
 * o addition of output stuff (e.g., write procedure)
 *
 * Revision 1.7  2000/07/27 22:13:06  jns
 * o set to extend Abs. Input parser - name change - will later extend output
 * parser as well.
 *
 * Revision 1.6  2000/07/18 11:10:33  lord
 * Import rationalisation.
 * Changes due to removal of biointerface
 *
 * Revision 1.5  2000/06/30 09:34:43  jns
 * o have added setting of sequence offsets and chomping of trailing gaps
 *
 * Revision 1.4  2000/06/16 11:36:29  jns
 * o adding chomp of leading gaps into code
 *
 * Revision 1.3  2000/06/16 09:38:52  jns
 * o added getDescription method to tie with the alteration to the
 * AlignmentParser interface
 *
 * Revision 1.2  2000/06/08 21:21:39  jns
 * o changed so no longer has state
 * o tie in with interface
 *
 * Revision 1.1  2000/06/08 12:32:17  jns
 * o I/O stuff written to parse in a sequence file
 * o currently only written the PIRProtein parser
 *
 */
