/* Copyright (c) 1997-2010
   Ewgenij Gawrilow, Michael Joswig (Technische Universitaet Darmstadt, Germany)
   http://www.polymake.de

   This program is free software; you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by the
   Free Software Foundation; either version 2, or (at your option) any
   later version: http://www.gnu.org/licenses/gpl.txt.

   This program 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 General Public License for more details.
--------------------------------------------------------------------------------
   $Project: polymake $$Id: Object.h 9738 2010-07-31 21:28:00Z gawrilow $
*/

#ifndef _POLYMAKE_PERL_OBJECT_H
#define _POLYMAKE_PERL_OBJECT_H

#include <sstream>

namespace pm { namespace perl {

class PropertyValue;
PropertyValue load_data(const std::string& filename);

void _save_data(SV*, const std::string&, const std::string&);

class PropertyValue : public Value {
   friend class Object;
   friend class ObjectType;
   friend PropertyValue load_data(const std::string& filename);

   explicit PropertyValue(SV *sv_arg, value_flags opt_arg=value_flags(0))
      : Value(sv_arg, opt_arg) {}

   void operator= (const PropertyValue&);	// undefined
public:
   PropertyValue(const PropertyValue& pv)
      : Value(pv.sv) { pm_perl_incr_SV(sv); }

   ~PropertyValue() { pm_perl_decr_SV(sv); }
};

enum property_type { _normal, temporary, attachment };

class PropertyOut : public Value {
   friend class Object;

   SV *obj;
   const char *name;
   size_t nl;
   property_type t;
   void put() const;
protected:
   PropertyOut(SV *obj_arg, const char *name_arg, size_t l_arg, property_type t_arg)
      : Value(value_flags(t_arg==_normal ? value_read_only :
			  t_arg==temporary ? value_allow_non_persistent|value_read_only : 0)),
	obj(obj_arg), name(name_arg), nl(l_arg), t(t_arg) {}

public:
   template <typename Source>
   void operator<< (const Source& x) const
   {
      static_cast<const Value&>(*this) << x;
      put();
   }
};

class ObjectType {
   friend class Value;
   friend class Object;

protected:
   SV *obj_ref;

   bool _isa(const char *type_name, size_t tl) const;
   void find(const char *type_name, size_t tl) { obj_ref=get_type(type_name,tl); }
   static SV* construct_type(const char *type_name, size_t tl, SV** (*fill)(SV**));
   explicit ObjectType(SV *r) : obj_ref(r) {}
public:
   template <size_t l>
   explicit ObjectType(const char (&type_name)[l])
   {
      find(type_name,l-1);
   }

   explicit ObjectType(const std::string& type_name)
   {
      find(type_name.c_str(), type_name.size());
   }

   ObjectType() : obj_ref(0) {}
   ObjectType(const ObjectType& o) : obj_ref(pm_perl_init_ref(o.obj_ref)) {}
   ~ObjectType() { pm_perl_decr_SV(obj_ref); }

   // construct a parameterized object type
   template <typename TypeList, size_t tl>
   static ObjectType construct(const char (&type_name)[tl])
   {
      return ObjectType(construct_type(type_name, tl-1, TypeListUtils<TypeList>::push()));
   }

   template <typename TypeList>
   static ObjectType construct(const std::string& type_name)
   {
      return ObjectType(construct_type(type_name.c_str(), type_name.size(), TypeListUtils<TypeList>::push()));
   }

   ObjectType& operator= (const ObjectType& o)
   {
      pm_perl_copy_ref(o.obj_ref, &obj_ref);
      return *this;
   }

   std::string name() const;
   std::string generic_name() const;

   bool isa(const ObjectType& o) const;

   template <size_t l>
   bool isa(const char (&type_name)[l]) const { return _isa(type_name,l-1); }
   bool isa(const std::string& type_name) const { return _isa(type_name.c_str(),type_name.size()); }
};

class Object {
   friend class Value;

protected:
   SV *obj_ref;
   mutable bool needs_commit;

   void _create(const ObjectType& type, const char *name=0, size_t nl=0);
   void _create_copy(const ObjectType& type, const Object& src);
   bool _isa(const char *type_name, size_t tl) const;

   explicit Object(SV *ref_arg) : obj_ref(ref_arg), needs_commit(false) {}
public:
   void create_new(const ObjectType& type)
   {
      pm_perl_decr_SV(obj_ref);
      _create(type);
   }

   bool valid() const { return obj_ref!=0; }

   // construct an empty object of the given type
   explicit Object(const ObjectType& type) { _create(type); }

   template <size_t l>
   explicit Object(const char (&type_name)[l]) { _create(ObjectType(type_name)); }

   explicit Object(const std::string& type_name) { _create(ObjectType(type_name)); }

   // construct an empty object of the given type and with the given name
   Object(const ObjectType& type, const std::string& name) { _create(type,name.c_str(),name.size()); }

   template <size_t l>
   explicit Object(const char (&type_name)[l], const std::string& name) { _create(ObjectType(type_name),name.c_str(),name.size()); }

   explicit Object(const std::string& type_name, const std::string& name) { _create(ObjectType(type_name),name.c_str(),name.size()); }

   // construct a copy of another object with possible property conversion
   Object(const ObjectType& type, const Object& src) { _create_copy(type,src); }

   template <size_t l>
   Object(const char (&type_name)[l], const Object& src) { _create_copy(ObjectType(type_name),src); }

   Object(const std::string& type_name, const Object& src) { _create_copy(ObjectType(type_name),src); }

   // construct an exact copy (up to temporary properties and local extensions)
   Object copy() const;

   // construct a really empty object without type
   Object() : obj_ref(0), needs_commit(false) {}

   // doesn't copy the Object, but merely creates a second reference to it!
   Object(const Object& o) : obj_ref(pm_perl_init_ref(o.obj_ref)), needs_commit(o.needs_commit) {}
   ~Object() { pm_perl_decr_SV(obj_ref); }

   Object& operator= (const Object& o)
   {
      pm_perl_copy_ref(o.obj_ref, &obj_ref);
      return *this;
   }

   // change the object type, possibly converting or discarding properties
   Object& cast(const ObjectType& type);

   template <size_t l>
   Object& cast(const char (&type_name)[l]) { return cast(ObjectType(type_name)); }

   Object& cast(const std::string& type_name) { return cast(ObjectType(type_name)); }

   std::string name() const;
   void set_name(const std::string& name);

   std::string description() const;
   void set_description(const std::string& value, bool append=false);

   template <bool append>
   class description_ostream {
      friend class Object;
   protected:
      mutable Object *obj;
      mutable std::ostringstream content;
      mutable PlainPrinter<> os;

      description_ostream(Object* obj_arg) : obj(obj_arg), os(content) {}
   public:
      description_ostream(const description_ostream& o) : obj(o.obj), os(content) { o.obj=0; }

      template <typename Source>
      PlainPrinter<>& operator<< (const Source& x) const
      {
	 return os << x;
      }

      ~description_ostream() { if (obj) obj->set_description(content.str(), append); }
   };

   description_ostream<false> set_description() { return this; }
   description_ostream<true> append_description() { return this; }

protected:
   SV* _give(const char *name, size_t nl) const;
   SV* _give_with_property_name(const char *name, size_t nl, std::string& given) const;
   SV* _lookup(const char *name, size_t nl) const;
   SV* _lookup_with_property_name(const char *name, size_t nl, std::string& given) const;
   SV* _lookup(const char *name, size_t nl, const std::string& subobj_name) const;
   SV* _give(const char *name, size_t nl, const Hash& props, property_type t) const;
   SV* _lookup(const char *name, size_t nl, const Hash& props) const;
   Array _give_all(const char *name, size_t nl) const;
   bool _exists(const char *name, size_t nl) const;
   void _add(const char *name, size_t nl, const Object& sub_obj, property_type t) const;
   void _remove(const char *name, size_t nl) const;
   SV* _get_attachment(const char *name, size_t nl) const;
   void _remove_attachment(const char *name, size_t nl) const;
public:
   template <size_t nl>
   PropertyValue give(const char (&name)[nl]) const { return PropertyValue(_give(name,nl-1)); }
   PropertyValue give(const std::string& name) const { return PropertyValue(_give(name.c_str(),name.size())); }

   template <size_t nl>
   PropertyValue give_with_property_name(const char (&name)[nl], std::string& given) const { return PropertyValue(_give_with_property_name(name,nl-1,given)); }
   PropertyValue give_with_property_name(const std::string& name, std::string& given) const { return PropertyValue(_give_with_property_name(name.c_str(),name.size(),given)); }

   template <size_t nl>
   PropertyValue lookup(const char (&name)[nl]) const { return PropertyValue(_lookup(name,nl-1), value_allow_undef); }
   PropertyValue lookup(const std::string& name) const { return PropertyValue(_lookup(name.c_str(),name.size()), value_allow_undef); }

   template <size_t nl>
   PropertyValue lookup_with_property_name(const char (&name)[nl], std::string& given) const { return PropertyValue(_lookup_with_property_name(name,nl-1,given), value_allow_undef); }
   PropertyValue lookup_with_property_name(const std::string& name, std::string& given) const { return PropertyValue(_lookup_with_property_name(name.c_str(),name.size(),given), value_allow_undef); }

   template <size_t nl>
   PropertyValue lookup(const char (&name)[nl], const std::string& subobj_name) const { return PropertyValue(_lookup(name,nl-1,subobj_name)); }
   PropertyValue lookup(const std::string& name, const std::string& subobj_name) const { return PropertyValue(_lookup(name.c_str(),name.size(),subobj_name)); }

   template <size_t nl>
   PropertyValue give(const char (&name)[nl], const Hash& props, property_type t=_normal) const { return PropertyValue(_give(name,nl-1,props,t)); }
   PropertyValue give(const std::string& name, const Hash& props, property_type t=_normal) const { return PropertyValue(_give(name.c_str(),name.size(),props,t)); }

   template <size_t nl>
   PropertyValue lookup(const char (&name)[nl], const Hash& props) const { return PropertyValue(_lookup(name,nl-1,props), value_allow_undef); }
   PropertyValue lookup(const std::string& name, const Hash& props) const { return PropertyValue(_lookup(name.c_str(),name.size(),props), value_allow_undef); }

   template <size_t nl>
   Array give_all(const char (&name)[nl]) const { return _give_all(name,nl-1); }
   Array give_all(const std::string& name) const { return _give_all(name.c_str(),name.size()); }

   template <size_t nl>
   bool exists(const char (&name)[nl]) const { return _exists(name,nl-1); }
   bool exists(const std::string& name) const { return _exists(name.c_str(),name.size()); }

   template <size_t nl>
   PropertyOut take(const char (&name)[nl], property_type t=_normal) const { return PropertyOut(obj_ref,name,nl-1,t); }
   PropertyOut take(const std::string& name, property_type t=_normal) const { return PropertyOut(obj_ref,name.c_str(),name.size(),t); }

   template <size_t nl>
   void add(const char (&name)[nl], const Object& sub_obj, property_type t=_normal) const { _add(name,nl-1,sub_obj,t); }
   void add(const std::string& name, const Object& sub_obj, property_type t=_normal) const { _add(name.c_str(),name.size(),sub_obj,t); }

   template <size_t nl>
   void remove(const char (&name)[nl]) const { _remove(name,nl-1); }
   void remove(const std::string& name) const { _remove(name.c_str(),name.size()); }

   void remove(const Object& sub_obj) const;

   template <size_t nl>
   PropertyValue get_attachment(const char (&name)[nl]) const { return PropertyValue(_get_attachment(name,nl-1), value_allow_undef); }
   PropertyValue get_attachment(const std::string& name) const { return PropertyValue(_get_attachment(name.c_str(),name.size()), value_allow_undef); }

   template <size_t nl>
   PropertyOut attach(const char (&name)[nl]) const { return PropertyOut(obj_ref,name,nl-1,attachment); }
   PropertyOut attach(const std::string& name) const { return PropertyOut(obj_ref,name.c_str(),name.size(),attachment); }

   template <size_t nl>
   void remove_attachment(const char (&name)[nl]) const { _remove_attachment(name,nl-1); }
   void remove_attachment(const std::string& name) const { _remove_attachment(name.c_str(),name.size()); }

   ObjectType type() const;
   bool isa(const ObjectType& type) const;

   template <size_t l>
   bool isa(const char (&type_name)[l]) const { return _isa(type_name,l-1); }
   bool isa(const std::string& type_name) const { return _isa(type_name.c_str(),type_name.size()); }

   static Object load(const std::string& filename);
   void save(const std::string& filename) const;

   Object parent() const;
};

template <typename Container>
void read_labels(Object& p, const char *label_prop, Container& labels)
{
   if (!(p.lookup(label_prop) >> labels)) {
      std::ostringstream label;
      int i=0;
      for (typename Entire<Container>::iterator dst=entire(labels); !dst.at_end(); ++i, ++dst) {
	 label.str("");
	 label << i;
	 *dst = label.str();
      }
   }
}

template <typename T>
void save_data(const std::string& filename, const T& data, const std::string& description=std::string())
{
   Value v;
   v << data;
   _save_data(v.get(), filename, description);
   v.forget();
}

} }

#endif // _POLYMAKE_PERL_OBJECT_H

// Local Variables:
// mode:C++
// End:
