/**********************************************************************
 *  Copyright (c) 2008-2013, Alliance for Sustainable Energy.
 *  All rights reserved.
 *
 *  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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 **********************************************************************/

#include <analysis/UncertaintyDescription.hpp>
#include <analysis/UncertaintyDescription_Impl.hpp>

#include <analysis/GenericUncertaintyDescription.hpp>

#include <utilities/core/Assert.hpp>
#include <utilities/core/Finder.hpp>
#include <utilities/core/Json.hpp>

#include <boost/foreach.hpp>
#include <boost/bind.hpp>

namespace openstudio {
namespace analysis {

namespace detail {

  UncertaintyDescription_Impl::UncertaintyDescription_Impl(const UncertaintyDescriptionType& type)
    : m_type(type)
  {
    OS_ASSERT(this->type() != UncertaintyDescriptionType::Generic);
    populateAttributeDescriptions();
    restoreDefaults();
  }

  UncertaintyDescription_Impl::UncertaintyDescription_Impl(const UncertaintyDescriptionType& type,
                                                           const std::vector<Attribute>& attributes)
    : m_type(type),
      m_attributes(attributes)
  {
    OS_ASSERT(this->type() != UncertaintyDescriptionType::Generic);
    populateAttributeDescriptions();
  }

  UncertaintyDescription_Impl::UncertaintyDescription_Impl(const UncertaintyDescription_Impl& other)
    : m_type(other.type()),
      m_attributes(other.attributes(true)),
      m_attributeDescriptions(other.attributeDescriptions())
  {
    OS_ASSERT(type() != UncertaintyDescriptionType::Generic);
  }

  UncertaintyDescriptionType UncertaintyDescription_Impl::type() const {
    return m_type;
  }

  std::vector<Attribute> UncertaintyDescription_Impl::attributes(bool clones) const {
    if (clones) {
      AttributeVector result;
      BOOST_FOREACH(const Attribute& attribute,m_attributes) {
        result.push_back(attribute.clone());
      }
      return result;
    }
    return m_attributes;
  }

  Attribute UncertaintyDescription_Impl::getAttribute(const std::string& attributeName, bool clone) const {
    OptionalAttribute result = findByName<Attribute>(m_attributes,attributeName,true);
    OS_ASSERT(result);
    if (clone) {
      return result->clone();
    }
    return result.get();
  }

  bool UncertaintyDescription_Impl::isSet(const std::string& attributeName) const {
    return findByName<Attribute>(m_attributes,attributeName,true);
  }

  std::vector<AttributeDescription> UncertaintyDescription_Impl::attributeDescriptions() const {
    return m_attributeDescriptions;
  }

  AttributeDescription UncertaintyDescription_Impl::getAttributeDescription(
    const std::string& attributeName) const
  {
    OptionalAttributeDescription result = findStructByName<AttributeDescription>(m_attributeDescriptions,
                                                                                attributeName,
                                                                                true);
    OS_ASSERT(result);
    return result.get();
  }

  bool UncertaintyDescription_Impl::hasDescription(const std::string& attributeName) const {
    return findStructByName<AttributeDescription>(m_attributeDescriptions,attributeName,true);
  }

  bool UncertaintyDescription_Impl::isComplete() const {
    BOOST_FOREACH(const AttributeDescription& desc,attributeDescriptions()) {
      if (desc.required && !isSet(desc.name)) {
        return false;
      }
    }
    return true;
  }

  bool UncertaintyDescription_Impl::setAttribute(const Attribute& candidate, bool check) {
    if (check) {
      if (!hasDescription(candidate.name()) ||
          !isConsistent(candidate,getAttributeDescription(candidate.name())))
      {
        return false;
      }
    }
    if (isSet(candidate.name())) {
      Attribute myCopy = getAttribute(candidate.name(),false);
      if (myCopy.valueType() == AttributeValueType::AttributeVector) {
        myCopy.setValue(candidate.valueAsAttributeVector());
      }
      else {
        myCopy.setValue(candidate.valueAsQVariant());
      }
    }
    else {
      m_attributes.push_back(candidate.clone());
      bool ok = prepareForDisplay(m_attributes.back(),getAttributeDescription(candidate.name()));
      OS_ASSERT(ok);
    }
    return true;
  }

  bool UncertaintyDescription_Impl::clearAttribute(const std::string& attributeName, bool check) {
    if (check) {
      if (!hasDescription(attributeName)) {
        return false;
      }
      AttributeDescription desc = getAttributeDescription(attributeName);
      if (desc.required) {
        return false;
      }
    }
    AttributeVector::iterator it = findIteratorByName<Attribute>(m_attributes,attributeName,true);
    if (it == m_attributes.end()) {
      return false;
    }
    m_attributes.erase(it);
    return true;
  }

  void UncertaintyDescription_Impl::restoreDefaults() {
    m_attributes.clear();
    std::vector<double> vectorValue(2);
    switch (type().value()) {
      case UncertaintyDescriptionType::normal_uncertain :
        setAttribute(Attribute("means",0.0),false);
        setAttribute(Attribute("std_deviations",1.0),false);
       break;
      case UncertaintyDescriptionType::lognormal_uncertain :
        setAttribute(Attribute("means",1.0),false);
        setAttribute(Attribute("std_deviations",1.0),false);
       break;
      case UncertaintyDescriptionType::uniform_uncertain :
        setAttribute(Attribute("lower_bounds",0.0),false);
        setAttribute(Attribute("upper_bounds",1.0),false);
       break;
      case UncertaintyDescriptionType::loguniform_uncertain :
        setAttribute(Attribute("lower_bounds",0.1),false);
        setAttribute(Attribute("upper_bounds",1.0),false);
       break;
      case UncertaintyDescriptionType::triangular_uncertain :
        setAttribute(Attribute("modes",0.5),false);
        setAttribute(Attribute("lower_bounds",0.0),false);
        setAttribute(Attribute("upper_bounds",1.0),false);
       break;
      case UncertaintyDescriptionType::exponential_uncertain :
        setAttribute(Attribute("betas",1.0),false);
       break;
      case UncertaintyDescriptionType::beta_uncertain :
        setAttribute(Attribute("alphas",2.0),false);
        setAttribute(Attribute("betas",2.0),false);
        setAttribute(Attribute("lower_bounds",0.0),false);
        setAttribute(Attribute("upper_bounds",1.0),false);
       break;
      case UncertaintyDescriptionType::gamma_uncertain :
        setAttribute(Attribute("alphas",2.0),false);
        setAttribute(Attribute("betas",0.5),false);
       break;
      case UncertaintyDescriptionType::gumbel_uncertain :
        setAttribute(Attribute("alphas",1.0),false);
        setAttribute(Attribute("betas",2.0),false);
       break;
      case UncertaintyDescriptionType::frechet_uncertain :
        setAttribute(Attribute("alphas",2.0),false);
        setAttribute(Attribute("betas",1.0),false);
       break;
      case UncertaintyDescriptionType::weibull_uncertain :
        setAttribute(Attribute("alphas",1.0),false);
        setAttribute(Attribute("betas",1.0),false);
       break;
      case UncertaintyDescriptionType::histogram_bin_uncertain :
        vectorValue[0] = 0.0;
        vectorValue[1] = 1.0;
        setAttribute(createAttributeFromVector("abscissas",vectorValue),false);
        vectorValue[0] = 1.0;
        vectorValue[1] = 0.0;
        setAttribute(createAttributeFromVector("counts",vectorValue),false);
       break;
      case UncertaintyDescriptionType::poisson_uncertain :
        setAttribute(Attribute("lambdas",1.0),false);
       break;
      case UncertaintyDescriptionType::binomial_uncertain :
        setAttribute(Attribute("prob_per_trial",0.5),false);
        setAttribute(Attribute("num_trials",20),false);
       break;
      case UncertaintyDescriptionType::negative_binomial_uncertain :
        setAttribute(Attribute("prob_per_trial",0.5),false);
        setAttribute(Attribute("num_trials",20),false);
       break;
      case UncertaintyDescriptionType::geometric_uncertain :
        setAttribute(Attribute("prob_per_trial",0.5),false);
       break;
      case UncertaintyDescriptionType::hypergeometric_uncertain :
        setAttribute(Attribute("total_population",20),false);
        setAttribute(Attribute("selected_population",10),false);
        setAttribute(Attribute("num_drawn",10),false);
       break;
      case UncertaintyDescriptionType::histogram_point_uncertain :
        vectorValue[0] = 0.0;
        vectorValue[1] = 1.0;
        setAttribute(createAttributeFromVector("abscissas",vectorValue),false);
        vectorValue[0] = 1.0;
        vectorValue[1] = 1.0;
        setAttribute(createAttributeFromVector("counts",vectorValue),false);
       break;
      default :
        OS_ASSERT(false);
    }
  }

  void UncertaintyDescription_Impl::populateAttributeDescriptions() {
    switch (type().value()) {
      case UncertaintyDescriptionType::normal_uncertain :
        m_attributeDescriptions.push_back(AttributeDescription("means",
                                                               "Mean",
                                                               "The mean value of this (optionally bounded) normal distribution",
                                                               AttributeValueType(AttributeValueType::Double),
                                                               true));
        m_attributeDescriptions.push_back(AttributeDescription("std_deviations",
                                                               "Standard Deviation",
                                                               "The standard deviation of this (optionally bounded) normal distribution",
                                                               AttributeValueType(AttributeValueType::Double),
                                                               true));
        m_attributeDescriptions.push_back(AttributeDescription("lower_bounds",
                                                               "Lower Bound",
                                                               "The lower bound of the region from which samples are drawn",
                                                               AttributeValueType(AttributeValueType::Double),
                                                               false,
                                                               QVariant(QString("-DBL_MAX"))));
        m_attributeDescriptions.push_back(AttributeDescription("upper_bounds",
                                                               "Upper Bound",
                                                               "The upper bound of the region from which samples are drawn",
                                                               AttributeValueType(AttributeValueType::Double),
                                                               false,
                                                               QVariant(QString("+DBL_MAX"))));
       break;
       case UncertaintyDescriptionType::lognormal_uncertain :
        m_attributeDescriptions.push_back(AttributeDescription("means",
                                                               "Mean",
                                                               "The mean value of this (optionally bounded) lognormal distribution",
                                                               AttributeValueType(AttributeValueType::Double),
                                                               false));
        m_attributeDescriptions.push_back(AttributeDescription("std_deviations",
                                                               "Standard Deviation",
                                                               "The standard deviation of this (optionally bounded) lognormal distribution",
                                                               AttributeValueType(AttributeValueType::Double),
                                                               false));
        m_attributeDescriptions.push_back(AttributeDescription("error_factors",
                                                               "Error Factor",
                                                               "The error factor of this (optionally bounded) lognormal distribution",
                                                               AttributeValueType(AttributeValueType::Double),
                                                               false));
        m_attributeDescriptions.push_back(AttributeDescription("lambdas",
                                                               "Lambda",
                                                               "The mean value (lambda) of the underlying normal distribution",
                                                               AttributeValueType(AttributeValueType::Double),
                                                               false));
        m_attributeDescriptions.push_back(AttributeDescription("zetas",
                                                               "Zeta",
                                                               "The standard deviation (zeta) of the underlying normal distribution",
                                                               AttributeValueType(AttributeValueType::Double),
                                                               false));
        m_attributeDescriptions.push_back(AttributeDescription("lower_bounds",
                                                               "Lower Bound",
                                                               "The lower bound of the region from which samples are drawn",
                                                               AttributeValueType(AttributeValueType::Double),
                                                               false,
                                                               QVariant(QString("0"))));
        m_attributeDescriptions.push_back(AttributeDescription("upper_bounds",
                                                               "Upper Bound",
                                                               "The upper bound of the region from which samples are drawn",
                                                               AttributeValueType(AttributeValueType::Double),
                                                               false,
                                                               QVariant(QString("+DBL_MAX"))));
       break;
       case UncertaintyDescriptionType::uniform_uncertain :
        m_attributeDescriptions.push_back(AttributeDescription("lower_bounds",
                                                               "Lower Bound",
                                                               "The lower bound of the sampling region",
                                                               AttributeValueType(AttributeValueType::Double),
                                                               true));
        m_attributeDescriptions.push_back(AttributeDescription("upper_bounds",
                                                               "Upper Bound",
                                                               "The upper bound of the sampling region",
                                                               AttributeValueType(AttributeValueType::Double),
                                                               true));
       break;
       case UncertaintyDescriptionType::loguniform_uncertain :
        m_attributeDescriptions.push_back(AttributeDescription("lower_bounds",
                                                               "Lower Bound",
                                                               "The lower bound of the sampling region",
                                                               AttributeValueType(AttributeValueType::Double),
                                                               true));
        m_attributeDescriptions.push_back(AttributeDescription("upper_bounds",
                                                               "Upper Bound",
                                                               "The upper bound of the sampling region",
                                                               AttributeValueType(AttributeValueType::Double),
                                                               true));
       break;
       case UncertaintyDescriptionType::triangular_uncertain :
        m_attributeDescriptions.push_back(AttributeDescription("modes",
                                                               "Mode",
                                                               "The mode of the distribution",
                                                               AttributeValueType(AttributeValueType::Double),
                                                               true));
        m_attributeDescriptions.push_back(AttributeDescription("lower_bounds",
                                                               "Lower Bound",
                                                               "The lower bound of the sampling region",
                                                               AttributeValueType(AttributeValueType::Double),
                                                               true));
        m_attributeDescriptions.push_back(AttributeDescription("upper_bounds",
                                                               "Upper Bound",
                                                               "The upper bound of the sampling region",
                                                               AttributeValueType(AttributeValueType::Double),
                                                               true));
       break;
       case UncertaintyDescriptionType::exponential_uncertain :
        m_attributeDescriptions.push_back(AttributeDescription("betas",
                                                               "Beta",
                                                               "The beta parameter of the distribution",
                                                               AttributeValueType(AttributeValueType::Double),
                                                               true));
       break;
       case UncertaintyDescriptionType::beta_uncertain :
        m_attributeDescriptions.push_back(AttributeDescription("alphas",
                                                               "Alpha",
                                                               "The alpha parameter of the distribution",
                                                               AttributeValueType(AttributeValueType::Double),
                                                               true));
        m_attributeDescriptions.push_back(AttributeDescription("betas",
                                                               "Beta",
                                                               "The beta parameter of the distribution",
                                                               AttributeValueType(AttributeValueType::Double),
                                                               true));
        m_attributeDescriptions.push_back(AttributeDescription("lower_bounds",
                                                               "Lower Bound",
                                                               "The lower bound of the sampling region",
                                                               AttributeValueType(AttributeValueType::Double),
                                                               true));
        m_attributeDescriptions.push_back(AttributeDescription("upper_bounds",
                                                               "Upper Bound",
                                                               "The upper bound of the sampling region",
                                                               AttributeValueType(AttributeValueType::Double),
                                                               true));
       break;
       case UncertaintyDescriptionType::gamma_uncertain :
        m_attributeDescriptions.push_back(AttributeDescription("alphas",
                                                               "Alpha",
                                                               "The alpha parameter of the distribution",
                                                               AttributeValueType(AttributeValueType::Double),
                                                               true));
        m_attributeDescriptions.push_back(AttributeDescription("betas",
                                                               "Beta",
                                                               "The beta parameter of the distribution",
                                                               AttributeValueType(AttributeValueType::Double),
                                                               true));
       break;
       case UncertaintyDescriptionType::gumbel_uncertain :
        m_attributeDescriptions.push_back(AttributeDescription("alphas",
                                                               "Alpha",
                                                               "The alpha parameter of the distribution",
                                                               AttributeValueType(AttributeValueType::Double),
                                                               true));
        m_attributeDescriptions.push_back(AttributeDescription("betas",
                                                               "Beta",
                                                               "The beta parameter of the distribution",
                                                               AttributeValueType(AttributeValueType::Double),
                                                               true));
       break;
       case UncertaintyDescriptionType::frechet_uncertain :
        m_attributeDescriptions.push_back(AttributeDescription("alphas",
                                                               "Alpha",
                                                               "The alpha parameter of the distribution",
                                                               AttributeValueType(AttributeValueType::Double),
                                                               true));
        m_attributeDescriptions.push_back(AttributeDescription("betas",
                                                               "Beta",
                                                               "The beta parameter of the distribution",
                                                               AttributeValueType(AttributeValueType::Double),
                                                               true));
       break;
       case UncertaintyDescriptionType::weibull_uncertain :
        m_attributeDescriptions.push_back(AttributeDescription("alphas",
                                                               "Alpha",
                                                               "The alpha parameter of the distribution",
                                                               AttributeValueType(AttributeValueType::Double),
                                                               true));
        m_attributeDescriptions.push_back(AttributeDescription("betas",
                                                               "Beta",
                                                               "The beta parameter of the distribution",
                                                               AttributeValueType(AttributeValueType::Double),
                                                               true));
       break;
       case UncertaintyDescriptionType::histogram_bin_uncertain :
        m_attributeDescriptions.push_back(AttributeDescription("abscissas",
                                                               "Abscissas",
                                                               "The abscissa values ('x' coordinates) that define the histogram bins",
                                                               AttributeValueType(AttributeValueType::AttributeVector), // of type double
                                                               true));
        m_attributeDescriptions.push_back(AttributeDescription("counts",
                                                               "Counts",
                                                               "The count or frequency associated with a bin",
                                                               AttributeValueType(AttributeValueType::AttributeVector), // of type double
                                                               false));
        m_attributeDescriptions.push_back(AttributeDescription("ordinates",
                                                               "Ordinates",
                                                               "The 'y' coordinates associated with the abscissa",
                                                               AttributeValueType(AttributeValueType::AttributeVector), // of type double
                                                               false));
        m_attributeDescriptions.push_back(AttributeDescription("num_pairs",
                                                               "Number of Pairs",
                                                               "The number of pairs of (x,counts) or (x,ordinates) that have been defined",
                                                               AttributeValueType(AttributeValueType::Integer),
                                                               false));
       break;
       case UncertaintyDescriptionType::poisson_uncertain :
        m_attributeDescriptions.push_back(AttributeDescription("lambdas",
                                                               "Lambda",
                                                               "The number of expected occurances in the time interval",
                                                               AttributeValueType(AttributeValueType::Double),
                                                               true));
       break;
       case UncertaintyDescriptionType::binomial_uncertain :
        m_attributeDescriptions.push_back(AttributeDescription("prob_per_trial",
                                                               "Probability Per Trial",
                                                               "The probability of failure per trial",
                                                               AttributeValueType(AttributeValueType::Double),
                                                               true));
        m_attributeDescriptions.push_back(AttributeDescription("num_trials",
                                                               "Number of Trials",
                                                               "The number of trials",
                                                               AttributeValueType(AttributeValueType::Integer),
                                                               true));
       break;
       case UncertaintyDescriptionType::negative_binomial_uncertain :
        m_attributeDescriptions.push_back(AttributeDescription("prob_per_trial",
                                                               "Probability Per Trial",
                                                               "The probability of success per trial",
                                                               AttributeValueType(AttributeValueType::Double),
                                                               true));
        m_attributeDescriptions.push_back(AttributeDescription("num_trials",
                                                               "Number of Trials",
                                                               "The number of trials",
                                                               AttributeValueType(AttributeValueType::Integer),
                                                               true));
       break;
       case UncertaintyDescriptionType::geometric_uncertain :
        m_attributeDescriptions.push_back(AttributeDescription("prob_per_trial",
                                                               "Probability Per Trial",
                                                               "The probability of failure per trial",
                                                               AttributeValueType(AttributeValueType::Double),
                                                               true));
       break;
       case UncertaintyDescriptionType::hypergeometric_uncertain :
        m_attributeDescriptions.push_back(AttributeDescription("total_population",
                                                               "Total Population",
                                                               "The total population from which samples are drawn",
                                                               AttributeValueType(AttributeValueType::Integer),
                                                               true));
        m_attributeDescriptions.push_back(AttributeDescription("selected_population",
                                                               "Selected Population",
                                                               "The number of items in the selected population (e.g., the number of white balls)",
                                                               AttributeValueType(AttributeValueType::Integer),
                                                               true));
        m_attributeDescriptions.push_back(AttributeDescription("num_drawn",
                                                               "Number Drawn",
                                                               "The number of items (e.g., balls) drawn",
                                                               AttributeValueType(AttributeValueType::Integer),
                                                               true));
       break;
       case UncertaintyDescriptionType::histogram_point_uncertain :
        m_attributeDescriptions.push_back(AttributeDescription("abscissas",
                                                               "Abscissas",
                                                               "The abscissa values ('x' coordinates) that define the histogram bins",
                                                               AttributeValueType(AttributeValueType::AttributeVector), // of type double
                                                               true));
        m_attributeDescriptions.push_back(AttributeDescription("counts",
                                                               "Counts",
                                                               "The count or frequency associated with a bin",
                                                               AttributeValueType(AttributeValueType::AttributeVector), // of type double
                                                               true));
        m_attributeDescriptions.push_back(AttributeDescription("num_pairs",
                                                               "Number of Pairs",
                                                               "The number of pairs of (x,counts) or (x,ordinates) that have been defined",
                                                               AttributeValueType(AttributeValueType::Integer),
                                                               false));
       break;
      default :
        OS_ASSERT(false);
    }
  }

} // detail

UncertaintyDescription UncertaintyDescription::clone() const {
  boost::shared_ptr<detail::UncertaintyDescription_Impl> cloneImpl(
      new detail::UncertaintyDescription_Impl(*impl()));
  return UncertaintyDescription(cloneImpl);
}

std::vector<UncertaintyDescriptionType> UncertaintyDescription::validTypes(
  const VariableValueType& variableValueType)
{
  UncertaintyDescriptionTypeVector result;
  if (variableValueType == VariableValueType::Continuous) {
    result.push_back(UncertaintyDescriptionType(UncertaintyDescriptionType::normal_uncertain));
    result.push_back(UncertaintyDescriptionType(UncertaintyDescriptionType::lognormal_uncertain));
    result.push_back(UncertaintyDescriptionType(UncertaintyDescriptionType::uniform_uncertain));
    result.push_back(UncertaintyDescriptionType(UncertaintyDescriptionType::loguniform_uncertain));
    result.push_back(UncertaintyDescriptionType(UncertaintyDescriptionType::triangular_uncertain));
    result.push_back(UncertaintyDescriptionType(UncertaintyDescriptionType::exponential_uncertain));
    result.push_back(UncertaintyDescriptionType(UncertaintyDescriptionType::beta_uncertain));
    result.push_back(UncertaintyDescriptionType(UncertaintyDescriptionType::gamma_uncertain));
    result.push_back(UncertaintyDescriptionType(UncertaintyDescriptionType::gumbel_uncertain));
    result.push_back(UncertaintyDescriptionType(UncertaintyDescriptionType::frechet_uncertain));
    result.push_back(UncertaintyDescriptionType(UncertaintyDescriptionType::weibull_uncertain));
    result.push_back(UncertaintyDescriptionType(UncertaintyDescriptionType::histogram_bin_uncertain));
  }
  else if (variableValueType == VariableValueType::Discrete) {
    result.push_back(UncertaintyDescriptionType(UncertaintyDescriptionType::poisson_uncertain));
    result.push_back(UncertaintyDescriptionType(UncertaintyDescriptionType::binomial_uncertain));
    result.push_back(UncertaintyDescriptionType(UncertaintyDescriptionType::negative_binomial_uncertain));
    result.push_back(UncertaintyDescriptionType(UncertaintyDescriptionType::geometric_uncertain));
    result.push_back(UncertaintyDescriptionType(UncertaintyDescriptionType::hypergeometric_uncertain));
    result.push_back(UncertaintyDescriptionType(UncertaintyDescriptionType::histogram_point_uncertain));
  }
  return result;
}

std::vector<UncertaintyDescriptionType> UncertaintyDescription::validTypes(
  const VariableValueType& variableValueType,
  const UncertaintyType& uncertaintyType)
{
  UncertaintyDescriptionTypeVector result;
  if (uncertaintyType == UncertaintyType::Aleatory) {
    if (variableValueType == VariableValueType::Continuous) {
      result.push_back(UncertaintyDescriptionType(UncertaintyDescriptionType::normal_uncertain));
      result.push_back(UncertaintyDescriptionType(UncertaintyDescriptionType::lognormal_uncertain));
      result.push_back(UncertaintyDescriptionType(UncertaintyDescriptionType::uniform_uncertain));
      result.push_back(UncertaintyDescriptionType(UncertaintyDescriptionType::loguniform_uncertain));
      result.push_back(UncertaintyDescriptionType(UncertaintyDescriptionType::triangular_uncertain));
      result.push_back(UncertaintyDescriptionType(UncertaintyDescriptionType::exponential_uncertain));
      result.push_back(UncertaintyDescriptionType(UncertaintyDescriptionType::beta_uncertain));
      result.push_back(UncertaintyDescriptionType(UncertaintyDescriptionType::gamma_uncertain));
      result.push_back(UncertaintyDescriptionType(UncertaintyDescriptionType::gumbel_uncertain));
      result.push_back(UncertaintyDescriptionType(UncertaintyDescriptionType::frechet_uncertain));
      result.push_back(UncertaintyDescriptionType(UncertaintyDescriptionType::weibull_uncertain));
      result.push_back(UncertaintyDescriptionType(UncertaintyDescriptionType::histogram_bin_uncertain));
    }
    else if (variableValueType == VariableValueType::Discrete) {
      result.push_back(UncertaintyDescriptionType(UncertaintyDescriptionType::poisson_uncertain));
      result.push_back(UncertaintyDescriptionType(UncertaintyDescriptionType::binomial_uncertain));
      result.push_back(UncertaintyDescriptionType(UncertaintyDescriptionType::negative_binomial_uncertain));
      result.push_back(UncertaintyDescriptionType(UncertaintyDescriptionType::geometric_uncertain));
      result.push_back(UncertaintyDescriptionType(UncertaintyDescriptionType::hypergeometric_uncertain));
      result.push_back(UncertaintyDescriptionType(UncertaintyDescriptionType::histogram_point_uncertain));
    }
  }
  return result;
}

UncertaintyDescriptionType UncertaintyDescription::type() const {
  return impl()->type();
}

void UncertaintyDescription::restoreDefaults() {
  return impl()->restoreDefaults();
}

UncertaintyDescription::UncertaintyDescription(boost::shared_ptr<detail::UncertaintyDescription_Impl> impl)
  : m_impl(impl)
{}

boost::shared_ptr<detail::UncertaintyDescription_Impl> UncertaintyDescription::impl() const {
  return m_impl;
}

namespace detail {

  QVariant toVariant(const UncertaintyDescription& udesc) {
    GenericUncertaintyDescription generic = udesc.cast<GenericUncertaintyDescription>();

    QVariantMap udescMap;
    udescMap["type"] = toQString(generic.actualType().valueName());
    if (!generic.attributes().empty()) {
      QVariantList attributesList;
      Q_FOREACH(const Attribute& attribute,generic.attributes()) {
        attributesList.push_back(openstudio::detail::toVariant(attribute));
      }
      udescMap["attributes"] = QVariant(attributesList);
    }

    return QVariant(udescMap);
  }

  UncertaintyDescription toUncertaintyDescription(const QVariant& variant,
                                                  const VersionString& version)
  {
    QVariantMap map = variant.toMap();

    AttributeVector attributes = deserializeUnorderedVector(
          map["attributes"].toList(),
          boost::function<Attribute (const QVariant&)>(boost::bind(openstudio::detail::toAttribute,_1,version)));

    GenericUncertaintyDescription result(UncertaintyDescriptionType(map["type"].toString().toStdString()),
                                         attributes);

    return result.cast<UncertaintyDescription>();
  }

} // detail

} // analysis
} // openstudio


/*
 * Copyright 2020 Tier IV, Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// Copyright (c) 2019, Map IV, Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright notice,
//   this list of conditions and the following disclaimer.
// * 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.
// * Neither the name of the Map IV, Inc. nor the names of its contributors
//   may be used to endorse or promote products derived from this software
//   without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT HOLDER 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.

/*
 * tag_serial_driver.cpp
 * Tamagawa IMU Driver
 * Author MapIV Sekino
 * Ver 1.00 2019/4/4
 */

#include <fcntl.h>
#include <math.h>
#include <signal.h>
#include <stdio.h>
#include <termios.h>
#include <unistd.h>
#include <string>
#include "ros/ros.h"
#include "sensor_msgs/Imu.h"
#include "std_msgs/Int32.h"

#include <sys/ioctl.h>

std::string device = "/dev/ttyUSB0";
std::string imu_type = "noGPS";
std::string rate = "50";

struct termios old_conf_tio;
struct termios conf_tio;

int fd;
int counter;
int raw_data;

sensor_msgs::Imu imu_msg;

int serial_setup(const char * device)
{
  int fd = open(device, O_RDWR);

  speed_t BAUDRATE = B115200;

  conf_tio.c_cflag += CREAD;   // 受信有効
  conf_tio.c_cflag += CLOCAL;  // ローカルライン（モデム制御なし）
  conf_tio.c_cflag += CS8;     // データビット:8bit
  conf_tio.c_cflag += 0;       // ストップビット:1bit
  conf_tio.c_cflag += 0;

  cfsetispeed(&conf_tio, BAUDRATE);
  cfsetospeed(&conf_tio, BAUDRATE);

  tcsetattr(fd, TCSANOW, &conf_tio);
  ioctl(fd, TCSETS, &conf_tio);
  return fd;
}

void receive_ver_req(const std_msgs::Int32::ConstPtr & msg)
{
  char ver_req[] = "$TSC,VER*29\x0d\x0a";
  int ver_req_data = write(fd, ver_req, sizeof(ver_req));
  ROS_INFO("Send Version Request:%s", ver_req);
}

void receive_offset_cancel_req(const std_msgs::Int32::ConstPtr & msg)
{
  char offset_cancel_req[32];
  sprintf(offset_cancel_req, "$TSC,OFC,%d\x0d\x0a", msg->data);
  int offset_cancel_req_data = write(fd, offset_cancel_req, sizeof(offset_cancel_req));
  ROS_INFO("Send Offset Cancel Request:%s", offset_cancel_req);
}

void receive_heading_reset_req(const std_msgs::Int32::ConstPtr & msg)
{
  char heading_reset_req[] = "$TSC,HRST*29\x0d\x0a";
  int heading_reset_req_data = write(fd, heading_reset_req, sizeof(heading_reset_req));
  ROS_INFO("Send Heading reset Request:%s", heading_reset_req);
}

void shutdown_cmd(int sig)
{
  tcsetattr(fd, TCSANOW, &old_conf_tio);  // Revert to previous settings
  close(fd);
  ROS_INFO("Port closed");
  ros::shutdown();
}

#include <boost/asio.hpp>
using namespace boost::asio;

int main(int argc, char ** argv)
{
  ros::init(argc, argv, "tag_serial_driver", ros::init_options::NoSigintHandler);
  ros::NodeHandle n;
  ros::Publisher pub = n.advertise<sensor_msgs::Imu>("imu/data_raw", 1000);
  ros::Subscriber sub1 = n.subscribe("receive_ver_req", 10, receive_ver_req);
  ros::Subscriber sub2 = n.subscribe("receive_offset_cancel_req", 10, receive_offset_cancel_req);
  ros::Subscriber sub3 = n.subscribe("receive_heading_reset_req", 10, receive_heading_reset_req);

  ros::NodeHandle nh("~");
  std::string imu_frame_id = "imu";
  nh.getParam("imu_frame_id", imu_frame_id);

  std::string port = "/dev/ttyUSB0";
  nh.getParam("port", port);

  io_service io;
  serial_port serial_port(io, port.c_str());
  serial_port.set_option(serial_port_base::baud_rate(115200));
  serial_port.set_option(serial_port_base::character_size(8));
  serial_port.set_option(serial_port_base::flow_control(serial_port_base::flow_control::none));
  serial_port.set_option(serial_port_base::parity(serial_port_base::parity::none));
  serial_port.set_option(serial_port_base::stop_bits(serial_port_base::stop_bits::one));

  std::string wbuf = "$TSC,BIN,30\x0d\x0a";
  std::size_t length;
  serial_port.write_some(buffer(wbuf));

  ros::Rate loop_rate(30);

  imu_msg.orientation.x = 0.0;
  imu_msg.orientation.y = 0.0;
  imu_msg.orientation.z = 0.0;
  imu_msg.orientation.w = 1.0;

  while (ros::ok()) {
    ros::spinOnce();

    boost::asio::streambuf response;
    boost::asio::read_until(serial_port, response, "\n");
    std::string rbuf(
      boost::asio::buffers_begin(response.data()), boost::asio::buffers_end(response.data()));

    length = rbuf.size();
    size_t len = response.size();

    if (length > 0) {
      if (rbuf[5] == 'B' && rbuf[6] == 'I' && rbuf[7] == 'N' && rbuf[8] == ',' && length == 58) {
        imu_msg.header.frame_id = imu_frame_id;
        imu_msg.header.stamp = ros::Time::now();

        counter = ((rbuf[11] << 8) & 0x0000FF00) | (rbuf[12] & 0x000000FF);
        raw_data = ((((rbuf[15] << 8) & 0xFFFFFF00) | (rbuf[16] & 0x000000FF)));
        imu_msg.angular_velocity.x =
          raw_data * (200 / pow(2, 15)) * M_PI / 180;  // LSB & unit [deg/s] => [rad/s]
        raw_data = ((((rbuf[17] << 8) & 0xFFFFFF00) | (rbuf[18] & 0x000000FF)));
        imu_msg.angular_velocity.y =
          raw_data * (200 / pow(2, 15)) * M_PI / 180;  // LSB & unit [deg/s] => [rad/s]
        raw_data = ((((rbuf[19] << 8) & 0xFFFFFF00) | (rbuf[20] & 0x000000FF)));
        imu_msg.angular_velocity.z =
          raw_data * (200 / pow(2, 15)) * M_PI / 180;  // LSB & unit [deg/s] => [rad/s]
        raw_data = ((((rbuf[21] << 8) & 0xFFFFFF00) | (rbuf[22] & 0x000000FF)));
        imu_msg.linear_acceleration.x = raw_data * (100 / pow(2, 15));  // LSB & unit [m/s^2]
        raw_data = ((((rbuf[23] << 8) & 0xFFFFFF00) | (rbuf[24] & 0x000000FF)));
        imu_msg.linear_acceleration.y = raw_data * (100 / pow(2, 15));  // LSB & unit [m/s^2]
        raw_data = ((((rbuf[25] << 8) & 0xFFFFFF00) | (rbuf[26] & 0x000000FF)));
        imu_msg.linear_acceleration.z = raw_data * (100 / pow(2, 15));  // LSB & unit [m/s^2]

        pub.publish(imu_msg);

      } else if (rbuf[5] == 'V' && rbuf[6] == 'E' && rbuf[7] == 'R' && rbuf[8] == ',') {
        ROS_DEBUG("%s", rbuf.c_str());
      }
    }
  }

  return 0;
}


//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
// Lua plugin for Zen Scripting
//
// Copyright (C) 2001 - 2016 Raymond A. Richards
//
//  This software is provided 'as-is', without any express or implied
//  warranty.  In no event will the authors be held liable for any damages
//  arising from the use of this software.
//
//  Permission is granted to anyone to use this software for any purpose,
//  including commercial applications, and to alter it and redistribute it
//  freely, subject to the following restrictions:
//
//  1. The origin of this software must not be misrepresented; you must not
//     claim that you wrote the original software. If you use this software
//     in a product, an acknowledgment in the product documentation would be
//     appreciated but is not required.
//  2. Altered source versions must be plainly marked as such, and must not be
//     misrepresented as being the original software.
//  3. This notice may not be removed or altered from any source distribution.
//
//  Tony Richards trichards@indiezen.com
//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~

extern "C" {
#include <lauxlib.h>
#include <lualib.h>
#include <lua.h>
}

#include "LuaEngine.hpp"
#include "LuaModule.hpp"
#include "Config.hpp"

#include <Zen/Core/Scripting/I_ScriptModule.hpp>
#include <Zen/Core/Scripting/I_ObjectReference.hpp>

#include <Zen/Core/Math/Matrix4.hpp>
#include <Zen/Core/Math/Point3.hpp>
#include <Zen/Core/Math/Vector3.hpp>
#include <Zen/Core/Math/Vector4.hpp>
#include <Zen/Core/Math/Radian.hpp>
#include <Zen/Core/Math/Degree.hpp>
#include <Zen/Core/Math/Quaternion4.hpp>

#include <Zen/Core/Utility/runtime_exception.hpp>

#include <boost/bind.hpp>
#include <boost/function.hpp>

#include <iostream>

#include <stdio.h>
#ifndef WIN32
#include <stdint.h>
#endif
#include <stdlib.h>

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
namespace Zen {
namespace ZLua {
//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
LuaEngine::LuaEngine()
{
#if 0
    m_pHeap =
        pObjectHeap_type(new LuaHeap(this),
        boost::bind(&LuaEngine::onDestroyObjectHeap,this,_1));
#endif

    // http://www.lua.org/pil/24.1.html
    m_pLua = lua_open();

    registerModules();
}

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
LuaEngine::~LuaEngine()
{
    lua_close(m_pLua);
}

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
Scripting::I_ObjectHeap&
LuaEngine::heap()
{
    //return *m_pHeap;
    throw Utility::runtime_exception("LuaEngine::heap(): Error, not implemented");
}

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
static
std::string
getLuaErrorString(int _error)
{
    std::string errorMessage;

    switch(_error)
    {
    case LUA_ERRSYNTAX:
        errorMessage = "Lua syntax error";
        break;
    case LUA_ERRMEM:
        errorMessage = "Lua out of memory error";
        break;
    case LUA_ERRRUN:
        errorMessage = "Lua runtime error";
        break;
    case LUA_ERRERR:
        errorMessage = "Lua fatal error";
        break;
    case LUA_ERRFILE:
        errorMessage = "Lua file not found error";
        break;
    case LUA_YIELD:
        errorMessage = "Lua yield";
        break;
    }

    return errorMessage;
}

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
bool
LuaEngine::executeScript(const std::string& _fileName)
{
    int luaTop = lua_gettop(m_pLua);

    // TODO This should really use a resource manager, but for now just do it this way
    int error;

    // Load a file.  The chunk is on top of the stack.
    if ((error = luaL_loadfile(m_pLua, _fileName.c_str())) != 0)
    {
        std::stringstream errString;
        errString << getLuaErrorString(error)
            << " while loading file " << _fileName << "." << std::endl;

        errString << lua_tostring(m_pLua, -1) << std::endl;

        // Pop the error message off the stack.
        lua_pop(m_pLua, 1);

        throw Utility::runtime_exception(errString.str());
    }

    int errorHandler = 0;

    lua_getglobal(m_pLua, "errorHandler");
    if (!lua_isnil(m_pLua, lua_gettop(m_pLua)))
    {
        // Set the error handler
        errorHandler = -2;
    }

    // Duplicate the chunk so we can keep a reference to it.
    // This also makes the chunk be on the top of the stack.
    lua_pushvalue(m_pLua, -2);

    // Call the chunk.
    if (error = lua_pcall(m_pLua, 0, 0, errorHandler) != 0)
    {
        std::stringstream errString;
        errString << getLuaErrorString(error) << " "
            << lua_tostring(m_pLua, -1) << std::endl;

        lua_getglobal(m_pLua, "errorHandler");

        if (!lua_isnil(m_pLua, lua_gettop(m_pLua)))
        {
            lua_pushstring(m_pLua, errString.str().c_str());
            lua_pcall(m_pLua, 1, 0, 0);
        }
        else
        {
            // TODO No errorHandler function exists.  Should this
            // throw an exception or should an error message be output?
            // For now just output an error message.
            std::cout << errString.str() << std::endl;
        }

        // Who knows what's leftover on the stack?  Just
        // punt and set it to where we know it should be.
        lua_settop(m_pLua, luaTop + 2);
    }

    // Pop the errorHandler and the initial chunk.
    lua_pop(m_pLua, 2);

    // TODO Verify that luaTop hasn't changed.
    luaTop = lua_gettop(m_pLua);

    return true;
}

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
static int new_Point3(lua_State *L, Math::Point3 _point3);

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
enum TYPES
{
    OBJECT_REFERENCE_TYPE,
    INT_TYPE,
    UINT_TYPE,
    STRING_TYPE,
    REAL_TYPE,
    POINT3_TYPE,
    VOID_TYPE
};

class TypeHandler
{
public:
	typedef boost::function<std::string(boost::any&)>		ConvertToStringFunction_type;
	typedef boost::function<void(lua_State*, boost::any&)>	PushArgumentFunction_type;

	TypeHandler(TYPES _type,
			ConvertToStringFunction_type _convertToStringFunction,
			PushArgumentFunction_type _pushArgumentFunction)
	:	m_type(_type)
	,	m_convertToStringFunction(_convertToStringFunction)
	,	m_pushArgumentFunction(_pushArgumentFunction)
	{

	}

	TypeHandler(const TypeHandler& _right)
	{
		clone(_right);
	}

	TypeHandler& operator=(const TypeHandler& _right)
	{
		return clone(_right);
	}

	TypeHandler& clone(const TypeHandler& _right)
	{
		m_type = _right.m_type;
		m_convertToStringFunction = _right.m_convertToStringFunction;
		m_pushArgumentFunction = _right.m_pushArgumentFunction;
		return *this;
	}

	std::string convertToString(boost::any& _value)
	{
		return m_convertToStringFunction(_value);
	}

	void pushArgument(lua_State* _pLuaState, boost::any& _value)
	{
		return m_pushArgumentFunction(_pLuaState, _value);
	}

	TYPES getType()
	{
		return m_type;
	}

private:
	TYPES 							m_type;
	ConvertToStringFunction_type 	m_convertToStringFunction;
	PushArgumentFunction_type 		m_pushArgumentFunction;

};

typedef std::map<std::string, TypeHandler*> TypeMap_type;
static TypeMap_type sm_typeMap;

static bool sm_initialized = false;

static std::string convertIntToString(boost::any& _value)
{
	std::stringstream stream;
	stream << boost::any_cast<int>(_value);
	return stream.str();
}

static void pushInt(lua_State* L, boost::any& _value)
{
    int value = boost::any_cast<int>(_value);
    lua_pushinteger(L, value);
}

static std::string convertUIntToString(boost::any& _value)
{
    std::stringstream stream;
    stream << boost::any_cast<unsigned int>(_value);
    return stream.str();
}

static void pushUInt(lua_State* L, boost::any& _value)
{
    int value = boost::any_cast<unsigned int>(_value);
    lua_pushinteger(L, value);
}

static std::string convertStringToString(boost::any& _value)
{
	std::string value = boost::any_cast<std::string>(_value);
	return value;
}

static void pushString(lua_State* L, boost::any& _value)
{
    std::string value = boost::any_cast<std::string>(_value);
    lua_pushstring(L, value.c_str());
}

static std::string convertRealToString(boost::any& _value)
{
	std::stringstream stream;
	stream << boost::any_cast<Math::Real>(_value);
	return stream.str();
}

static void pushReal(lua_State* L, boost::any& _value)
{
    Math::Real value = boost::any_cast<Math::Real>(_value);
    lua_pushnumber(L, value);
}

static std::string convertPoint3ToString(boost::any& _value)
{
	std::stringstream stream;
	Math::Point3 value = boost::any_cast<Math::Point3>(_value);
	stream << value.m_x << " " << value.m_y << " " << value.m_z;
	return stream.str();
}

static void pushPoint3(lua_State* L, boost::any& _value)
{
    new_Point3(L, boost::any_cast<Math::Point3>(_value));
}

static std::string convertVoidToString(boost::any& _value)
{
	return std::string("<void>");
}

static void pushVoid(lua_State* L, boost::any& _value)
{
	lua_pushnil(L);
}

static std::string convertObjectToString(boost::any& _value)
{
	throw Zen::Utility::runtime_exception("Error, cannot convert an object to a string.");
}

static void pushObject(lua_State* L, boost::any& _value)
{
    Scripting::I_ObjectReference* pObj = boost::any_cast<Scripting::I_ObjectReference*>(_value);
    lua_rawgeti(L, LUA_REGISTRYINDEX, (uintptr_t)pObj->getScriptUserData());
}

static void initTypeHuntMaps()
{
    if (sm_initialized)
    {
    	return ;
    }

    sm_initialized = true;
    sm_typeMap[typeid(Scripting::I_ObjectReference*).name()] = new TypeHandler(OBJECT_REFERENCE_TYPE, convertObjectToString, pushObject);
    sm_typeMap[typeid(int).name()] = new TypeHandler(INT_TYPE, convertIntToString, pushInt);
    sm_typeMap[typeid(unsigned int).name()] = new TypeHandler(UINT_TYPE, convertUIntToString, pushUInt);
    sm_typeMap[typeid(std::string).name()] = new TypeHandler(STRING_TYPE, convertStringToString, pushString);
    sm_typeMap[typeid(Math::Real).name()] = new TypeHandler(REAL_TYPE, convertRealToString, pushReal);
    sm_typeMap[typeid(Math::Point3).name()] = new TypeHandler(POINT3_TYPE, convertPoint3ToString, pushPoint3);
    sm_typeMap[typeid(void).name()] = new TypeHandler(VOID_TYPE, convertVoidToString, pushVoid);
}

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
void
LuaEngine::executeMethod(boost::any& _object, boost::any& _method, std::vector<boost::any>& _parms)
{
	initTypeHuntMaps();

    lua_State* const L = getState();

    int object;

    TypeMap_type::iterator pType = sm_typeMap.find(_object.type().name());
    if (pType != sm_typeMap.end())
    {
        switch(pType->second->getType())
        {
        case OBJECT_REFERENCE_TYPE:
            object = (uintptr_t)(boost::any_cast<Scripting::I_ObjectReference*>(_object)->getScriptUserData());
            break;
        case INT_TYPE:
            object = boost::any_cast<int>(_object);
            break;
        default:
            // TODO Error?
            return;
        }
    }
    else
    {
        // TODO Error?
        return;
    }

    int method = boost::any_cast<int>(_method);

    lua_rawgeti(L, LUA_REGISTRYINDEX, method);
    lua_rawgeti(L, LUA_REGISTRYINDEX, object);

    int parms = 1;

    for(std::vector<boost::any>::iterator iter = _parms.begin(); iter != _parms.end(); iter++)
    {
        pType = sm_typeMap.find(iter->type().name());
        if (pType == sm_typeMap.end())
        {
            std::cout << "Error, undefined parameter type " << iter->type().name() << " in LuaEngine::executeMethod: " << iter->type().name() << std::endl;
            return;
        }

        pType->second->pushArgument(L, *iter);
        parms++;
    }

    if (lua_pcall(m_pLua, parms, 0, 0) != 0)
    {
        std::string errString = lua_tostring(m_pLua, -1);
        std::cout << errString << std::endl;
    }
}

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
LuaEngine::pScriptModule_type
LuaEngine::createScriptModule(const std::string& _moduleName, const std::string& _docString)
{
    return pScriptModule_type(new LuaModule(this, _moduleName, _docString),
        boost::bind(&LuaEngine::onDestroyScriptModule,this,_1));
}

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
LuaTypeMap&
LuaEngine::getTypeMap()
{
    return m_typeMap;
}

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
#if 0
LuaEngine::pObjectReference_type
LuaEngine::getObject(PyObject* _pObject)
{
    return m_objectMap[_pObject];
}
#endif

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
#if 0
void
LuaEngine::setObject(PyObject* _pPyObject, pObjectReference_type _pCPPObject)
{
    // Isn't there a better way to do this?  Is there a way to use the PyObject to store the
    // raw C++ pointer?  For now, we just use this map.
    m_objectMap[_pPyObject] = _pCPPObject;
}
#endif

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
void
LuaEngine::onDestroyObjectHeap(wpObjectHeap_type _pObjectHeap)
{
    /// Fire the ObjectHeap's onDestroyEvent
    _pObjectHeap->onDestroyEvent(_pObjectHeap);

    /// Delete the ObjectHeap
    LuaHeap* pObjectHeap = dynamic_cast<LuaHeap*>(_pObjectHeap.get());

    if( pObjectHeap )
    {
        delete pObjectHeap;
    }
    else
    {
        throw Utility::runtime_exception("Zen::ZLua::LuaEngine::onDestroyObjectHeap() : _pObjectHeap is an invalid LuaHeap.");
    }
}

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
void
LuaEngine::onDestroyScriptModule(wpScriptModule_type _pScriptModule)
{
    /// Fire the ScriptModule's onDestroyEvent
    _pScriptModule->onDestroyEvent(_pScriptModule);

    /// Delete the ScriptModule
    LuaModule* pScriptModule = dynamic_cast<LuaModule*>(_pScriptModule.get());

    if( pScriptModule )
    {
        delete pScriptModule;
    }
    else
    {
        throw Utility::runtime_exception("Zen::ZLua::LuaEngine::onDestroyScriptModule() : _pScriptModule is an invalid LuaModule.");
    }
}

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~

static Math::Real checkReal(lua_State *L, int idx)
{
    luaL_argcheck(L, lua_isnumber(L, idx), idx, "Real expected");
    return (Math::Real)lua_tonumber(L, idx);
}

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
static const char* sm_pMatrix4 = "Matrix4";

static Math::Matrix4* checkMatrix4(lua_State *L, int idx)
{
    lua_pushliteral(L, "__zen_userdata");
    lua_gettable(L, idx);

    Math::Matrix4* pMatrix4 = (Math::Matrix4*)luaL_checkudata(L, -1, sm_pMatrix4);
    luaL_argcheck(L, pMatrix4 != NULL, idx, "Matrix4 expected");
    lua_pop(L, 1);

    return pMatrix4;
}

static int new_Matrix4 (lua_State *L)
{
    const int top = lua_gettop(L);

    lua_newtable(L);
    const int table = lua_gettop(L);

    Math::Matrix4* pMatrix4 = new (lua_newuserdata(L, sizeof(Math::Matrix4))) Math::Matrix4(Math::Matrix4::INIT_IDENTITY);
    const int userdata = lua_gettop(L);

    // T["__zen_userdata"] = UD
    lua_pushvalue(L, userdata);
    lua_setfield(L, table, "__zen_userdata");

    // Get the meta table for this class type and set it for this object
    luaL_getmetatable(L, sm_pMatrix4);
    lua_setmetatable(L, table);

    // Get the meta table for this class type and set it for the user data
    luaL_getmetatable(L, sm_pMatrix4);
    lua_setmetatable(L, userdata);

    // Pop the userdata so only the table is left
    lua_pop(L, 1);

    // Initialize up to 4x4 matrix
    for(int x = 1; x < 17; x++)
    {
        if (x < top)
        {
            pMatrix4->m_array[x - 1] =  (Zen::Math::Real)lua_tonumber(L, x);
        }
        else
        {
            pMatrix4->m_array[x - 1] = 0.0f;
        }
    }

    return 1;
}

static int setXRotation_Matrix4(lua_State *L)
{
    Math::Matrix4* pMatrix4 = checkMatrix4(L, 1);
    Math::Radian rotation(checkReal(L, 2));

    pMatrix4->setXRotation(rotation);
    return 0;
}

static int setYRotation_Matrix4(lua_State *L)
{
    Math::Matrix4* pMatrix4 = checkMatrix4(L, 1);
    Math::Radian rotation(checkReal(L, 2));

    pMatrix4->setYRotation(rotation);
    return 0;
}

static int setZRotation_Matrix4(lua_State *L)
{
    Math::Matrix4* pMatrix4 = checkMatrix4(L, 1);
    Math::Radian rotation(checkReal(L, 2));

    pMatrix4->setZRotation(rotation);
    return 0;
}

static int setXYZRotation_Matrix4(lua_State *L)
{
    Math::Matrix4* pMatrix4 = checkMatrix4(L, 1);
    Math::Radian xRotation(checkReal(L, 2));
    Math::Radian yRotation(checkReal(L, 3));
    Math::Radian zRotation(checkReal(L, 4));

    pMatrix4->setXYZRotation(xRotation, yRotation, zRotation);
    return 0;
}

static int setPosition_Matrix4(lua_State *L)
{
    Math::Matrix4* pMatrix4 = checkMatrix4(L, 1);
    Math::Radian x(checkReal(L, 2));
    Math::Radian y(checkReal(L, 3));
    Math::Radian z(checkReal(L, 4));

    pMatrix4->setPosition(x,y,z);
    return 0;
}

static const struct luaL_reg MatrixLib [] = {
    {"setXRotation", setXRotation_Matrix4},
    {"setYRotation", setYRotation_Matrix4},
    {"setZRotation", setZRotation_Matrix4},
    {"setXYZRotation", setXYZRotation_Matrix4},
    {"setPosition", setPosition_Matrix4},
    {NULL, NULL}
};

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~

static const char* sm_pRadian = "Radian";

static Math::Radian* checkRadian(lua_State *L, int idx)
{
    lua_pushliteral(L, "__zen_userdata");
    lua_gettable(L, idx);

    Math::Radian* pRadian = (Math::Radian*)luaL_checkudata(L, -1, sm_pRadian);
    luaL_argcheck(L, pRadian != NULL, idx, "Radian expected");
    lua_pop(L, 1);

    return pRadian;
}

static int new_Radian(lua_State* L)
{
    const int top = lua_gettop(L);

    lua_newtable(L);
    const int table = lua_gettop(L);

    Math::Radian* pRadian = new (lua_newuserdata(L, sizeof(Math::Radian))) Math::Radian();
    const int userdata = lua_gettop(L);

    // T["__zen_userdata"] = UD
    lua_pushvalue(L, userdata);
    lua_setfield(L, table, "__zen_userdata");

    // Get the meta table for this class type and set it for this object
    luaL_getmetatable(L, sm_pRadian);
    lua_setmetatable(L, table);

    // Get the meta table for this class type and set it for the user data
    luaL_getmetatable(L, sm_pRadian);
    lua_setmetatable(L, userdata);

    // Pop the userdata so only the table is left
    lua_pop(L, 1);

    if( 1 <= top )
    {
        *pRadian = (Zen::Math::Real)lua_tonumber(L, 1);
    }
    else
    {
        *pRadian = 0.0f;
    }

    return 1;
}

static const struct luaL_reg RadianLib [] = {
    {NULL, NULL}
};

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~

static const char* sm_pDegree = "Degree";

static Math::Degree* checkDegree(lua_State *L, int idx)
{
    lua_pushliteral(L, "__zen_userdata");
    lua_gettable(L, idx);

    Math::Degree* pDegree = (Math::Degree*)luaL_checkudata(L, -1, sm_pDegree);
    luaL_argcheck(L, pDegree != NULL, idx, "Degree expected");
    lua_pop(L, 1);

    return pDegree;
}

static int new_Degree(lua_State* L)
{
    const int top = lua_gettop(L);

    lua_newtable(L);
    const int table = lua_gettop(L);

    Math::Degree* pDegree = new (lua_newuserdata(L, sizeof(Math::Degree))) Math::Degree();
    const int userdata = lua_gettop(L);

    // T["__zen_userdata"] = UD
    lua_pushvalue(L, userdata);
    lua_setfield(L, table, "__zen_userdata");

    // Get the meta table for this class type and set it for this object
    luaL_getmetatable(L, sm_pRadian);
    lua_setmetatable(L, table);

    // Get the meta table for this class type and set it for the user data
    luaL_getmetatable(L, sm_pRadian);
    lua_setmetatable(L, userdata);

    // Pop the userdata so only the table is left
    lua_pop(L, 1);

    if( 1 <= top )
    {
        *pDegree = (Zen::Math::Real)lua_tonumber(L, 1);
    }
    else
    {
        *pDegree = 0.0f;
    }

    return 1;
}

static const struct luaL_reg DegreeLib [] = {
    {NULL, NULL}
};

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~

static const char* sm_pPoint3= "Point3";

static Math::Point3* checkPoint3(lua_State *L, int idx)
{
    lua_pushliteral(L, "__zen_userdata");
    lua_gettable(L, idx);

    Math::Point3* pPoint3 = (Math::Point3*)luaL_checkudata(L, -1, sm_pPoint3);
    luaL_argcheck(L, pPoint3 != NULL, idx, "Point3 expected");
    lua_pop(L, 1);

    return pPoint3;
}

static int new_Point3 (lua_State *L, Math::Point3 _point3)
{
    const int top = lua_gettop(L);

    lua_newtable(L);
    const int table = lua_gettop(L);

    Math::Point3* pPoint3 = new (lua_newuserdata(L, sizeof(Math::Point3))) Math::Point3(_point3);
    const int userdata = lua_gettop(L);

    // T["__zen_userdata"] = UD
    lua_pushvalue(L, userdata);
    lua_setfield(L, table, "__zen_userdata");

    // Get the meta table for this class type and set it for this object
    luaL_getmetatable(L, sm_pPoint3);
    lua_setmetatable(L, table);

    // Get the meta table for this class type and set it for the user data
    luaL_getmetatable(L, sm_pPoint3);
    lua_setmetatable(L, userdata);

    // Pop the userdata so only the table is left
    lua_pop(L, 1);

    return 1;
}

static int new_Point3NoArgs(lua_State *L)
{
    return new_Point3(L, Math::Point3());
}

static int setX_Point3(lua_State *L)
{
    Math::Point3* pPoint3 = checkPoint3(L, 1);
    Math::Real value = checkReal(L, 2);

    pPoint3->setX(value);
    return 0;
}

static int setY_Point3(lua_State *L)
{
    Math::Point3* pPoint3 = checkPoint3(L, 1);
    Math::Real value = checkReal(L, 2);

    pPoint3->setY(value);
    return 0;
}

static int setZ_Point3(lua_State *L)
{
    Math::Point3* pPoint3 = checkPoint3(L, 1);
    Math::Real value = checkReal(L, 2);

    pPoint3->setZ(value);
    return 0;
}

static int getX_Point3(lua_State *L)
{
    Math::Point3* pPoint3 = checkPoint3(L, 1);

    lua_pushnumber(L, pPoint3->getX());
    return 1;
}

static int getY_Point3(lua_State *L)
{
    Math::Point3* pPoint3 = checkPoint3(L, 1);

    lua_pushnumber(L, pPoint3->getY());
    return 1;
}

static int getZ_Point3(lua_State *L)
{
    Math::Point3* pPoint3 = checkPoint3(L, 1);

    lua_pushnumber(L, pPoint3->getZ());
    return 1;
}

static const struct luaL_reg Point3Lib [] = {
    {"setX", setX_Point3},
    {"setY", setY_Point3},
    {"setZ", setZ_Point3},
    {"getX", getX_Point3},
    {"getY", getY_Point3},
    {"getZ", getZ_Point3},
    {NULL, NULL}
};

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~

static const char* sm_pVector3 = "Vector3";

static Math::Vector3* checkVector3(lua_State *L, int idx)
{
    lua_pushliteral(L, "__zen_userdata");
    lua_gettable(L, idx);

    Math::Vector3* pVector3 = (Math::Vector3*)luaL_checkudata(L, -1, sm_pVector3);
    luaL_argcheck(L, pVector3 != NULL, idx, "Vector3 expected");
    lua_pop(L, 1);

    return pVector3;
}

static int new_Vector3 (lua_State *L)
{
    const int top = lua_gettop(L);

    lua_newtable(L);
    const int table = lua_gettop(L);

    Math::Vector3* pVector3 = new (lua_newuserdata(L, sizeof(Math::Vector3))) Math::Vector3();
    const int userdata = lua_gettop(L);

    // T["__zen_userdata"] = UD
    lua_pushvalue(L, userdata);
    lua_setfield(L, table, "__zen_userdata");

    // Get the meta table for this class type and set it for this object
    luaL_getmetatable(L, sm_pVector3);
    lua_setmetatable(L, table);

    // Get the meta table for this class type and set it for the user data
    luaL_getmetatable(L, sm_pVector3);
    lua_setmetatable(L, userdata);

    // Pop the userdata so only the table is left
    lua_pop(L, 1);

    for(int x = 1; x < 4; x++)
    {
        if (x <= top)
        {
            pVector3->m_array[x - 1] = (Zen::Math::Real)lua_tonumber(L, x);
        }
        else
        {
            pVector3->m_array[x - 1] = 0.0f;
        }
    }

    return 1;
}

static int destroy_Vector3(lua_State *L)
{
    /// TODO Determine if lua takes care of deleting the pointer automatically
    /// Had problems with the commented code below.
    //Math::Vector3* pVector3 = checkVector3(L,1);
    //if (pVector3 != NULL)
    //{
    //    delete pVector3;
    //}
    return 0;
}

static int setX_Vector3(lua_State *L)
{
    Math::Vector3* pVector3 = checkVector3(L, 1);
    pVector3->m_x = checkReal(L, 2);
    return 0;
}

static int setY_Vector3(lua_State *L)
{
    Math::Vector3* pVector3 = checkVector3(L, 1);
    pVector3->m_y = checkReal(L, 2);
    return 0;
}

static int setZ_Vector3(lua_State *L)
{
    Math::Vector3* pVector3 = checkVector3(L, 1);
    pVector3->m_z = checkReal(L, 2);
    return 0;
}

static int getX_Vector3(lua_State *L)
{
    Math::Vector3* pVector3 = checkVector3(L, 1);

    lua_pushnumber(L, pVector3->m_x);
    return 1;
}

static int getY_Vector3(lua_State *L)
{
    Math::Vector3* pVector3 = checkVector3(L, 1);

    lua_pushnumber(L, pVector3->m_y);
    return 1;
}

static int getZ_Vector3(lua_State *L)
{
    Math::Vector3* pVector3 = checkVector3(L, 1);

    lua_pushnumber(L, pVector3->m_z);
    return 1;
}

static int add_Vector3(lua_State *L)
{
    new_Vector3(L);
    Math::Vector3* pVector3A = checkVector3(L,1);
    Math::Vector3* pVector3B = checkVector3(L,2);
    Math::Vector3* pVectorResult = checkVector3(L,3);

    *pVectorResult = *pVector3A + *pVector3B;

    return 1;
}

static const struct luaL_reg Vector3Lib [] = {
    {"setX", setX_Vector3},
    {"setY", setY_Vector3},
    {"setZ", setZ_Vector3},
    {"getX", getX_Vector3},
    {"getY", getY_Vector3},
    {"getZ", getZ_Vector3},
    {"add", add_Vector3},
    {NULL, NULL}
};

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~

static const char* sm_pVector4= "Vector4";

static Math::Vector4* checkVector4(lua_State *L, int idx)
{
    lua_pushliteral(L, "__zen_userdata");
    lua_gettable(L, idx);

    Math::Vector4* pVector4 = (Math::Vector4*)luaL_checkudata(L, -1, sm_pVector4);
    luaL_argcheck(L, pVector4 != NULL, idx, "Vector4 expected");
    lua_pop(L, 1);

    return pVector4;
}

static int new_Vector4 (lua_State *L)
{
    const int top = lua_gettop(L);

    lua_newtable(L);
    const int table = lua_gettop(L);

    Math::Vector4* pVector4 = new (lua_newuserdata(L, sizeof(Math::Vector4))) Math::Vector4();
    const int userdata = lua_gettop(L);

    // T["__zen_userdata"] = UD
    lua_pushvalue(L, userdata);
    lua_setfield(L, table, "__zen_userdata");

    // Get the meta table for this class type and set it for this object
    luaL_getmetatable(L, sm_pVector4);
    lua_setmetatable(L, table);

    // Get the meta table for this class type and set it for the user data
    luaL_getmetatable(L, sm_pVector4);
    lua_setmetatable(L, userdata);

    // Pop the userdata so only the table is left
    lua_pop(L, 1);

    for(int x = 1; x < 5; x++)
    {
        if (x <= top)
        {
            pVector4->m_array[x - 1] = (Zen::Math::Real)lua_tonumber(L, x);
        }
        else
        {
            pVector4->m_array[x - 1] = 0.0f;
        }
    }

    return 1;
}

static int setX_Vector4(lua_State *L)
{
    Math::Vector4* pVector4 = checkVector4(L, 1);
    pVector4->m_x = checkReal(L, 2);
    return 0;
}

static int setY_Vector4(lua_State *L)
{
    Math::Vector4* pVector4 = checkVector4(L, 1);
    pVector4->m_y = checkReal(L, 2);
    return 0;
}

static int setZ_Vector4(lua_State *L)
{
    Math::Vector4* pVector4 = checkVector4(L, 1);
    pVector4->m_z = checkReal(L, 2);
    return 0;
}

static int setW_Vector4(lua_State *L)
{
    Math::Vector4* pVector4 = checkVector4(L, 1);
    pVector4->m_w = checkReal(L, 2);
    return 0;
}

static int getX_Vector4(lua_State *L)
{
    Math::Vector4* pVector4 = checkVector4(L, 1);

    lua_pushnumber(L, pVector4->m_x);
    return 1;
}

static int getY_Vector4(lua_State *L)
{
    Math::Vector4* pVector4 = checkVector4(L, 1);

    lua_pushnumber(L, pVector4->m_y);
    return 1;
}

static int getZ_Vector4(lua_State *L)
{
    Math::Vector4* pVector4 = checkVector4(L, 1);

    lua_pushnumber(L, pVector4->m_z);
    return 1;
}

static int getW_Vector4(lua_State *L)
{
    Math::Vector4* pVector4 = checkVector4(L, 1);

    lua_pushnumber(L, pVector4->m_w);
    return 1;
}

static const struct luaL_reg Vector4Lib [] = {
    {"setX", setX_Vector4},
    {"setY", setY_Vector4},
    {"setZ", setZ_Vector4},
    {"setW", setW_Vector4},
    {"getX", getX_Vector4},
    {"getY", getY_Vector4},
    {"getZ", getZ_Vector4},
    {"getW", getW_Vector4},
    {NULL, NULL}
};

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~

static const char* sm_pQuaternion4 = "Quaternion4";

static Math::Quaternion4* checkQuaternion4(lua_State *L, int idx)
{
    lua_pushliteral(L, "__zen_userdata");
    lua_gettable(L, idx);

    Math::Quaternion4* pQuaternion4 = (Math::Quaternion4*)luaL_checkudata(L, -1, sm_pQuaternion4);
    luaL_argcheck(L, pQuaternion4 != NULL, idx, "Quaternion4 expected");
    lua_pop(L, 1);

    return pQuaternion4;
}

static int new_Quaternion4 (lua_State *L)
{
    const int top = lua_gettop(L);

    lua_newtable(L);
    const int table = lua_gettop(L);

    Math::Quaternion4* pQuaternion4 = new (lua_newuserdata(L, sizeof(Math::Quaternion4))) Math::Quaternion4();
    const int userdata = lua_gettop(L);

    // T["__zen_userdata"] = UD
    lua_pushvalue(L, userdata);
    lua_setfield(L, table, "__zen_userdata");

    // Get the meta table for this class type and set it for this object
    luaL_getmetatable(L, sm_pQuaternion4);
    lua_setmetatable(L, table);

    // Get the meta table for this class type and set it for the user data
    luaL_getmetatable(L, sm_pQuaternion4);
    lua_setmetatable(L, userdata);

    // Pop the userdata so only the table is left
    lua_pop(L, 1);

    for(int x = 1; x < 5; x++)
    {
        if (x <= top)
        {
            pQuaternion4->m_array[x - 1] = (Zen::Math::Real)lua_tonumber(L, x);
        }
        else
        {
            pQuaternion4->m_array[x - 1] = 0.0f;
        }
    }

    return 1;
}

static int setX_Quaternion4(lua_State *L)
{
    Math::Quaternion4* pQuaternion4 = checkQuaternion4(L, 1);
    pQuaternion4->m_x = checkReal(L, 2);
    return 0;
}

static int setY_Quaternion4(lua_State *L)
{
    Math::Quaternion4* pQuaternion4 = checkQuaternion4(L, 1);
    pQuaternion4->m_y = checkReal(L, 2);
    return 0;
}

static int setZ_Quaternion4(lua_State *L)
{
    Math::Quaternion4* pQuaternion4 = checkQuaternion4(L, 1);
    pQuaternion4->m_z = checkReal(L, 2);
    return 0;
}

static int setW_Quaternion4(lua_State *L)
{
    Math::Quaternion4* pQuaternion4 = checkQuaternion4(L, 1);
    pQuaternion4->m_w = checkReal(L, 2);
    return 0;
}

static int getX_Quaternion4(lua_State *L)
{
    Math::Quaternion4* pQuaternion4 = checkQuaternion4(L, 1);

    lua_pushnumber(L, pQuaternion4->m_x);
    return 1;
}

static int getY_Quaternion4(lua_State *L)
{
    Math::Quaternion4* pQuaternion4 = checkQuaternion4(L, 1);

    lua_pushnumber(L, pQuaternion4->m_y);
    return 1;
}

static int getZ_Quaternion4(lua_State *L)
{
    Math::Quaternion4* pQuaternion4 = checkQuaternion4(L, 1);

    lua_pushnumber(L, pQuaternion4->m_z);
    return 1;
}

static int getW_Quaternion4(lua_State *L)
{
    Math::Quaternion4* pQuaternion4 = checkQuaternion4(L, 1);

    lua_pushnumber(L, pQuaternion4->m_w);
    return 1;
}

static const struct luaL_reg Quaternion4Lib [] = {
    {"setX", setX_Quaternion4},
    {"setY", setY_Quaternion4},
    {"setZ", setZ_Quaternion4},
    {"setW", setW_Quaternion4},
    {"getX", getX_Quaternion4},
    {"getY", getY_Quaternion4},
    {"getZ", getZ_Quaternion4},
    {"getW", getW_Quaternion4},
    {NULL, NULL}
};

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
static const luaL_Reg MathLib[] = {
    {sm_pMatrix4,       new_Matrix4},
    {sm_pVector3,       new_Vector3},
    {sm_pVector4,       new_Vector4},
    {sm_pPoint3,        new_Point3NoArgs},
    {sm_pRadian,        new_Radian},
    {sm_pDegree,        new_Degree},
    {sm_pQuaternion4,   new_Quaternion4},
    {NULL, NULL}
};

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
static const char* sm_pConfig = "Config";

static Config* checkConfig(lua_State *L, int idx)
{
    lua_pushliteral(L, "__zen_userdata");
    lua_gettable(L, idx);

    Config* pConfig = (Config*)luaL_checkudata(L, -1, sm_pConfig);
    luaL_argcheck(L, pConfig != NULL, idx, "Config expected");
    lua_pop(L, 1);

    return pConfig;
}

static int new_Config (lua_State *L)
{
	initTypeHuntMaps();

	// Save the top of the stack.
	// This is the first parameter to the Utility.Config(table);
    const int firstArgument = lua_gettop(L);

    lua_newtable(L);
    const int newTable = lua_gettop(L);

    // In-place creation of a Config
    Config* pConfig = new (lua_newuserdata(L, sizeof(Config))) Config;
    const int userdata = lua_gettop(L);

    // T["__zen_userdata"] = UD
    lua_pushvalue(L, userdata);
    lua_setfield(L, newTable, "__zen_userdata");

    // Get the meta table for this class type and set it for this object
    luaL_getmetatable(L, sm_pConfig);
    lua_setmetatable(L, newTable);

    // Get the meta table for this class type and set it for the user data
    luaL_getmetatable(L, sm_pConfig);
    lua_setmetatable(L, userdata);

    // Pop the userdata so only the table is left
    lua_pop(L, 1);

    // Iterate through the first argument, which should be a table.
    // Push nil as the first key
    lua_pushnil(L);
    int x = 0;

    // Loop through the items in the first argument
    // using the top of the stack as the key.  The first
    // iteration is nil, subsequent iterations are the key.
    // lua_next pushes the next key, next value onto the stack in
    // that order.
    while (lua_next(L, firstArgument) != 0)
    {
    	x++;

    	// Get the type of the key and verify that it's a string
    	if (lua_type(L, -2) != LUA_TSTRING)
    	{
    		// If it's not a string, display an error message.
    		std::cout << "Error: argument " << x << " to Utility.Config() must be a string and not "
    				<< lua_typename(L, -2) << " for key " << lua_tostring(L, -2);

    		// Break out of the loop.
    		break;
    	}

    	// Get the key and the value for this iteration
    	const std::string key = lua_tostring(L, -2);
    	const std::string value = lua_tostring(L, -1);

    	// Save the value in the map using the key.
    	pConfig->m_config[key] = value;

    	// Pop the value off the stack.  Leave the key on the stack so that
    	// the lua_next can use it.
    	lua_pop(L, 1);
    }

    return 1;
}

static const struct luaL_reg ConfigLib [] = {
	// TODO Add getter / setter methods?
    {NULL, NULL}
};

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
static const luaL_Reg UtilityLib[] = {
    {sm_pConfig,       	new_Config},
    {NULL, NULL}
};

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
void
LuaEngine::registerModules()
{
    lua_State* const L = getState();

    luaL_openlibs(L);

    int libTop = lua_gettop(L);

    //-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~

    luaL_register(L, "Math", MathLib);

    int mathTop = lua_gettop(L);

    //-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~

    // Register the Matrix meta table
    luaL_newmetatable(L, sm_pMatrix4);

    // metatable.__index = metatable
    lua_pushstring(L, "__index");
    lua_pushvalue(L, -2);
    lua_settable(L, -3);

    luaL_openlib(L, NULL, MatrixLib, 0);

    // Pop the metatable
    lua_pop(L, 1);
    int top2 = lua_gettop(L);
    assert(mathTop == lua_gettop(L));

    //-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~

    // Register the Point3 meta table
    luaL_newmetatable(L, sm_pPoint3);

    // metatable.__index = metatable
    lua_pushstring(L, "__index");
    lua_pushvalue(L, -2);
    lua_settable(L, -3);

    luaL_openlib(L, NULL, Point3Lib, 0);

    // Pop the metatable
    lua_pop(L, 1);
    assert(mathTop == lua_gettop(L));

    //-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~

    // Register the Radian meta table
    luaL_newmetatable(L, sm_pRadian);

    // metatable.__index = metatable
    lua_pushstring(L, "__index");
    lua_pushvalue(L, -2);
    lua_settable(L, -3);

    luaL_openlib(L, NULL, RadianLib, 0);

    // Pop the metatable
    lua_pop(L, 1);
    assert(mathTop == lua_gettop(L));

    //-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~

    // Register the Degree meta table
    luaL_newmetatable(L, sm_pDegree);

    // metatable.__index = metatable
    lua_pushstring(L, "__index");
    lua_pushvalue(L, -2);
    lua_settable(L, -3);

    luaL_openlib(L, NULL, DegreeLib, 0);

    // Pop the metatable
    lua_pop(L, 1);
    assert(mathTop == lua_gettop(L));

    //-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~

    // Register the Vector3 meta table
    luaL_newmetatable(L, sm_pVector3);

    // metatable.__index = metatable
    lua_pushstring(L, "__index");
    lua_pushvalue(L, -2);
    lua_settable(L, -3);

    // metatable.__gc function
    lua_pushstring(L, "__gc");
    lua_pushcfunction(L, destroy_Vector3);
    lua_settable(L, -3);

    // metatable.__add function
    lua_pushstring(L, "__add");
    lua_pushcfunction(L, add_Vector3);
    lua_settable(L, -3);

    luaL_openlib(L, NULL, Vector3Lib, 0);

    // Pop the metatable
    lua_pop(L, 1);
    assert(mathTop == lua_gettop(L));

    //-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~

    // Register the Vector4 meta table
    luaL_newmetatable(L, sm_pVector4);

    // metatable.__index = metatable
    lua_pushstring(L, "__index");
    lua_pushvalue(L, -2);
    lua_settable(L, -3);

    luaL_openlib(L, NULL, Vector4Lib, 0);

    // Pop the metatable
    lua_pop(L, 1);
    assert(mathTop == lua_gettop(L));

    //-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~

    // Register the Quaternion4 meta table
    luaL_newmetatable(L, sm_pQuaternion4);

    // metatable.__index = metatable
    lua_pushstring(L, "__index");
    lua_pushvalue(L, -2);
    lua_settable(L, -3);

    luaL_openlib(L, NULL, Quaternion4Lib, 0);

    // Pop the metatable
    lua_pop(L, 1);
    assert(mathTop == lua_gettop(L));

    //-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~

    lua_pop(L,1);
    assert(libTop == lua_gettop(L));

    //-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~

    luaL_register(L, "Utility", UtilityLib);

    int utilityTop = lua_gettop(L);

    // Register the Config meta table
    luaL_newmetatable(L, sm_pConfig);

    // metatable.__index = metatable
    lua_pushstring(L, "__index");
    lua_pushvalue(L, -2);
    lua_settable(L, -3);

    luaL_openlib(L, NULL, ConfigLib, 0);

    // Pop the metatable
    lua_pop(L, 1);
    assert(utilityTop == lua_gettop(L));

    //-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~

    lua_pop(L, 1);
    assert(libTop == lua_gettop(L));
}

//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
}   // namespace ZLua
}   // namespace Zen
//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~