/* 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 9999 2010-12-15 22:59:13Z gawrilow $
*/

#ifndef POLYMAKE_PERL_OBJECT_H
#define POLYMAKE_PERL_OBJECT_H

#include <sstream>

namespace pm { namespace perl {

enum property_type { _done, _normal, temporary, attachment };

class PropertyOut {
private:
   Value val;
   PerlInterpreter *pi;
   property_type t;
   void finish();
   void cancel();
protected:
   explicit PropertyOut(PerlInterpreter *pi_arg, property_type t_arg)
      : val(value_flags(t_arg==_normal ? value_read_only :
                        t_arg==temporary ? value_allow_non_persistent|value_read_only : 0)),
        pi(pi_arg),
        t(t_arg) {}
public:
   template <typename Source>
   void operator<< (const Source& x)
   {
      val << x;
      finish();
   }

   ~PropertyOut()
   {
      if (__builtin_expect(t != _done, 0)) cancel();
   }

   friend class Object;
};

class ObjectType {
   friend class Value;
   friend class Object;

protected:
   SV *obj_ref;

   bool _isa(const char *type_name, size_t tl) const;
   static SV* find_type(const char *type_name, size_t tl);
   static SV* construct_parameterized_type(const char *type_name, size_t tl);

   template <typename TypeList>
   static SV* construct(const char *type_name, size_t tl)
   {
      Stack stack(false, TypeListUtils<TypeList>::type_cnt);
      if (TypeListUtils<TypeList>::push_types(stack)) {
         return construct_parameterized_type(type_name, tl);
      } else {
         stack.cancel();
         throw exception("one of the type arguments is not declared in the rules");
      }
   }
   explicit ObjectType(SV *r) : obj_ref(r) {}
public:
   template <size_t nl>
   explicit ObjectType(const char (&type_name)[nl]) :
      obj_ref(find_type(type_name, nl-1)) {}

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

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

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

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

   ObjectType& operator= (const ObjectType& o);

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

   bool isa(const ObjectType& o) const;

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

class Object {
   friend class Value;  friend class Main;
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);

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

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

   template <size_t nl>
   explicit Object(const char (&type_name)[nl])
   {
      _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 nl>
   explicit Object(const char (&type_name)[nl], 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 nl>
   Object(const char (&type_name)[nl], 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);

   ~Object();

   Object& operator= (const Object& o);

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

   template <size_t nl>
   Object& cast(const char (&type_name)[nl])
   {
      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; }

   typedef ArrayOwner<Object> array;
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, SV *props, property_type t) const;
   SV* _lookup(const char *name, size_t nl, SV *props) const;
   SV* _give_all(const char *name, size_t nl) const;
   PerlInterpreter* _take(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.get(), t));
   }
   PropertyValue give(const std::string& name, const Hash& props, property_type t=_normal) const
   {
      return PropertyValue(_give(name.c_str(), name.size(), props.get(), t));
   }

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

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

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

   template <size_t nl>
   array give_all(const char (&name)[nl]) const
   {
      return array(_give_all(name, nl-1));
   }
   array give_all(const std::string& name) const
   {
      return array(_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)
   {
      return PropertyOut(_take(name, nl-1), t);
   }
   PropertyOut take(const std::string& name, property_type t=_normal)
   {
      return PropertyOut(_take(name.c_str(), name.size()), t);
   }

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

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

   void remove(const Object& sub_obj);

   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])
   {
      return PropertyOut(_take(name, nl-1), attachment);
   }
   PropertyOut attach(const std::string& name)
   {
      return PropertyOut(_take(name.c_str(), name.size()), attachment);
   }

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

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

   template <size_t nl>
   bool isa(const char (&type_name)[nl]) const
   {
      return _isa(type_name, nl-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;

   PropertyValue call_polymake_method(const std::string& name, const FunCall& funcall) const
   {
      return PropertyValue(funcall.evaluate_method(obj_ref, name.c_str()),
                           value_flags(value_allow_undef | value_not_trusted));
   }
   PropertyValue call_polymake_method(const char* name, const FunCall& funcall) const
   {
      return PropertyValue(funcall.evaluate_method(obj_ref, name),
                           value_flags(value_allow_undef | value_not_trusted));
   }

   ListResult list_call_polymake_method(const std::string& name, const FunCall& funcall) const
   {
      return ListResult(funcall.list_evaluate_method(obj_ref, name.c_str()), funcall);
   }
   ListResult list_call_polymake_method(const char* name, const FunCall& funcall) const
   {
      return ListResult(funcall.list_evaluate_method(obj_ref, name), funcall);
   }

   void void_call_polymake_method(const std::string& name, const FunCall& funcall) const
   {
      funcall.void_evaluate_method(obj_ref, name.c_str());
   }
   void void_call_polymake_method(const char* name, const FunCall& funcall) const
   {
      funcall.void_evaluate_method(obj_ref, name);
   }
};

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();
      }
   }
}

} }

#endif // POLYMAKE_PERL_OBJECT_H

// Local Variables:
// mode:C++
// c-basic-offset:3
// indent-tabs-mode:nil
// End:
