/* Copyright (C) 2010 The gtkmm 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, see <http://www.gnu.org/licenses/>.
 */

// ugly code ahead.

#include <iostream>

#include <gio/gio.h>

#include <glibmm/vectorutils.h>

#include <giomm/credentials.h>
#include <giomm/init.h>

// utilities

// Use this line if you want debug output:
// std::ostream& ostr = std::cout;

// This seems nicer and more useful than putting an ifdef around the use of std::cout:
std::stringstream debug;
std::ostream& ostr = debug;

const unsigned int magic_limit(5);

GList*
create_list()
{
  GList* head = nullptr;

  for (unsigned int iter(0); iter < magic_limit; ++iter)
  {
    head = g_list_prepend(head, g_credentials_new());
  }

  return g_list_reverse(head);
}

void
print_list(GList* list)
{
  unsigned int counter(1);

  for (GList *node(list); node; node = node->next, ++counter)
  {
    ostr << counter << ": ";
    if (G_IS_CREDENTIALS(node->data))
    {
      ostr << node->data << ", ref: " << G_OBJECT(node->data)->ref_count << "\n";
    }
    else
    {
      ostr << "no C instance?\n";
    }
  }
}

GSList*
create_slist()
{
  GSList* head = nullptr;

  for (unsigned int iter(0); iter < magic_limit; ++iter)
  {
    head = g_slist_prepend(head, g_credentials_new());
  }

  return g_slist_reverse(head);
}

void
print_slist(GSList* slist)
{
  unsigned int counter(1);

  for (GSList *node(slist); node; node = node->next, ++counter)
  {
    ostr << counter << ": ";
    if (G_IS_CREDENTIALS(node->data))
    {
      ostr << node->data << ", ref: " << G_OBJECT(node->data)->ref_count << "\n";
    }
    else
    {
      ostr << "no C instance?\n";
    }
  }
}

GCredentials**
create_array()
{
  GCredentials** array = g_new0(GCredentials*, magic_limit + 1);

  for (unsigned int iter(0); iter < magic_limit; ++iter)
  {
    array[iter] = g_credentials_new();
  }
  return array;
}

void
print_array(GCredentials** array)
{
  for (unsigned int iter(0); iter < magic_limit; ++iter)
  {
    GCredentials* credentials(array[iter]);

    ostr << iter + 1 << ": ";
    if (G_IS_CREDENTIALS(credentials))
    {
      ostr << reinterpret_cast<gpointer>(credentials)
           << ", ref: " << G_OBJECT(credentials)->ref_count << "\n";
    }
    else
    {
      ostr << "no C instance?\n";
    }
  }
}

// shallow copy
GCredentials**
copy_array(GCredentials** array)
{
  GCredentials** dup = g_new0(GCredentials*, magic_limit + 1);

  for (unsigned int iter(0); iter < magic_limit; ++iter)
  {
    dup[iter] = array[iter];
  }
  dup[magic_limit] = nullptr;
  return dup;
}

void
free_array(GCredentials** array, bool container_too = true)
{
  for (unsigned int iter(0); iter < magic_limit; ++iter)
  {
    g_object_unref(array[iter]);
  }
  if (container_too)
  {
    g_free(array);
  }
}

void
print_vector(const std::vector<Glib::RefPtr<Gio::Credentials>>& v)
{
  const unsigned int size(v.size());

  for (unsigned int iter(0); iter < size; ++iter)
  {
    const Glib::RefPtr<Gio::Credentials>& obj_ptr(v[iter]);

    ostr << iter + 1 << ": ";
    if (obj_ptr)
    {
      GCredentials* gobj(obj_ptr->gobj());

      if (G_IS_CREDENTIALS(gobj))
      {
        ostr << static_cast<gpointer>(gobj) << ", ref: " << G_OBJECT(gobj)->ref_count << "\n";
      }
      else
      {
        ostr << "No C instance?\n";
      }
    }
    else
    {
      ostr << "No C++ instance?\n";
    }
  }
}

struct Cache
{
public:
  Cache() : glist_(create_list()), gslist_(create_slist()), garray_(create_array()) {}

  ~Cache()
  {
    if (glist_)
    {
      g_list_foreach(glist_, reinterpret_cast<GFunc>(g_object_unref), nullptr);
      g_list_free(glist_);
    }
    if (gslist_)
    {
      g_slist_foreach(gslist_, reinterpret_cast<GFunc>(g_object_unref), nullptr);
      g_slist_free(gslist_);
    }
    if (garray_)
    {
      free_array(garray_);
    }
  }

  GList* get_list() const { return glist_; }

  GSList* get_slist() const { return gslist_; }

  GCredentials** get_array() const { return garray_; }

private:
  // just in case
  Cache(const Cache&);
  Cache operator=(const Cache&);

  GList* glist_;
  GSList* gslist_;
  GCredentials** garray_;
};

Cache&
get_cache()
{
  static Cache global_cache;

  return global_cache;
}

// C functions

GList*
c_get_deep_owned_list()
{
  return get_cache().get_list();
}

GList*
c_get_shallow_owned_list()
{
  return g_list_copy(c_get_deep_owned_list());
}

GList*
c_get_unowned_list()
{
  return create_list();
}

GSList*
c_get_deep_owned_slist()
{
  return get_cache().get_slist();
}

GSList*
c_get_shallow_owned_slist()
{
  return g_slist_copy(c_get_deep_owned_slist());
}

GSList*
c_get_unowned_slist()
{
  return create_slist();
}

GCredentials**
c_get_deep_owned_array()
{
  return get_cache().get_array();
}

GCredentials**
c_get_shallow_owned_array()
{
  return copy_array(c_get_deep_owned_array());
}

GCredentials**
c_get_unowned_array()
{
  return create_array();
}

/* these are probably buggy by design...
void
c_take_list_all(GList* list)
{
  if(list)
  {
    print_list(list);
    g_list_foreach(list, reinterpret_cast<GFunc>(g_object_unref), 0);
    g_list_free(list);
  }
}

void
c_take_list_members(GList* list)
{
  if(list)
  {
    print_list(list);
    g_list_foreach(list, reinterpret_cast<GFunc>(g_object_unref), 0);
  }
}
*/

void
c_take_list_nothing(GList* list)
{
  if (list)
  {
    print_list(list);
  }
}

/* they are probably buggy by design...
void
c_take_slist_all(GSList* slist)
{
  if(slist)
  {
    print_slist(slist);
    g_slist_foreach(slist, reinterpret_cast<GFunc>(g_object_unref), 0);
    g_slist_free(slist);
  }
}

void
c_take_list_members(GSList* slist)
{
  if(slist)
  {
    print_slist(slist);
    g_slist_foreach(slist, reinterpret_cast<GFunc>(g_object_unref), 0);
  }
}
*/

void
c_take_slist_nothing(GSList* slist)
{
  if (slist)
  {
    print_slist(slist);
  }
}

/* they are probably buggy by design...
void
c_take_array_all(GCredentials** array)
{
  if(array)
  {
    print_array(array);
    free_array(array);
  }
}

void
c_take_array_members(GCredentials** array)
{
  if(array)
  {
    print_array(array);
    free_array(array, false);
  }
}
*/

void
c_take_array_nothing(GCredentials** array)
{
  if (array)
  {
    print_array(array);
  }
}

// C++ wrappers.

std::vector<Glib::RefPtr<Gio::Credentials>>
cxx_get_deep_owned_list()
{
  return Glib::ListHandler<Glib::RefPtr<Gio::Credentials>>::list_to_vector(
    c_get_deep_owned_list(), Glib::OWNERSHIP_NONE);
}

std::vector<Glib::RefPtr<Gio::Credentials>>
cxx_get_shallow_owned_list()
{
  return Glib::ListHandler<Glib::RefPtr<Gio::Credentials>>::list_to_vector(
    c_get_shallow_owned_list(), Glib::OWNERSHIP_SHALLOW);
}

std::vector<Glib::RefPtr<Gio::Credentials>>
cxx_get_unowned_list()
{
  return Glib::ListHandler<Glib::RefPtr<Gio::Credentials>>::list_to_vector(
    c_get_unowned_list(), Glib::OWNERSHIP_DEEP);
}

std::vector<Glib::RefPtr<Gio::Credentials>>
cxx_get_deep_owned_slist()
{
  return Glib::SListHandler<Glib::RefPtr<Gio::Credentials>>::slist_to_vector(
    c_get_deep_owned_slist(), Glib::OWNERSHIP_NONE);
}

std::vector<Glib::RefPtr<Gio::Credentials>>
cxx_get_shallow_owned_slist()
{
  return Glib::SListHandler<Glib::RefPtr<Gio::Credentials>>::slist_to_vector(
    c_get_shallow_owned_slist(), Glib::OWNERSHIP_SHALLOW);
}

std::vector<Glib::RefPtr<Gio::Credentials>>
cxx_get_unowned_slist()
{
  return Glib::SListHandler<Glib::RefPtr<Gio::Credentials>>::slist_to_vector(
    c_get_unowned_slist(), Glib::OWNERSHIP_DEEP);
}

std::vector<Glib::RefPtr<Gio::Credentials>>
cxx_get_deep_owned_array()
{
  return Glib::ArrayHandler<Glib::RefPtr<Gio::Credentials>>::array_to_vector(
    c_get_deep_owned_array(), Glib::OWNERSHIP_NONE);
}

std::vector<Glib::RefPtr<Gio::Credentials>>
cxx_get_shallow_owned_array()
{
  return Glib::ArrayHandler<Glib::RefPtr<Gio::Credentials>>::array_to_vector(
    c_get_shallow_owned_array(), Glib::OWNERSHIP_SHALLOW);
}

std::vector<Glib::RefPtr<Gio::Credentials>>
cxx_get_unowned_array()
{
  return Glib::ArrayHandler<Glib::RefPtr<Gio::Credentials>>::array_to_vector(
    c_get_unowned_array(), Glib::OWNERSHIP_DEEP);
}

/* they are probably buggy by design...
void
cxx_list_take_all(const std::vector<Glib::RefPtr<Gio::Credentials> >& v)
{
  c_take_list_all(Glib::ListHandler<Glib::RefPtr<Gio::Credentials> >::vector_to_list(v).data());
}

void
cxx_list_take_members(const std::vector<Glib::RefPtr<Gio::Credentials> >& v)
{
  c_take_list_members(Glib::ListHandler<Glib::RefPtr<Gio::Credentials> >::vector_to_list(v).data());
}
*/

void
cxx_list_take_nothing(const std::vector<Glib::RefPtr<Gio::Credentials>>& v)
{
  c_take_list_nothing(Glib::ListHandler<Glib::RefPtr<Gio::Credentials>>::vector_to_list(v).data());
}

/* they are probably buggy by design...
void
cxx_slist_take_all(const std::vector<Glib::RefPtr<Gio::Credentials> >& v)
{
  c_take_slist_all(Glib::SListHandler<Glib::RefPtr<Gio::Credentials> >::vector_to_slist(v).data());
}

void
cxx_slist_take_members(const std::vector<Glib::RefPtr<Gio::Credentials> >& v)
{
  c_take_slist_members(Glib::SListHandler<Glib::RefPtr<Gio::Credentials>
>::vector_to_slist(v).data());
}
*/

void
cxx_slist_take_nothing(const std::vector<Glib::RefPtr<Gio::Credentials>>& v)
{
  c_take_slist_nothing(
    Glib::SListHandler<Glib::RefPtr<Gio::Credentials>>::vector_to_slist(v).data());
}

/* they are probably buggy by design...
void
cxx_array_take_all(const std::vector<Glib::RefPtr<Gio::Credentials> >& v)
{
  c_take_array_all(Glib::ArrayHandler<Glib::RefPtr<Gio::Credentials> >::vector_to_array(v).data());
}

void
cxx_array_take_members(const std::vector<Glib::RefPtr<Gio::Credentials> >& v)
{
  c_take_array_members(Glib::ArrayHandler<Glib::RefPtr<Gio::Credentials>
>::vector_to_array(v).data());
}
*/

void
cxx_array_take_nothing(const std::vector<Glib::RefPtr<Gio::Credentials>>& v)
{
  c_take_array_nothing(
    Glib::ArrayHandler<Glib::RefPtr<Gio::Credentials>>::vector_to_array(v).data());
}

int
main()
{
  Gio::init();

  Cache& cache(get_cache());

  ostr << "Cache list before:\n";
  print_list(cache.get_list());
  ostr << "Cache slist before:\n";
  print_slist(cache.get_slist());
  ostr << "Cache array before:\n";
  print_array(cache.get_array());
  ostr << "Deep owned list:\n";
  print_vector(cxx_get_deep_owned_list());
  ostr << "Shallow owned list:\n";
  print_vector(cxx_get_shallow_owned_list());
  ostr << "Unowned list:\n";
  print_vector(cxx_get_unowned_list());
  ostr << "Deep owned slist:\n";
  print_vector(cxx_get_deep_owned_slist());
  ostr << "Shallow owned slist:\n";
  print_vector(cxx_get_shallow_owned_slist());
  ostr << "Unowned slist:\n";
  print_vector(cxx_get_unowned_slist());
  ostr << "Deep owned array:\n";
  print_vector(cxx_get_deep_owned_array());
  ostr << "Shallow owned array:\n";
  print_vector(cxx_get_shallow_owned_array());
  ostr << "Unowned array:\n";
  print_vector(cxx_get_unowned_array());
  ostr << "Cache list after:\n";
  print_list(cache.get_list());
  ostr << "Cache slist after:\n";
  print_slist(cache.get_slist());
  ostr << "Cache array after:\n";
  print_array(cache.get_array());

  std::vector<Glib::RefPtr<Gio::Credentials>> v(cxx_get_unowned_list());

  ostr << "Gotten vector before:\n";
  print_vector(v);
  // I am wondering if C functions wrapped by the ones below are not buggy by
  // design. Anyway - it segfaults. Maybe the test case is just wrong.
  // ostr << "Take list all:\n";
  // cxx_list_take_all(v);
  // ostr << "Take list members:\n";
  // cxx_list_take_members(v);
  ostr << "Take list nothing:\n";
  cxx_list_take_nothing(v);
  // Ditto.
  // ostr << "Take slist all:\n";
  // cxx_slist_take_all(v);
  // ostr << "Take slist members:\n";
  // cxx_slist_take_members(v);
  ostr << "Take slist nothing:\n";
  cxx_slist_take_nothing(v);
  // Ditto.
  // ostr << "Take array all:\n";
  // cxx_array_take_all(v);
  // ostr << "Take array members:\n";
  // cxx_array_take_members(v);
  ostr << "Take array nothing:\n";
  cxx_array_take_nothing(v);
  ostr << "Gotten vector after:\n";
  print_vector(v);
}
