/* SimpleDocumentFeature.java
 *
 * created: Thu Feb 17 2000
 *
 * This file is part of Artemis
 * 
 * Copyright (C) 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/uk/ac/sanger/pathogens/embl/SimpleDocumentFeature.java,v 1.2 2000/07/24 12:34:59 kmr Exp $
 */

package uk.ac.sanger.pathogens.embl;

import uk.ac.sanger.pathogens.*;
import java.io.*;

/**
 *  SimpleDocumentFeature class
 *
 *  @author Kim Rutherford <kmr@sanger.ac.uk>
 *  @version $Id: SimpleDocumentFeature.java,v 1.2 2000/07/24 12:34:59 kmr Exp $
 **/

abstract class SimpleDocumentFeature extends LineGroup
    implements Feature {
  /**
   *  Create a new DocumentFeature with the given DocumentEntry as it's
   *  owner.  The Entry can be null if this Feature doesn't have an owner
   *  (yet).
   **/
  public SimpleDocumentFeature (final DocumentEntry entry) {
    this.entry = entry;
  }

  /**
   *  Return the Entry object that contains this Feature.
   **/
  public Entry getEntry () {
    return entry;
  }

  /**
   *  If the entry is not null set its dirty flag.
   **/
  protected void setDirtyFlag () {
    if (getEntry () != null) {
      getDocumentEntry ().setDirtyFlag ();
    }
  }


  /**
   *  Return the DocumentEntry that contains this DocumentFeature.  A
   *  DocumentFeature will always be owned by a DocumentEntry.
   **/
  public DocumentEntry getDocumentEntry () {
    return entry;
  }

  /**
   *  Set the owning Entry of this Feature.  Other objects should call
   *  setDocumentEntry () to change the owner.
   *  @param entry The Entry that now owns this Feature.
   **/
  private void setEntry (final DocumentEntry entry) {
    this.entry = entry;
  }

  /**
   *  Set the owning DocumentEntry of this Feature.
   *  @param entry The Entry that now owns this Feature.
   **/
  public void setDocumentEntry (final DocumentEntry entry) {
    setEntry (entry);
  }

  /**
   *  This is incremented each time a constructor is called.
   **/
  private static long id_counter = 0;

  /**
   *  Set the key, location and qualifiers of this object to be the same as
   *  the given Feature.
   *  @param key The new feature key
   *  @param location The Location object for the new feature
   *  @param qualifiers The qualifiers for the new feature
   *  @exception EntryInformationException Thrown if this Feature cannot contain
   *    the given Key, Qualifier or Key/Qualifier combination.
   **/
//    public void set (final Feature feature)
//        throws EntryInformationException, ReadOnlyException {
//      set (feature.getKey (), feature.getLocation (), feature.getQualifiers ());
//    }

  /**
   *  Set the value of this object.
   *  @param key The new feature key
   *  @param location The Location object for the new feature
   *  @param qualifiers The qualifiers for the new feature
   *  @exception EntryInformationException Thrown if this Feature cannot contain
   *    the given Key, Qualifier or Key/Qualifier combination.
   **/
  public void set (final Key key,
                   final Location location,
                   final QualifierVector qualifiers)
      throws EntryInformationException, OutOfRangeException, ReadOnlyException {
    // save the old contents of the feature so they cannot be reset if there
    // is an exception
    for (int i = 0 ; i < qualifiers.size () ; ++i) {
      final String this_name = qualifiers.elementAt (i).getName ();

      if (!getEntryInformation ().isValidQualifier (key, this_name)) {
        final String message =
          key + " cannot have /" + qualifiers.elementAt (i).getName () +
          " as a qualifier";
        throw new InvalidRelationException (message, key,
                                            qualifiers.elementAt (i));
      }
    }

    this.key = key;
    this.qualifiers = qualifiers;
    setLocation (location);

    setDirtyFlag ();
  }

  /**
   *  Set the value of this object.
   *  @param entry The Entry that now owns this Feature.
   *  @param key The new feature key
   *  @param location The Location object for the new feature
   *  @param qualifiers The qualifiers for the new feature
   *  @exception EntryInformationException Thrown if this Feature cannot contain
   *    the given Key, Qualifier or Key/Qualifier combination.
   **/
  private void set (final DocumentEntry entry,
                    final Key key,
                    final Location location,
                    final QualifierVector qualifiers)
      throws EntryInformationException, ReadOnlyException {
    setDocumentEntry (entry);
    setKey (key);

    // throw away the cache
    first_base = -1;
    last_base = -1;
    this.location = location;

    setQualifiers (qualifiers);

    setDirtyFlag ();
  }

  /**
   *  Set the key field of this object.
   *  @param key The new feature key
   *  @exception EntryInformationException Thrown if this Feature cannot
   *    contain the given Key, Qualifier or Key/Qualifier combination.
   **/
  public void setKey (final Key key)
      throws EntryInformationException, ReadOnlyException{
    for (int i = 0 ; i < qualifiers.size () ; ++i) {
      final String this_name = qualifiers.elementAt (i).getName ();

      if (!getEntryInformation ().isValidQualifier (key, this_name)) {
        final String message =
          key + " cannot have /" + qualifiers.elementAt (i).getName () +
          " as a qualifier";
        throw new InvalidRelationException (message, key,
                                            qualifiers.elementAt (i));
      }
    }
    this.key = key;

    setDirtyFlag ();
  }

  /**
   *  Set the location of this object.
   *  @param location The Location object for the new feature
   **/
  public void setLocation (final Location location)
      throws ReadOnlyException, OutOfRangeException {
    // save the Entry because the call to remove () will set it to null
    final Entry saved_entry = getEntry ();

    if (saved_entry != null) {
      // remove and then add the Feature because changing the Location may
      // change the position of the Feature in the FeatureTable
      saved_entry.remove (this);
    }

    this.location = location;

    // throw away the cache
    first_base = -1;
    last_base = -1;

    if (saved_entry != null) {
      try {
        saved_entry.add (this);
      } catch (EntryInformationException e) {
        saved_entry.forcedAdd (this);

        throw new Error ("internal error (some feature qualifiers may have " +
                         "been lost) - unexpected exception: " + e);
      }
    }

    setDirtyFlag ();
  }

  /**
   *  Set the qualifiers of this object.
   *  @param qualifiers The new qualifiers for the feature.  null means remove
   *    all qualifiers.
   *  @exception EntryInformationException Thrown if this Feature cannot contain
   *    the given Key, Qualifier or Key/Qualifier combination.
   **/
  public void setQualifiers (final QualifierVector qualifiers)
      throws EntryInformationException, ReadOnlyException {

    if (qualifiers == null) {
      this.qualifiers = new QualifierVector ();
    } else {
      for (int i = 0 ; i < qualifiers.size () ; ++i) {

        final String this_name = qualifiers.elementAt (i).getName ();

        if (!getEntryInformation ().isValidQualifier (getKey (), this_name)) {
          final String message =
            getKey () + " cannot have /" +
            qualifiers.elementAt (i).getName () + " as a qualifier";
          throw new InvalidRelationException (message, getKey (),
                                              qualifiers.elementAt (i));
        }
      }
      this.qualifiers = qualifiers.copy ();
    }

    setDirtyFlag ();
  }

  /**
   *  Add the given Qualifier to this Feature.  If this Feature contains a
   *  Qualifier with the same name as the new Qualifier it will be replaced.
   *  @param qualifier The new qualifier to add.
   *  @exception EntryInformationException Thrown if this Feature cannot contain
   *    the given Key, Qualifier or Key/Qualifier combination.
   **/
  public void setQualifier (final Qualifier qualifier)
      throws EntryInformationException, ReadOnlyException {
    getQualifiers ().setQualifier (qualifier);

    setDirtyFlag ();
  }

  /**
   *  Remove the Qualifier with the given name.  If there is no Qualifier with
   *  that name then return immediately.
   *  @exception EntryInformationException Thrown if a required qualifier is
   *    removed.
   *  @param name The qualifier name to look for.
   **/
  public void removeQualiferByName (final String name)
      throws EntryInformationException, ReadOnlyException {
    getQualifiers ().removeQualiferByName (name);

    setDirtyFlag ();
  }

  /**
   *  Add the values from the given qualifier to the Qualifier object with the
   *  same name in this Feature or otherwise add a copy of the argument.
   *  @param qualifier This object contains name and values to add.
   *  @return The Qualifier that was changed or created.
   *  @exception EntryInformationException Thrown if this Feature cannot contain
   *    the given Key, Qualifier or Key/Qualifier combination.
   **/
  public Qualifier addQualifierValues (final Qualifier qualifier)
      throws EntryInformationException {
    setDirtyFlag ();

    return getQualifiers ().addQualifierValues (qualifier);
  }

  /**
   *  Return the unique identifier of this PublicDBStreamFeature.
   **/
  public long getNumericID () {
    return id;
  }

  /**
   *  Return the Key of this Feature, as passed to the constructor.
   **/
  public Key getKey () {
    return key;
  }

  /**
   *  Return the Location of this Feature, as passed to the constructor.
   **/
  public Location getLocation () {
    return location;
  }

  /**
   *  Return the first base of this feature.
   **/
  public int getFirstBase () {
    if (first_base == -1) {
      first_base = getLocation ().getFirstBase ();
    }
    return first_base;
  }

  /**
   *  Return the last base of this feature.
   **/
  public int getLastBase () {
    if (last_base == -1) {
      last_base = getLocation ().getLastBase ();
    }
    return last_base;
  }

  /**
   *  Return a vector containing the qualifiers for this feature.  This method
   *  does not return a copy of the qualifier vector so changing the vector
   *  will change the feature.
   **/
  public QualifierVector getQualifiers () {
    return qualifiers;
  }

  /**
   *  Return the Qualifier in this Feature with the given name or null if
   *  there no such Qualifier.
   **/
  public Qualifier getQualifierByName (String name) {
    return getQualifiers ().getQualifierByName (name);
  }


  /**
   *  Return the EntryInformation object from the Entry of this feature (if
   *  there is an Entry) or SimpleEntryInformation.default_entry_information
   *  otherwise.
   **/
  public EntryInformation getEntryInformation () {
    if (getEntry () == null) {
      return SimpleEntryInformation.getDefaultEntryInformation ();
    } else {
      return getEntry ().getEntryInformation ();
    }
  }

  /**
   *  Return a String containing this Feature written in its native format.
   **/
  public String toString () {
    final StringWriter string_writer = new StringWriter ();

    try {
      writeToStream (string_writer);
    } catch (IOException e) {
      throw new Error ("internal error - unexpected exception: " + e);
    }

    return string_writer.toString ();
  }

  /**
   *  Return the reference of a new copy of this Feature.
   **/
  public abstract Feature copy ();

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

  /**
   *  The is the key that was passed to the constructor
   **/
  private Key key;

  /**
   *  The is the Location object that was passed to the constructor
   **/
  private Location location;

  /**
   *  The is the vector of qualifiers that was passed to the constructor
   **/
  private QualifierVector qualifiers = new QualifierVector ();

  /**
   *  The first base of the location of this feature.  This is a cache.
   **/
  private int first_base = -1;

  /**
   *  The last base of the location of this feature.  This is a cache.
   **/
  private int last_base = -1;

  /**
   *  A unique identifier for this feature.
   **/
  private final long id = id_counter++;
}
