/* SimpleEntryInformation.java
 *
 * created: Wed Feb  9 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: /cvsroot/pathsoft/artemis/uk/ac/sanger/artemis/io/SimpleEntryInformation.java,v 1.1 2004/06/09 09:50:31 tjc Exp $
 */


package uk.ac.sanger.artemis.io;

import uk.ac.sanger.artemis.util.*;

import java.util.*;

/**
 *  A SimpleEntryInformation is an EntryInformation object allows any key or
 *  qualifier.
 *
 *  @author Kim Rutherford
 *  @version $Id: SimpleEntryInformation.java,v 1.1 2004/06/09 09:50:31 tjc Exp $
 **/

public class SimpleEntryInformation
  implements EntryInformation {

  /**
   *  Create a new SimpleEntryInformation object.
   **/
  public SimpleEntryInformation () {

  }

  /**
   *  Create a new SimpleEntryInformation object which is a copy of the given
   *  object.
   **/
  public SimpleEntryInformation (final EntryInformation new_info) {
    if (new_info.getAllQualifierInfo () != null) {
      qualifier_info_hash = new_info.getAllQualifierInfo ();
    }
    if (new_info.getValidKeys () != null) {
      valid_keys = new_info.getValidKeys ();
    }
    if (new_info.getUserKeys () != null) {
      user_keys = new_info.getUserKeys ().copy ();
    }

    use_embl_format = new_info.useEMBLFormat ();
  }

  /**
   *  Add a new feature key to the list of keys returned by getValidKeys()
   *  and getSortedValidKeys().  Any key added with this method can have any
   *  qualifier.
   **/
  public void addKey (final Key key) {
    if (user_keys != null && user_keys.contains (key)) {
      return;
    }

    if (user_keys == null) {
      user_keys = new KeyVector ();
    }

    user_keys.add (key);
  }

  /**
   *  Add a QualifierInfo object to this EntryInformation object.  This will
   *  change the return values of getSortedValidKeys() and
   *  getValidKeys() if the new QualifierInfo object refers to a new key.
   *  @exception QualifierInfoException Thrown if a QualifierInfo object with
   *    the same name, but conflicting types has already been added to this
   *    EntryInformation object.
   **/
  public void addQualifierInfo (final QualifierInfo qualifier_info)
      throws QualifierInfoException {

    if (qualifier_info_hash == null) {
      qualifier_info_hash = new QualifierInfoHash ();
    }

    final QualifierInfo current_qualifier_info =
      getQualifierInfo (qualifier_info.getName ());

    if (current_qualifier_info != null) {
      if (qualifier_info.getType () != qualifier_info.UNKNOWN &&
          current_qualifier_info.getType () != qualifier_info.getType ()) {
        final String message =
          "qualifier " + qualifier_info.getName () + " used with " +
          "conflicting types";
        throw new QualifierInfoException (message);
      }

      if (qualifier_info.getValidKeys () == null ||
          qualifier_info.getRequiredKeys () == null) {

        qualifier_info_hash.put (qualifier_info);
      }
    } else {
      qualifier_info_hash.put (qualifier_info);
    }

    final KeyVector qualifier_valid_keys = qualifier_info.getValidKeys ();

    if (qualifier_valid_keys != null) {
      for (int i = 0 ; i < qualifier_valid_keys.size () ; ++i) {
        final Key this_key = qualifier_valid_keys.elementAt (i);

        if (valid_keys != null && valid_keys.contains (this_key)) {
          continue;
        }

        if (valid_keys == null) {
          valid_keys = new KeyVector ();
        }

        valid_keys.add (this_key);
      }
    }
  }

  /**
   *  Return a vector containing the valid feature keys.  The returned
   *  Vector is a copy.
   *  @return null if and only if all keys are valid.
   **/
  public KeyVector getValidKeys () {
    if (valid_keys == null) {
      if (user_keys == null) {
        return null;
      } else {
        return user_keys.copy ();
      }
    } else {
      final KeyVector return_keys = valid_keys.copy ();

      if (user_keys != null) {
        for (int i = 0 ; i < user_keys.size () ; ++i) {
          if (!return_keys.contains (user_keys.elementAt (i))) {
            return_keys.add (user_keys.elementAt (i));
          }
        }
      }

      return return_keys;
    }
  }

  /**
   *  Return a alphanumerically sorted vector of the valid keys.
   *  @return null if and only if all keys are valid.
   **/
  public KeyVector getSortedValidKeys () {
    final KeyVector return_vector = getValidKeys ();

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

    return_vector.sort ();
    return return_vector;
  }

  /**
   *  Return the Key that should be used when a new (empty) feature is created.
   **/
  public Key getDefaultKey () {
    final Key misc_feature_key = new Key ("misc_feature");

    if (isValidKey (misc_feature_key)) {
      return misc_feature_key;
    } else {
      return getValidKeys ().elementAt (0);
    }
  }

  /**
   *  Return a vector containing all valid feature qualifier names for
   *  features with the given key.  The returned StringVector is a copy.
   *  @return null if and only if any Qualifier is legal.
   **/
  public StringVector getValidQualifierNames (final Key key) {
    if (getQualifierInfoHash () == null) {
      return null;
    }

    final StringVector all_names = getQualifierInfoHash ().names ();

    final StringVector return_names = new StringVector ();

    for (int i = 0 ; i < all_names.size () ; ++i) {
      final QualifierInfo this_qualifier_info =
        getQualifierInfoHash ().get (all_names.elementAt (i));

      if (this_qualifier_info.isValidFor (key)) {
        return_names.add (this_qualifier_info.getName ());
      }
    }

    if (return_names.size () == 0) {
      return null;
    } else {
      return_names.sort ();
      return return_names;
    }
  }

 /**
   *  Return a vector containing the names of the required feature qualifiers
   *  for the given key.
   *  @return null if and only if no Qualifier is necessary.
   **/
  public StringVector getRequiredQualifiers (final Key key) {
    if (getQualifierInfoHash () == null) {
      return null;
    }

    final StringVector all_names = getQualifierInfoHash ().names ();

    final StringVector return_names = new StringVector ();

    for (int i = 0 ; i < all_names.size () ; ++i) {
      final QualifierInfo this_qualifier_info =
        getQualifierInfoHash ().get (all_names.elementAt (i));

      if (this_qualifier_info.isRequiredFor (key)) {
        return_names.add (this_qualifier_info.getName ());
      }
    }

    if (return_names.size () == 0) {
      return null;
    } else {
      return_names.sort ();
      return return_names;
    }
  }

  /**
   *  Return true if and only if the given String contains a valid
   *  qualifier name.
   **/
  public boolean isValidQualifier (final String name) {
    if (getQualifierInfoHash () == null) {
      return true;
    }

    if (getQualifierInfoHash ().get (name) == null) {
      return false;
    } else {
      return true;
    }
  }

  /**
   *  Return true if and only if given qualifier_name is a legal qualifier
   *  name for the given key.
   **/
  public boolean isValidQualifier (final Key key,
                                   final String qualifier_name) {
    if (getUserKeys () != null && getUserKeys ().contains (key)) {
      // any qualifier is valid for a user key
      return true;
    }

    if (getQualifierInfoHash () == null) {
      return true;
    }

    final QualifierInfo qualifier_info =
      getQualifierInfoHash ().get (qualifier_name);

    if (qualifier_info == null) {
      return false;
    } else {
      return qualifier_info.isValidFor (key);
    }
  }

  /**
   *  Return true if and only if given qualifier_name is a legal qualifier
   *  name for the given key and must be present in a feature with that key.
   **/
  public boolean isRequiredQualifier (final Key key,
                                      final String qualifier_name) {
    if (getUserKeys () != null && getUserKeys ().contains (key)) {
      // there are no required qualifiers for a user key
      return false;
    }

    if (getQualifierInfoHash () == null) {
      return false;
    }

    final QualifierInfo qualifier_info =
      getQualifierInfoHash ().get (qualifier_name);

    if (qualifier_info == null) {
      return false;
    } else {
      return qualifier_info.isRequiredFor (key);
    }
  }

  /**
   *  Return true if and only if the given key is a valid in this
   *  EntryInformation.
   **/
  public boolean isValidKey (final Key key) {
    if (valid_keys == null) {
      return true;
    } else {
      if (valid_keys.contains (key)) {
        return true;
      } else {
        if (getUserKeys () != null && getUserKeys ().contains (key)) {
          return true;
        } else {
          return false;
        }
      }
    }
  }

  /**
   *  Return the QualifierInfo object for the given qualifier name, or null if
   *  there are no qualifiers with the given name associated with this
   *  EntryInformation object.
   **/
  public QualifierInfo getQualifierInfo (final String qualifier_name) {
    if (getQualifierInfoHash () == null) {
      return null;
    } else {
      return getQualifierInfoHash ().get (qualifier_name);
    }
  }

  /**
   *  Return a default EntryInformation object.  It will allow any key or
   *  qualifier.
   **/
  public static EntryInformation getDefaultEntryInformation () {
    return new SimpleEntryInformation ();
  }

  /**
   *  Returns true if strict EMBL format will be used when writing.  If true
   *  qualifiers will always wrap at 80 columns.
   *  EMBL format locations have the complement (if any) inside the join.  eg:
   *  join(complement(1..100),complement(200..300)).
   *  The default format has the complement on the outside, which is much more
   *  readable.  eg: complement(join(1..100,200..300))
   **/
  public boolean useEMBLFormat () {
    return use_embl_format;
  }

  /**
   *  Set the use_embl_format flag (see useEMBLFormat ()).  true means the
   *  strict EMBL format should be used when writing.
   **/
  public void setEMBLFormat (final boolean use_embl_format) {
    this.use_embl_format = use_embl_format;
  }

  /**
   *  Return a Vector contains all the QualifierInfo objects that this
   *  EntryInformation knows about.
   *  @return null if and only if there are no QualifierInfo objects in this
   *    object.
   **/
  public QualifierInfoHash getAllQualifierInfo () {
    if (getQualifierInfoHash () == null) {
      return null;
    } else {
      return getQualifierInfoHash ().copy ();
    }
  }

  /**
   *  Fix this EntryInformation so that the given exception won't happen
   *  again.
   **/
  public void fixException (final EntryInformationException exception) {
    if (exception instanceof InvalidKeyException) {
      final InvalidKeyException key_exception =
        (InvalidKeyException) exception;

      addKey (key_exception.getKey ());
    }

    if (exception instanceof InvalidRelationException) {
      final InvalidRelationException relation_exception =
        (InvalidRelationException) exception;

      if (relation_exception.getQualifier () == null) {
        final Key key_to_add = relation_exception.getKey ();

        addKey (key_to_add);
      } else {
        final Qualifier qualifier = relation_exception.getQualifier ();

        // change the QualifierInfo object in the EntryInformation so that it
        // can handle this key/qualifier combination

        final QualifierInfo qualifier_info =
          getQualifierInfo (qualifier.getName ());

        final QualifierInfo new_qualifier_info;

        if (qualifier_info == null) {
          new_qualifier_info =
            new QualifierInfo (qualifier.getName (),
                               QualifierInfo.QUOTED_TEXT, null, null, false);
        } else {
          new_qualifier_info =
            new QualifierInfo (qualifier_info.getName (),
                               qualifier_info.getType (), null, null, false);
        }

        try {
          addQualifierInfo (new_qualifier_info);
        } catch (QualifierInfoException e) {
          throw new Error ("internal error - unexpected exception: " + e);
        }
      }
    }

    if (exception instanceof InvalidQualifierException) {
      final String exception_qualifier_name =
        ((InvalidQualifierException) exception).getQualifier ().getName ();

      // add it again, but make it less restrictive
      final QualifierInfo new_info =
        new QualifierInfo (exception_qualifier_name, QualifierInfo.UNKNOWN,
                           null, null, false);

      try {
        addQualifierInfo (new_info);
      } catch (QualifierInfoException e) {
        throw new Error ("internal error - unexpected exception: " + e);
      }
    }
  }

  /**
   *  Returns the keys that where added with addKey ().
   *  @return null if and only if there are no user keys yet.
   **/
  public KeyVector getUserKeys () {
    return user_keys;
  }

  /**
   *  Return qualifier_info_hash.
   **/
  private QualifierInfoHash getQualifierInfoHash () {
    return qualifier_info_hash;
  }

  /**
   *  Returns valid_keys.
   **/
  private KeyVector getKeys () {
    return valid_keys;
  }

  /**
   *  This is set by the constructor and addQualifierInfo().  null means
   *  any qualifier is valid
   **/
  private QualifierInfoHash qualifier_info_hash = null;

  /**
   *  This is set by the constructor and addQualifierInfo().  null means any
   *  key is valid
   **/
  private KeyVector valid_keys = null;

  /**
   *  This is set by the constructor and by addUserKey().  null means any
   **/
  private KeyVector user_keys = null;

  /**
   *  true if the EMBL location format should be used when writing.  (See
   *  useEMBLLocationFormat () ).
   **/
  private boolean use_embl_format = false;
}
