/* Copyright 2010 The glibmm Development Team
 *
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <glibmm/variant.h>
#include <glibmm/utility.h>
#include <glib.h>
#include <iostream>

namespace Glib
{

void VariantBase::get_normal_form(VariantBase& result) const
{
  GVariant* const g_value =
    g_variant_get_normal_form(const_cast<GVariant*>(gobj()));

  //The C function never returns NULL, according to its docuemenation,
  //so we don't need a bool return value.
  result.init(g_value); // g_value is already referenced.
}

void VariantBase::byteswap(VariantBase& result) const
{
  GVariant* const g_value = g_variant_byteswap(const_cast<GVariant*>(gobj()));
  result.init(g_value); // g_value is already referenced.
}


VariantStringBase::VariantStringBase()
: VariantBase()
{
}

VariantStringBase::VariantStringBase(GVariant* castitem, bool take_a_reference)
: VariantBase(castitem, take_a_reference)
{
}

//static
void VariantStringBase::create_object_path(VariantStringBase& output,
  const std::string& object_path)
{
  GVariant* result = 0;
  result = g_variant_new_object_path(object_path.c_str());
  g_variant_ref_sink(result);
  output.init(result);
}

//static
void VariantStringBase::create_signature(VariantStringBase& output,
  const std::string& signature)
{
  GVariant* result = 0;
  result = g_variant_new_signature(signature.c_str());
  g_variant_ref_sink(result);
  output.init(result);
}


VariantContainerBase::VariantContainerBase()
: VariantBase()
{
}

VariantContainerBase::VariantContainerBase(GVariant* castitem, bool take_a_reference)
: VariantBase(castitem, take_a_reference)
{
}

//static
VariantContainerBase
VariantContainerBase::create_tuple(const std::vector<VariantBase>& children)
{
  typedef GVariant* var_ptr;
  var_ptr* const var_array = new var_ptr[children.size()];

  for(std::vector<VariantBase>::size_type i = 0; i < children.size(); i++)
  {
    var_array[i] = const_cast<GVariant*>(children[i].gobj());
  }

  VariantContainerBase result = VariantContainerBase(g_variant_new_tuple(
    var_array, children.size()));

  g_variant_ref_sink(result.gobj());

  delete[] var_array;

  return result;
}

//static
VariantContainerBase
VariantContainerBase::create_tuple(const VariantBase& child)
{
  std::vector<VariantBase> vec;
  vec.push_back(child);
  return create_tuple(vec);
}

void VariantContainerBase::get_child(VariantBase& child, gsize index) const
{
  if(index >= g_variant_n_children(gobject_))
    throw std::out_of_range(
      "VariantContainerBase::get(): Index out of bounds.");

  GVariant* const gvariant = g_variant_get_child_value(gobject_, index);
  child.init(gvariant);
}

// VariantContainerBase has no method variant_type()
template<>
VariantContainerBase VariantBase::cast_dynamic<VariantContainerBase>(const VariantBase& v)
throw(std::bad_cast)
{
  if(!v.gobj())
  {
    return VariantContainerBase();
  }
  if(v.get_type().is_container())
  {
    return VariantContainerBase(const_cast<GVariant*>(v.gobj()), true);
  }
  else
  {
    //std::cerr << "vtype=" << v.get_type_string() << std::endl;
    throw std::bad_cast();
  }
}

bool VariantContainerBase::get_maybe(VariantBase& maybe) const
{
  GVariant* const g_value =
    g_variant_get_maybe(const_cast<GVariant*>(gobj()));

  if(g_value)
  {
    maybe.init(g_value); // g_value is already referenced.
    return true;
  }
  else
    return false;
}

/****************** Specializations ***********************************/

void VariantBase::init(const GVariant* cobject, bool take_a_reference)
{
  if(gobject_)
    g_variant_unref(gobject_);

  gobject_ = const_cast<GVariant*>(cobject);
  if(take_a_reference)
    g_variant_ref(gobject_);
}


Variant<VariantBase>::Variant()
: VariantContainerBase()
{
}

Variant<VariantBase>::Variant(GVariant* castitem, bool take_a_reference)
: VariantContainerBase(castitem, take_a_reference)
{
}

// static
const VariantType& Variant<VariantBase>::variant_type()
{
  return VARIANT_TYPE_VARIANT;
}

Variant<VariantBase> Variant<VariantBase>::create(const VariantBase& data)
{
  Variant<VariantBase> result = Variant<VariantBase>(g_variant_new_variant(const_cast<GVariant*>(data.gobj())));

  // Remove the floating reference (since it is newly created).
  g_variant_ref_sink(result.gobj());

  return result;
}

void Variant<VariantBase>::get(VariantBase& variant) const
{
  GVariant* const gvariant = g_variant_get_variant(gobject_);
  variant.init(gvariant);
}


Variant<Glib::ustring>::Variant()
: VariantStringBase()
{
}

Variant<Glib::ustring>::Variant(GVariant* castitem, bool take_a_reference)
: VariantStringBase(castitem, take_a_reference)
{
}

// static
const VariantType& Variant<Glib::ustring>::variant_type()
{
  return VARIANT_TYPE_STRING;
}

Variant<Glib::ustring>
Variant<Glib::ustring>::create(const Glib::ustring& data)
{
  Variant<Glib::ustring> result =
    Variant<Glib::ustring>(g_variant_new_string(data.c_str()));

  // Remove the floating reference (since it is newly created).
  g_variant_ref_sink(result.gobj());

  return result;
}

Glib::ustring Variant<Glib::ustring>::get() const
{
  return convert_const_gchar_ptr_to_ustring(g_variant_get_string(gobject_, 0));
}

// Variant<Glib::ustring> makes sense for multiple types.
// See http://library.gnome.org/devel/glib/unstable/glib-GVariant.html#g-variant-get-string
template<>
Variant<Glib::ustring> VariantBase::cast_dynamic< Variant<Glib::ustring> >(const VariantBase& v)
throw(std::bad_cast)
{
  if(!v.gobj())
  {
    return Variant<Glib::ustring>();
  }

  const VariantType vtype = v.get_type();
  if( vtype.equal(VARIANT_TYPE_STRING) ||
      vtype.equal(VARIANT_TYPE_OBJECT_PATH) ||
      vtype.equal(VARIANT_TYPE_SIGNATURE) )
  {
    return Variant<Glib::ustring>(const_cast<GVariant*>(v.gobj()), true);
  }
  else
  {
    //std::cerr << "vtype=" << v.get_type_string() << std::endl;
    throw std::bad_cast();
  }
}

Variant<std::string>::Variant()
: VariantStringBase()
{
}

Variant<std::string>::Variant(GVariant* castitem, bool take_a_reference)
: VariantStringBase(castitem, take_a_reference)
{
}

// static
const VariantType& Variant<std::string>::variant_type()
{
  return VARIANT_TYPE_BYTESTRING;
}

Variant<std::string>
Variant<std::string>::create(const std::string& data)
{
  Variant<std::string> result =
    Variant<std::string>(g_variant_new_bytestring(data.c_str()));

  // Remove the floating reference (since it is newly created).
  g_variant_ref_sink(result.gobj());

  return result;
}

// Variant<std::string> makes sense for multiple types.
// See http://library.gnome.org/devel/glib/unstable/glib-GVariant.html#g-variant-get-string
template<>
Variant<std::string> VariantBase::cast_dynamic< Variant<std::string> >(const VariantBase& v)
throw(std::bad_cast)
{
  if(!v.gobj())
  {
    return Variant<std::string>();
  }

  const VariantType vtype = v.get_type();
  if( vtype.equal(VARIANT_TYPE_STRING) ||
      vtype.equal(VARIANT_TYPE_BYTESTRING) ||
      vtype.equal(VARIANT_TYPE_OBJECT_PATH) ||
      vtype.equal(VARIANT_TYPE_SIGNATURE) )
  {
    return Variant<std::string>(const_cast<GVariant*>(v.gobj()), true);
  }
  else
  {
    //std::cerr << "vtype=" << v.get_type_string() << std::endl;
    throw std::bad_cast();
  }
}

std::string Variant<std::string>::get() const
{
  const VariantType vtype = get_type();

  const char* pch = 0;
  if(vtype.equal(VARIANT_TYPE_BYTESTRING))
    pch = g_variant_get_bytestring(gobject_);
  else //g_variant_get_string() cna handle strings, object paths, and signatures.
    pch = g_variant_get_string(gobject_, 0);

  return convert_const_gchar_ptr_to_stdstring(pch);
}

typedef std::vector<Glib::ustring> type_vec_ustring;

Variant<type_vec_ustring>::Variant()
: VariantContainerBase()
{
}

Variant<type_vec_ustring>::Variant(GVariant* castitem, bool take_a_reference)
: VariantContainerBase(castitem, take_a_reference)
{
}

// static
const VariantType& Variant<type_vec_ustring>::variant_type()
{
  return VARIANT_TYPE_STRING_ARRAY;
}

Variant<type_vec_ustring>
Variant<type_vec_ustring>::create(const type_vec_ustring& data)
{
  // Get the variant type of the elements.
  VariantType element_variant_type = Variant<Glib::ustring>::variant_type();

  // Get the variant type of the array.
  VariantType array_variant_type = Variant<type_vec_ustring>::variant_type();

  // Create a GVariantBuilder to build the array.
  GVariantBuilder* builder = g_variant_builder_new(array_variant_type.gobj());

  // Add the elements of the vector into the builder.
  for(type_vec_ustring::const_iterator iter = data.begin();
   iter < data.end(); iter++)
  {
    g_variant_builder_add(builder,
      element_variant_type.get_string().c_str(), iter->c_str());
  }

  // Create the variant using the builder.
  Variant<type_vec_ustring> result =
    Variant<type_vec_ustring>(g_variant_new(
      array_variant_type.get_string().c_str(), builder));

  // Remove the floating reference (since it is newly created).
  g_variant_ref_sink(result.gobj());

  return result;
}

Glib::ustring Variant<type_vec_ustring>::get_child(gsize index) const
{
  gsize n_elements = 0;

  const gchar** array = g_variant_get_strv(const_cast<GVariant*>(gobj()),
    &n_elements);

  if(index >= n_elements)
    throw std::out_of_range(
      "Variant< std::vector<Glib::ustring> >::get(): Index out of bounds.");

  Glib::ustring const result(array[index]);
  g_free(array);
  return result;
}

type_vec_ustring Variant<type_vec_ustring>::get() const
{
  gsize n_elements = 0;

  const gchar** array = g_variant_get_strv(const_cast<GVariant*>(gobj()),
    &n_elements);

  type_vec_ustring const result(array, array + n_elements);
  g_free(array);
  return result;
}

VariantIter Variant<type_vec_ustring>::get_iter() const
{
  // Get the variant type of the elements.
  VariantType element_variant_type = Variant<Glib::ustring>::variant_type();

  // Get the variant type of the array.
  VariantType array_variant_type = Variant<type_vec_ustring>::variant_type();

  // Get the GVariantIter.
  GVariantIter* g_iter = 0;
  g_variant_get(const_cast<GVariant*>(gobj()),
    array_variant_type.get_string().c_str(), &g_iter);

  return VariantIter(g_iter);
}

typedef std::vector<std::string> type_vec_string;

Variant<type_vec_string>::Variant()
: VariantContainerBase()
{
}

Variant<type_vec_string>::Variant(GVariant* castitem, bool take_a_reference)
: VariantContainerBase(castitem, take_a_reference)
{
}

// static
const VariantType& Variant<type_vec_string>::variant_type()
{
  return VARIANT_TYPE_BYTESTRING_ARRAY;
}

Variant<type_vec_string>
Variant<type_vec_string>::create(const type_vec_string& data)
{
  // Create a string array to add the strings of the vector to.
  char** str_array = g_new(char*, data.size() + 1);

  // Add the elements of the vector into the string array.
  for(type_vec_string::size_type i = 0; i < data.size(); i++)
  {
    str_array[i] = g_strdup(data[i].c_str());
  }

  // Terminate the string array.
  str_array[data.size()] = NULL;

  // Create the variant using the builder.
  Variant<type_vec_string> result =
    Variant<type_vec_string>(g_variant_new_bytestring_array(str_array,
      data.size()));

  g_strfreev(str_array);

  // Remove the floating reference (since it is newly created).
  g_variant_ref_sink(result.gobj());

  return result;
}

std::string Variant<type_vec_string>::get_child(gsize index) const
{
  gsize n_elements = 0;

  const gchar** array =
    g_variant_get_bytestring_array(const_cast<GVariant*>(gobj()), &n_elements);

  if(index >= n_elements)
    throw std::out_of_range(
      "Variant< std::vector<std::string> >::get(): Index out of bounds.");

  std::string const result(array[index]);
  g_free(array);
  return result;
}

type_vec_string Variant<type_vec_string>::get() const
{
  gsize n_elements = 0;

  const gchar** array =
    g_variant_get_bytestring_array(const_cast<GVariant*>(gobj()), &n_elements);

  type_vec_string const result(array, array + n_elements);
  g_free(array);
  return result;
}

VariantIter Variant<type_vec_string>::get_iter() const
{
  // Get the variant type of the elements.
  const VariantType element_variant_type = Variant<std::string>::variant_type();

  // Get the variant type of the array.
  const VariantType array_variant_type = Variant<type_vec_string>::variant_type();

  // Get the GVariantIter.
  GVariantIter* g_iter = 0;
  g_variant_get(const_cast<GVariant*>(gobj()),
    array_variant_type.get_string().c_str(), &g_iter);

  return VariantIter(g_iter);
}

} // namespace Glib
