/* 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.awt.Color;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ext.edu.ucsf.rbvi.strucviz2.StructureManager.ModelType;

public abstract class ChimUtils
{

  private static Logger logger = LoggerFactory.getLogger(ChimUtils.class);

  static int MAX_SUB_MODELS = 1000;

  public static final HashMap<String, String> aaNames;

  public static String RESIDUE_ATTR = "ChimeraResidue";

  public static String RINALYZER_ATTR = "RINalyzerResidue";

  public static String DEFAULT_STRUCTURE_KEY = "pdbFileName";

  /**
   * Parse the model number returned by Chimera and return the int value
   */
  // invoked by the ChimeraModel constructor
  // line = model id #0 type Molecule name 1ert
  public static int[] parseModelNumber(String inputLine)
  {
    int hash = inputLine.indexOf('#');
    int space = inputLine.indexOf(' ', hash);
    int decimal = inputLine.substring(hash + 1, space).indexOf('.');
    // model number is between hash+1 and space
    int modelNumber = -1;
    int subModelNumber = 0;
    try
    {
      if (decimal > 0)
      {
        subModelNumber = Integer
                .parseInt(inputLine.substring(decimal + hash + 2, space));
        space = decimal + hash + 1;
      }
      modelNumber = Integer.parseInt(inputLine.substring(hash + 1, space));
    } catch (Exception e)
    {
      logger.warn("Unexpected return from Chimera: " + inputLine, e);
    }
    return new int[] { modelNumber, subModelNumber };
  }

  /**
   * Parse the model number returned by Chimera and return the int value
   */
  // invoked by openModel in ChimeraManager
  // line: #1, chain A: hiv-1 protease
  // line: Model 0 (filename)
  public static int[] parseOpenedModelNumber(String inputLine)
  {
    int hash = inputLine.indexOf('#');
    int space = -1;
    if (hash == (-1))
    {
      hash = inputLine.indexOf("Model");
      if (hash >= 0)
      {
        hash = hash + 5;
      }
      space = inputLine.indexOf(' ', hash + 1);
    }
    else
    {
      space = inputLine.indexOf(',', hash);
    }

    int decimal = inputLine.substring(hash + 1, space).indexOf('.');
    // model number is between hash+1 and space
    int modelNumber = -1;
    int subModelNumber = 0;
    try
    {
      if (decimal > 0)
      {
        subModelNumber = Integer
                .parseInt(inputLine.substring(decimal + hash + 2, space));
        space = decimal + hash + 1;
      }
      modelNumber = Integer.parseInt(inputLine.substring(hash + 1, space));
    } catch (Exception e)
    {
      logger.warn("Unexpected return from Chimera: " + inputLine, e);
    }
    return new int[] { modelNumber, subModelNumber };
  }

  /**
   * Parse the model identifier returned by Chimera and return the String value
   */
  // invoked by the ChimeraModel constructor
  // line = model id #0 type Molecule name 1ert
  public static String parseModelName(String inputLine)
  {
    int start = inputLine.indexOf("name ");
    if (start < 0)
    {
      return null;
    }
    // Might get a quoted string (don't understand why, but there you have it)
    if (inputLine.startsWith("\"", start + 5))
    {
      start += 6; // Skip over the first quote
      int end = inputLine.lastIndexOf('"');
      if (end >= 1)
      {
        return inputLine.substring(start, end);
      }
      else
      {
        return inputLine.substring(start);
      }
    }
    else
    {
      return inputLine.substring(start + 5);
    }
  }

  public static Color parseModelColor(String inputLine)
  {
    try
    {
      int colorStart = inputLine.indexOf("color ");
      String colorString = inputLine.substring(colorStart + 6);
      String[] rgbStrings = colorString.split(",");
      float[] rgbValues = new float[4];
      for (int i = 0; i < rgbStrings.length; i++)
      {
        Float f = Float.valueOf(rgbStrings[i]);
        rgbValues[i] = f.floatValue();
      }
      if (rgbStrings.length == 4)
      {
        return new Color(rgbValues[0], rgbValues[1], rgbValues[2],
                rgbValues[3]);
      }
      else
      {
        return new Color(rgbValues[0], rgbValues[1], rgbValues[2]);
      }
    } catch (Exception ex)
    {
      logger.warn("Unexpected return from Chimera: " + inputLine, ex);
    }
    return Color.white;
  }

  /**
   * Create the key to use for forming the model/submodel key into the modelHash
   * 
   * @param model
   *          the model number
   * @param subModel
   *          the submodel number
   * @return the model key as an Integer
   */
  public static Integer makeModelKey(int model, int subModel)
  {
    return Integer.valueOf(model * MAX_SUB_MODELS + subModel);
  }

  // invoked by the getResdiue (parseConnectivityReplies in
  // CreateStructureNetworkTask)
  // atomSpec = #0:1.A or #1:96.B@N
  public static ChimeraModel getModel(String atomSpec,
          ChimeraManager chimeraManager)
  {
    // System.out.println("getting model for "+atomSpec);
    String[] split = atomSpec.split(":");
    // No model specified....
    if (split[0].length() == 0)
    {
      logger.info("Unexpected return from Chimera: " + atomSpec);
      return null;
    }
    // System.out.println("model = "+split[0].substring(1));
    int model = 0;
    int submodel = 0;
    try
    {
      String[] subSplit = split[0].substring(1).split("\\.");
      if (subSplit.length > 0)
      {
        model = Integer.parseInt(subSplit[0]);
      }
      else
      {
        model = Integer.parseInt(split[0].substring(1));
      }

      if (subSplit.length > 1)
      {
        submodel = Integer.parseInt(subSplit[1]);
      }
    } catch (Exception e)
    {
      // ignore
      logger.warn("Unexpected return from Chimera: " + atomSpec, e);
    }
    return chimeraManager.getChimeraModel(model, submodel);
  }

  // invoked by the parseConnectivityReplies in CreateStructureNetworkTask
  // atomSpec = #0:1.A or #1:96.B@N
  public static ChimeraResidue getResidue(String atomSpec,
          ChimeraManager chimeraManager)
  {
    // System.out.println("Getting residue from: "+atomSpec);
    ChimeraModel model = getModel(atomSpec, chimeraManager); // Get the model
    if (model == null)
    {
      model = chimeraManager.getChimeraModel();
    }
    return getResidue(atomSpec, model);
  }

  // invoked by the getResdiue (parseConnectivityReplies in
  // CreateStructureNetworkTask)
  // atomSpec = #0:1.A or #1:96.B@N
  public static ChimeraResidue getResidue(String atomSpec,
          ChimeraModel model)
  {
    // System.out.println("Getting residue from: "+atomSpec);
    String[] split = atomSpec.split(":|@");

    // Split into residue and chain
    String[] residueChain = split[1].split("\\.");

    if (residueChain[0].length() == 0)
    {
      logger.info("Unexpected return from Chimera: " + atomSpec);
      return null;
    }

    if (residueChain.length == 2 && residueChain[1].length() > 0)
    {
      ChimeraChain chain = model.getChain(residueChain[1]);
      return chain.getResidue(residueChain[0]);
    }
    return model.getResidue("_", residueChain[0]);
  }

  public static ChimeraChain getChain(String atomSpec, ChimeraModel model)
  {
    String[] split = atomSpec.split(":|@");

    // Split into residue and chain
    String[] residueChain = split[1].split("\\.");
    if (residueChain.length == 1)
    {
      logger.info("Unexpected return from Chimera: " + atomSpec);
      return null;
    }
    return model.getChain(residueChain[1]);
  }

  public static String getAtomName(String atomSpec)
  {
    String[] split = atomSpec.split("@");
    if (split.length > 1)
    {
      return split[1];
    }
    return atomSpec;
  }

  public static boolean isBackbone(String atom)
  {
    if (atom.equals("C") || atom.equals("CA") || atom.equals("N")
            || atom.equals("O") || atom.equals("H"))
    {
      return true;
    }
    return false;
  }

  public static String getIntSubtype(String node, String atom)
  {
    String[] split = node.split("#| ");
    String resType = "";
    if (split.length == 2)
    {
      resType = split[0].trim().toUpperCase(Locale.ROOT);
    }
    else if (split.length == 3)
    {
      resType = split[1].trim().toUpperCase(Locale.ROOT);
    }
    if (resType.equalsIgnoreCase("HOH") || resType.equalsIgnoreCase("WAT"))
    {
      return "water";
    }
    else if (aaNames.containsKey(resType))
    {
      if (atom.equals("C") || atom.equals("CA") || atom.equals("N")
              || atom.equals("O") || atom.equals("H"))
      {
        return "mc";
      }
      else
      {
        return "sc";
      }
    }
    else
    {
      return "other";
    }
  }

  public static String[] getResKeyParts(String resKey)
  {
    // [pdbID[.modelNo]#][residueID][.chainID]
    // pdbID := 4-character code | "URL" | "path"
    String[] resKeyParts = new String[4];
    String[] split = resKey.split("#");
    String resChain = null;
    // if no "#" then it is either only a pdb id or a residue or a chain
    if (split.length == 1)
    {
      // pdb id without model
      if (resKey.length() == 4 && resKey.indexOf("\\.") < 0)
      {
        parseModelID(resKey, resKeyParts);
      }
      // pdb link or file
      else if (resKey.startsWith("\""))
      {
        parseModelID(resKey, resKeyParts);
      }
      // chain and residue or model and number
      else
      {
        String[] splitSplit = resKey.split("\\.");
        if (splitSplit.length == 1)
        {
          // only a chain or a residue
          resChain = resKey;
        }
        else
        {
          try
          {
            // pdb with a model
            Integer.parseInt(splitSplit[1]);
            parseModelID(resKey, resKeyParts);
          } catch (NumberFormatException ex)
          {
            // residue and chain
            resChain = resKey;
          }
        }
      }
    }
    else if (split.length == 2)
    {
      // model and residue+chain
      parseModelID(split[0], resKeyParts);
      resChain = split[1];
    }
    else
    {
      // model string with "#"
      // TODO: [Optional] Are there more possibilities?
      parseModelID(resKey.substring(0, resKey.lastIndexOf("#")),
              resKeyParts);
      resChain = resKey.substring(resKey.lastIndexOf("#") + 1,
              resKey.length());
    }
    if (resChain != null)
    {
      // System.out.println(resChain);
      String[] resChainSplit = resChain.split("\\.");
      if (resChainSplit.length == 1)
      {
        // TODO: [Optional] Find a better way to distinguish between chain and
        // residue
        // if only one character and not an int, probably a chain
        if (resChainSplit[0].length() == 1)
        {
          try
          {
            Integer.parseInt(resChainSplit[0]);
            resKeyParts[3] = resChainSplit[0];
          } catch (NumberFormatException ex)
          {
            resKeyParts[2] = resChainSplit[0];
          }
        }
        else
        {
          resKeyParts[3] = resChainSplit[0];
        }
      }
      else if (resChainSplit.length == 2)
      {
        resKeyParts[2] = resChainSplit[0];
        resKeyParts[3] = resChainSplit[1];
      }
      else
      {
        // too many dots?
        logger.info("Could not parse residue identifier: " + resKey);
      }
    }
    // String print = "";
    // for (int i = 0; i < resKeyParts.length; i++) {
    // if (resKeyParts[i] == null) {
    // print += i + ": null\t";
    // } else {
    // print += i + ": " + resKeyParts[i] + ";";
    // }
    // }
    // System.out.println(print);
    return resKeyParts;
  }

  public static void parseModelID(String modelID, String[] resKeyParts)
  {
    if (modelID.startsWith("\""))
    {
      if (modelID.endsWith("\""))
      {
        resKeyParts[0] = modelID.substring(1, modelID.length() - 1);
        return;
      }
      else
      {
        try
        {
          Integer.parseInt(modelID.substring(modelID.lastIndexOf("\"") + 2,
                  modelID.length()));
          resKeyParts[0] = modelID.substring(0,
                  modelID.lastIndexOf("\"") - 1);
          resKeyParts[1] = modelID.substring(modelID.lastIndexOf("\"") + 2,
                  modelID.length());
        } catch (NumberFormatException ex)
        {
          resKeyParts[0] = modelID.substring(1);
        }
      }
    }
    else
    {
      String[] modelIDNo = modelID.split("\\.");
      if (modelIDNo.length == 1)
      {
        resKeyParts[0] = modelIDNo[0];
      }
      else if (modelIDNo.length == 2)
      {
        try
        {
          Integer.parseInt(modelIDNo[1]);
          resKeyParts[0] = modelIDNo[0];
          resKeyParts[1] = modelIDNo[1];
        } catch (NumberFormatException ex)
        {
          resKeyParts[0] = modelID;
        }
      }
      else
      {
        // length > 1, so we probably have a file name with "." in it
        logger.info("Could not parse model identifier: " + modelID);
        resKeyParts[0] = modelID;
      }
    }
  }

  /**
   * This method takes a Cytoscape attribute specification
   * ([structure#][residue][.chainID]) and returns the lowest-level object
   * referenced by the spec. For example, if the spec is "1tkk", this method
   * will return a ChimeraModel. If the spec is ".A", it will return a
   * ChimeraChain, etc.
   * 
   * @param attrSpec
   *          the specification string
   * @param chimeraManager
   *          the Chimera object we're currently using
   * @return a ChimeraStructuralObject of the lowest type
   */
  public static ChimeraStructuralObject fromAttributeOld(String attrSpec,
          ChimeraManager chimeraManager)
  {
    if (attrSpec == null || attrSpec.indexOf(',') > 0
            || attrSpec.indexOf('-') > 0)
    {
      // No support for either lists or ranges
      logger.warn("No support for identifier: " + attrSpec);
      return null;
    }

    String residue = null;
    String model = null;
    String chain = null;

    ChimeraModel chimeraModel = null;
    ChimeraChain chimeraChain = null;
    ChimeraResidue chimeraResidue = null;

    // System.out.println("Getting object from attribute: "+attrSpec);
    try
    {
      String[] split = attrSpec.split("#");
      String resChain = null;
      if (split.length == 1)
      {
        // no model
        resChain = split[0];
      }
      else if (split.length == 2)
      {
        // model and rest
        model = split[0];
        resChain = split[1];
      }
      else
      {
        // model string with "#"
        model = attrSpec.substring(0, attrSpec.lastIndexOf("#"));
        resChain = attrSpec.substring(attrSpec.lastIndexOf("#") + 1,
                attrSpec.length());
      }
      if (resChain != null)
      {
        String[] resChainSplit = resChain.split("\\.");
        if (resChainSplit.length == 1)
        {
          residue = resChainSplit[0];
        }
        else if (resChainSplit.length == 2)
        {
          residue = resChainSplit[0];
          chain = resChainSplit[1];
        }
        else
        {
          // too many dots?
          logger.warn("No support for identifier: " + attrSpec);
        }
      }

      // if (split.length == 1) {
      // // No model
      // residue = split[0];
      // } else if (split.length == 3) {
      // // We have all three
      // model = split[0];
      // residue = split[1];
      // chain = split[2];
      // } else if (split.length == 2 && attrSpec.indexOf('#') > 0) {
      // // Model and Residue
      // model = split[0];
      // residue = split[1];
      // } else {
      // // Residue and Chain
      // residue = split[0];
      // chain = split[1];
      // }

      // System.out.println("model = " + model + " chain = " + chain +
      // " residue = " + residue);
      if (model != null)
      {
        List<ChimeraModel> models = chimeraManager.getChimeraModels(model,
                ModelType.PDB_MODEL);
        if (models.size() == 1)
        {
          chimeraModel = models.get(0);
        }
        else
        {
          try
          {
            chimeraModel = chimeraManager
                    .getChimeraModel(Integer.valueOf(model), 0);
          } catch (NumberFormatException ex)
          {
            // ignore
          }
        }
      }
      if (chimeraModel == null)
      {
        chimeraModel = chimeraManager.getChimeraModel();
      }
      // System.out.println("ChimeraModel = " + chimeraModel);

      if (chain != null)
      {
        chimeraChain = chimeraModel.getChain(chain);
        // System.out.println("ChimeraChain = " + chimeraChain);
      }
      if (residue != null)
      {
        if (chimeraChain != null)
        {
          chimeraResidue = chimeraChain.getResidue(residue);
        }
        else
        {
          chimeraResidue = chimeraModel.getResidue("_", residue);
        }
        // System.out.println("ChimeraResidue = " + chimeraResidue);
      }

      if (chimeraResidue != null)
      {
        return chimeraResidue;
      }

      if (chimeraChain != null)
      {
        return chimeraChain;
      }

      if (chimeraModel != null)
      {
        return chimeraModel;
      }

    } catch (Exception ex)
    {
      logger.warn("Could not parse residue identifier: " + attrSpec, ex);
    }
    return null;
  }

  public static ChimeraStructuralObject fromAttribute(String attrSpec,
          ChimeraManager chimeraManager)
  {
    // TODO: Make sure it is OK to remove this: || attrSpec.indexOf('-') > 0
    if (attrSpec == null || attrSpec.indexOf(',') > 0)
    {
      // No support for either lists or ranges
      // System.out.println("No support for identifier: " + attrSpec);
      logger.warn("No support for identifier: " + attrSpec);
      return null;
    }
    String[] modelIDNoResChain = getResKeyParts(attrSpec);

    ChimeraModel chimeraModel = null;
    ChimeraChain chimeraChain = null;
    ChimeraResidue chimeraResidue = null;

    // System.out.println("Getting object from attribute: "+attrSpec);
    try
    {
      if (modelIDNoResChain[0] != null)
      {
        String modelID = modelIDNoResChain[0];
        List<ChimeraModel> models = chimeraManager.getChimeraModels(modelID,
                ModelType.PDB_MODEL);
        if (models.size() == 1)
        { // usual case with only one model
          chimeraModel = models.get(0);
        }
        else if (models.size() > 1 && modelIDNoResChain[1] != null)
        {
          // there are several submodels
          try
          {
            int modelNo = Integer.valueOf(modelIDNoResChain[1]);
            for (ChimeraModel model : models)
            {
              if (model.getSubModelNumber() == modelNo)
              {
                chimeraModel = model;
                break;
              }
            }
          } catch (NumberFormatException ex)
          {
            // ignore
          }
        }
        else
        {
          // TODO: [Optional] What is this doing?
          try
          {
            chimeraModel = chimeraManager
                    .getChimeraModel(Integer.valueOf(modelID), 0);
          } catch (NumberFormatException ex)
          {
            // ignore
          }
        }
      }
      if (chimeraModel == null)
      {
        // TODO: [Optional] Find a better way to handle this case
        // If no model can be matched, continue
        // System.out.println("No matching model could be find for " +
        // attrSpec);
        return null;
        // chimeraModel = chimeraManager.getChimeraModel();
        // logger.warn("No matching model could be find for " + attrSpec +
        // ". Trying with "
        // + chimeraModel.toSpec());
      }
      // System.out.println("ChimeraModel = " + chimeraModel);

      if (modelIDNoResChain[3] != null)
      {
        chimeraChain = chimeraModel.getChain(modelIDNoResChain[3]);
        // System.out.println("ChimeraChain = " + chimeraChain);
      }
      if (modelIDNoResChain[2] != null)
      {
        String residue = modelIDNoResChain[2];
        if (chimeraChain != null)
        {
          chimeraResidue = chimeraChain.getResidue(residue);
        }
        else if (chimeraModel.getChain("_") != null)
        {
          chimeraResidue = chimeraModel.getResidue("_", residue);
        }
        else if (chimeraModel.getChainCount() == 1)
        {
          chimeraResidue = chimeraModel.getResidue(
                  chimeraModel.getChainNames().iterator().next(), residue);
        }
        // System.out.println("ChimeraResidue = " + chimeraResidue);
      }

      if (chimeraResidue != null)
      {
        return chimeraResidue;
      }

      if (chimeraChain != null)
      {
        return chimeraChain;
      }

      if (chimeraModel != null)
      {
        return chimeraModel;
      }

    } catch (Exception ex)
    {
      // System.out.println("Could not parse chimera identifier: " +
      // attrSpec+"("+ex.getMessage()+")");
      logger.warn("Could not parse chimera identifier: " + attrSpec, ex);
    }
    return null;
  }

  /**
   * Search for structure references in the residue list
   * 
   * @param residueList
   *          the list of residues
   * @return a concatenated list of structures encoded in the list
   */
  public static String findStructures(String residueList)
  {
    if (residueList == null)
    {
      return null;
    }
    String[] residues = residueList.split(",");
    Map<String, String> structureNameMap = new HashMap<String, String>();
    for (int i = 0; i < residues.length; i++)
    {
      String[] components = residues[i].split("#");
      if (components.length > 1)
      {
        structureNameMap.put(components[0], components[1]);
      }
    }
    if (structureNameMap.isEmpty())
    {
      return null;
    }

    String structure = null;
    for (String struct : structureNameMap.keySet())
    {
      if (structure == null)
      {
        structure = new String();
      }
      else
      {
        structure = structure.concat(",");
      }
      structure = structure.concat(struct);
    }
    return structure;
  }

  // invoked by openStructures in StructureManager
  public static List<String> parseFuncRes(List<String> residueNames,
          String modelName)
  {
    List<String> resRanges = new ArrayList<String>();
    for (int i = 0; i < residueNames.size(); i++)
    {
      String residue = residueNames.get(i);
      // Parse out the structure, if there is one
      String[] components = residue.split("#");
      if (components.length > 1 && !modelName.equals(components[0]))
      {
        continue;
      }
      else if (components.length > 1)
      {
        residue = components[1];
      }
      else if (components.length == 1)
      {
        residue = components[0];
      }
      // Check to see if we have a range-spec
      String resRange = "";
      if (residue == null || residue.equals("") || residue.length() == 0)
      {
        continue;
      }
      String[] range = residue.split("-", 2);
      String chain = null;
      for (int res = 0; res < range.length; res++)
      {
        if (res == 1)
        {
          resRange = resRange.concat("-");
          if (chain != null && range[res].indexOf('.') == -1)
          {
            range[res] = range[res].concat("." + chain);
          }
        }

        if (res == 0 && range.length >= 2 && range[res].indexOf('.') > 0)
        {
          // This is a range spec with the leading residue containing a chain
          // spec
          String[] resChain = range[res].split("\\.");
          chain = resChain[1];
          range[res] = resChain[0];
        }
        // Fix weird SFLD syntax...
        if (range[res].indexOf('|') > 0
                && Character.isDigit(range[res].charAt(0)))
        {
          int offset = range[res].indexOf('|');
          String str = range[res].substring(offset + 1)
                  + range[res].substring(0, offset);
          range[res] = str;
        }

        // Convert to legal atom-spec
        if (Character.isDigit(range[res].charAt(0)))
        {
          resRange = resRange.concat(range[res]);
        }
        else if (Character.isDigit(range[res].charAt(1)))
        {
          resRange = resRange.concat(range[res].substring(1));
        }
        else if (range[res].charAt(0) == '.')
        {
          // Do we have a chain spec?
          resRange = resRange.concat(range[res]);
        }
        else
        {
          resRange = resRange.concat(range[res].substring(3));
        }
      }
      if (!resRanges.contains(resRange))
      {
        resRanges.add(resRange);
      }
    }
    return resRanges;
  }

  static
  {
    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]");
  }

  /**
   * Convert the amino acid type to a full name
   * 
   * @param aaType
   *          the residue type to convert
   * @return the full name of the residue
   */
  public static String toFullName(String aaType)
  {
    if (!aaNames.containsKey(aaType))
    {
      return aaType;
    }
    String[] ids = aaNames.get(aaType).split(" ");
    return ids[2].replace('_', ' ');
  }

  /**
   * Convert the amino acid type to a single letter
   * 
   * @param aaType
   *          the residue type to convert
   * @return the single letter representation of the residue
   */
  public static String toSingleLetter(String aaType)
  {
    if (!aaNames.containsKey(aaType))
    {
      return aaType;
    }
    String[] ids = aaNames.get(aaType).split(" ");
    return ids[0];
  }

  /**
   * Convert the amino acid type to three letters
   * 
   * @param aaType
   *          the residue type to convert
   * @return the three letter representation of the residue
   */
  public static String toThreeLetter(String aaType)
  {
    if (!aaNames.containsKey(aaType))
    {
      return aaType;
    }
    String[] ids = aaNames.get(aaType).split(" ");
    return ids[1];
  }

  /**
   * Convert the amino acid type to its SMILES string
   * 
   * @param aaType
   *          the residue type to convert
   * @return the SMILES representation of the residue
   */
  public static String toSMILES(String aaType)
  {
    if (!aaNames.containsKey(aaType))
    {
      return null;
    }
    String[] ids = aaNames.get(aaType).split(" ");
    if (ids.length < 4)
    {
      return null;
    }
    return ids[3];
  }

  public static String getAlignName(ChimeraStructuralObject chimObj)
  {
    String name = chimObj.getChimeraModel().toString();
    if (chimObj instanceof ChimeraChain)
    {
      name = ((ChimeraChain) chimObj).toString() + " [" + name + "]";
    }
    return name;
  }
}
