/* vim: set ts=2: */
/**
 * Copyright (c) 2006 The Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *   1. Redistributions of source code must retain the above copyright
 *      notice, this list of conditions, and the following disclaimer.
 *   2. Redistributions in binary form must reproduce the above
 *      copyright notice, this list of conditions, and the following
 *      disclaimer in the documentation and/or other materials provided
 *      with the distribution.
 *   3. Redistributions must acknowledge that this software was
 *      originally developed by the UCSF Computer Graphics Laboratory
 *      under support by the NIH National Center for Research Resources,
 *      grant P41-RR01081.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */
package ext.edu.ucsf.rbvi.strucviz2;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.slf4j.LoggerFactory;

/**
 * This class provides the implementation for the ChimeraResidue, object
 * 
 * @author scooter
 * 
 */

public class ChimeraResidue
        implements ChimeraStructuralObject, Comparable<ChimeraResidue>
{

  /* Constants */
  public static final int SINGLE_LETTER = 0; // Display residues as a single
                                             // letter

  public static final int THREE_LETTER = 1; // Display residues as three letters

  public static final int FULL_NAME = 2; // Display full residue names

  private String type; // Residue type

  private String index; // Residue index

  private String chainId; // ChainID for this residue

  private int modelNumber; // model number for this residue

  private int subModelNumber; // sub-model number for this residue

  protected int residueNumber;

  protected String insertionCode;

  private ChimeraModel chimeraModel; // ChimeraModel the residue is part of

  private Object userData; // user data to associate with this residue

  // public static HashMap<String, String> aaNames = null; // a map of amino
  // acid
  // names
  private static int displayType = THREE_LETTER; // the current display type

  private boolean selected = false; // the selection state

  /**
   * Constructor to create a new ChimeraResidue
   * 
   * @param type
   *          the residue type
   * @param index
   *          the index of the residue
   * @param modelNumber
   *          the model number this residue is part of
   */
  public ChimeraResidue(String type, String index, int modelNumber)
  {
    this(type, index, modelNumber, 0);
  }

  /**
   * Constructor to create a new ChimeraResidue
   * 
   * @param type
   *          the residue type
   * @param index
   *          the index of the residue
   * @param modelNumber
   *          the model number this residue is part of
   * @param subModelNumber
   *          the sub-model number this residue is part of
   */
  public ChimeraResidue(String type, String index, int modelNumber,
          int subModelNumber)
  {
    this.type = type;
    this.index = index;
    this.modelNumber = modelNumber;
    this.subModelNumber = subModelNumber;
    splitInsertionCode(this.index);
    // if (aaNames == null) {
    // initNames();
    // }
  }

  /**
   * Constructor to create a new ChimeraResidue from an input line
   * 
   * @param chimeraInputLine
   *          a Chimera residue description
   */
  // invoked when listing (selected) residues: listr spec #0; lists level
  // residue
  // Line: residue id #0:37.A type MET
  public ChimeraResidue(String chimeraInputLine)
  {
    // initNames();
    String[] split1 = chimeraInputLine.split(":");

    // First half has model number -- get the number
    int numberOffset = split1[0].indexOf('#');
    String model = split1[0].substring(numberOffset + 1);
    int decimalOffset = model.indexOf('.'); // Do we have a sub-model?
    try
    {
      this.subModelNumber = 0;
      if (decimalOffset > 0)
      {
        this.subModelNumber = Integer
                .parseInt(model.substring(decimalOffset + 1));
        this.modelNumber = Integer
                .parseInt(model.substring(0, decimalOffset));
      }
      else
      {
        this.modelNumber = Integer.parseInt(model);
      }
    } catch (Exception e)
    {
      LoggerFactory.getLogger(ChimeraResidue.class)
              .error("Unexpected return from Chimera: " + model);
      this.modelNumber = -1;
    }

    // Second half has residue info: index & type
    String[] rTokens = split1[1].split(" ");
    this.type = rTokens[2];

    String[] iTokens = rTokens[0].split("\\.");
    if (iTokens.length > 0)
    {
      this.index = iTokens[0];

      // Careful, might or might not have a chainID
      if (iTokens.length > 1)
        this.chainId = iTokens[1];
      else
        this.chainId = "_";
    }
    else
      this.index = rTokens[0];

    splitInsertionCode(this.index);
  }

  /**
   * Set the selected state for this residue
   * 
   * @param selected
   *          the selection state to set
   */
  public void setSelected(boolean selected)
  {
    this.selected = selected;
  }

  /**
   * Return the selected state of this residue
   * 
   * @return the selected state
   */
  public boolean isSelected()
  {
    return selected;
  }

  /**
   * Return an array made up of this residue (required for
   * ChimeraStructuralObject interface
   * 
   * @return a List with this residue as its sole member
   */
  public List<ChimeraStructuralObject> getChildren()
  {
    List<ChimeraStructuralObject> v = new ArrayList<ChimeraStructuralObject>();
    v.add(this);
    return v;
  }

  /**
   * Return the string representation of this residue as follows:
   * "<i>residue_name</i> <i>index</i>" where <i>residue_name</i> could be
   * either the single letter, three letter, or full name representation of the
   * amino acid.
   * 
   * @return the string representation
   */
  public String displayName()
  {
    return toString();
  }

  /**
   * Return the string representation of this residue as follows:
   * "<i>residue_name</i> <i>index</i>" where <i>residue_name</i> could be
   * either the single letter, three letter, or full name representation of the
   * amino acid.
   * 
   * @return the string representation
   */
  public String toString()
  {
    if (displayType == FULL_NAME)
    {
      return (ChimUtils.toFullName(type) + " " + index);
    }
    else if (displayType == SINGLE_LETTER)
    {
      return (ChimUtils.toSingleLetter(type) + " " + index);
    }
    else if (displayType == THREE_LETTER)
    {
      return (ChimUtils.toThreeLetter(type) + " " + index);
    }
    else
    {
      return (type + " " + index);
    }
  }

  /**
   * Return the Chimera specification for this Residue
   * 
   * @return Chimera specification
   */
  public String toSpec()
  {
    if (!chainId.equals("_"))
      return ("#" + modelNumber + ":" + index + "." + chainId);
    else
      return ("#" + modelNumber + ":" + index + ".");
  }

  /**
   * Get the index of this residue
   * 
   * @return residue index
   */
  public String getIndex()
  {
    return this.index;
  }

  /**
   * Get the chainID for this residue
   * 
   * @return String value of the chainId
   */
  public String getChainId()
  {
    return this.chainId;
  }

  /**
   * Get the type for this residue
   * 
   * @return residue type
   */
  public String getType()
  {
    return this.type;
  }

  /**
   * Get the model number for this residue
   * 
   * @return the model number
   */
  public int getModelNumber()
  {
    return this.modelNumber;
  }

  /**
   * Get the sub-model number for this residue
   * 
   * @return the sub-model number
   */
  public int getSubModelNumber()
  {
    return this.subModelNumber;
  }

  /**
   * Get the model this residue is part of
   * 
   * @return the ChimeraModel
   */
  public ChimeraModel getChimeraModel()
  {
    return this.chimeraModel;
  }

  /**
   * Set the model this residue is part of
   * 
   * @param chimeraModel
   *          the ChimeraModel this model is part of
   */
  public void setChimeraModel(ChimeraModel chimeraModel)
  {
    this.chimeraModel = chimeraModel;
  }

  /**
   * Get the user data for this residue
   * 
   * @return user data
   */
  public Object getUserData()
  {
    return userData;
  }

  /**
   * Set the user data for this Residue
   * 
   * @param data
   *          the user data to associate with this residue
   */
  public void setUserData(Object data)
  {
    this.userData = data;
  }

  public int compareTo(ChimeraResidue c2)
  {
    if (residueNumber < c2.residueNumber)
      return -1;
    else if (residueNumber == c2.residueNumber)
    {
      if (insertionCode == null && c2.insertionCode == null)
        return 0;
      else if (insertionCode == null)
        return -1;
      else if (c2.insertionCode == null)
        return 1;
      return (insertionCode.compareTo(c2.insertionCode));
    }
    return 1;
  }

  public void splitInsertionCode(String residue)
  {
    // OK, split the index into number and insertion code
    // JBPNote - m.matches() can be true even if there is no resnum - this can
    // cause NumberFormatExceptions below
    Pattern p = Pattern.compile("(\\d*)([A-Z]?)");
    Matcher m = p.matcher(residue);
    if (m.matches())
    {
      this.residueNumber = Integer.parseInt(m.group(1));
      if (m.groupCount() > 1)
        this.insertionCode = m.group(2);
      else
        this.insertionCode = null;
    }
  }

  /**********************************************
   * Static routines
   *********************************************/

  /**
   * Initialize the residue names
   */
  // private static void initNames() {
  // // Create our residue name table
  // aaNames = new HashMap<String, String>();
  // aaNames.put("ALA", "A Ala Alanine N[C@@H](C)C(O)=O");
  // aaNames.put("ARG", "R Arg Arginine N[C@@H](CCCNC(N)=N)C(O)=O");
  // aaNames.put("ASN", "N Asn Asparagine N[C@@H](CC(N)=O)C(O)=O");
  // aaNames.put("ASP", "D Asp Aspartic_acid N[C@@H](CC(O)=O)C(O)=O");
  // aaNames.put("CYS", "C Cys Cysteine N[C@@H](CS)C(O)=O");
  // aaNames.put("GLN", "Q Gln Glutamine N[C@H](C(O)=O)CCC(N)=O");
  // aaNames.put("GLU", "E Glu Glumatic_acid N[C@H](C(O)=O)CCC(O)=O");
  // aaNames.put("GLY", "G Gly Glycine NCC(O)=O");
  // aaNames.put("HIS", "H His Histidine N[C@@H](CC1=CN=CN1)C(O)=O");
  // aaNames.put("ILE", "I Ile Isoleucine N[C@]([C@H](C)CC)([H])C(O)=O");
  // aaNames.put("LEU", "L Leu Leucine N[C@](CC(C)C)([H])C(O)=O");
  // aaNames.put("LYS", "K Lys Lysine N[C@](CCCCN)([H])C(O)=O");
  // aaNames.put("DLY", "K Dly D-Lysine NCCCC[C@@H](N)C(O)=O");
  // aaNames.put("MET", "M Met Methionine N[C@](CCSC)([H])C(O)=O");
  // aaNames.put("PHE", "F Phe Phenylalanine N[C@](CC1=CC=CC=C1)([H])C(O)=O");
  // aaNames.put("PRO", "P Pro Proline OC([C@@]1([H])NCCC1)=O");
  // aaNames.put("SER", "S Ser Serine OC[C@](C(O)=O)([H])N");
  // aaNames.put("THR", "T Thr Threonine O[C@H](C)[C@](C(O)=O)([H])N");
  // aaNames.put("TRP",
  // "W Trp Tryptophan N[C@@]([H])(CC1=CN([H])C2=C1C=CC=C2)C(O)=O");
  // aaNames.put("TYR", "Y Tyr Tyrosine N[C@@](C(O)=O)([H])CC1=CC=C(O)C=C1");
  // aaNames.put("VAL", "V Val Valine N[C@@](C(O)=O)([H])C(C)C");
  // aaNames.put("ASX", "B Asx Aspartic_acid_or_Asparagine");
  // aaNames.put("GLX", "Z Glx Glutamine_or_Glutamic_acid");
  // aaNames.put("XAA", "X Xaa Any_or_unknown_amino_acid");
  // aaNames.put("HOH", "HOH HOH Water [H]O[H]");
  // }

  /**
   * Set the display type.
   * 
   * @param type
   *          the display type
   */
  public static void setDisplayType(int type)
  {
    displayType = type;
  }

  public static int getDisplayType()
  {
    return displayType;
  }

  public boolean hasSelectedChildren()
  {
    return false;
  }
}
