/* -----------------------------------------------------------------------------
 * director.swg
 *
 * This file contains support for director classes so that PHP proxy
 * methods can be called from C++.
 * ----------------------------------------------------------------------------- */

#ifndef SWIG_DIRECTOR_PHP_HEADER_
#define SWIG_DIRECTOR_PHP_HEADER_

#define SWIG_DIRECTOR_CAST(ARG) dynamic_cast<Swig::Director *>(ARG)

#include <string>
#include <exception>
#include <map>

namespace Swig {

  /* memory handler */
  struct GCItem {
    virtual ~GCItem() {
    }

    virtual int get_own() const {
      return 0;
    }
  };

  struct GCItem_var {
    GCItem_var(GCItem *item = 0) : _item(item) {
    }

    GCItem_var& operator=(GCItem *item) {
      GCItem *tmp = _item;
      _item = item;
      delete tmp;
      return *this;
    }

    ~GCItem_var() {
      delete _item;
    }

    GCItem * operator->() const {
      return _item;
    }

    private:
    GCItem *_item;
  };

  struct GCItem_Object : GCItem {
    GCItem_Object(int own) : _own(own) {
    }

    virtual ~GCItem_Object() {
    }

    int get_own() const {
      return _own;
    }

    private:
    int _own;
  };

  template <typename Type>
  struct GCItem_T : GCItem {
    GCItem_T(Type *ptr) : _ptr(ptr) {
    }

    virtual ~GCItem_T() {
      delete _ptr;
    }

    private:
    Type *_ptr;
  };

  class Director {
    private:
      /* flag indicating whether the object is owned by PHP or C++ */
      mutable bool swig_disown_flag;

    protected:
      // "mutable" so we can get a non-const pointer to it in const methods.
      mutable zval swig_self;
      typedef std::map<void *, GCItem_var> swig_ownership_map;
      mutable swig_ownership_map swig_owner;

    public:
      Director(zval *self) : swig_disown_flag(false) {
        ZVAL_COPY_VALUE(&swig_self, self);
      }

      ~Director() {
        if (swig_disown_flag) {
          Z_DELREF(swig_self);
	}
      }

      zend_object *swig_get_self() const { return Z_OBJ(swig_self); }

      void swig_disown() const {
        if (!swig_disown_flag) {
          swig_disown_flag = true;
          Z_ADDREF(swig_self);
        }
      }

      template <typename Type>
      void swig_acquire_ownership(Type *vptr) const {
        if (vptr) {
          swig_owner[vptr] = new GCItem_T<Type>(vptr);
        }
      }

      void swig_acquire_ownership_obj(void *vptr, int own) const {
        if (vptr && own) {
          swig_owner[vptr] = new GCItem_Object(own);
        }
      }
  };

  /* base class for director exceptions */
  class DirectorException : public std::exception {
  protected:
    std::string swig_msg;
  public:
    DirectorException(int code, const char *hdr, const char *msg) : swig_msg(hdr) {
      if (msg && msg[0]) {
        swig_msg += " ";
        swig_msg += msg;
      }
      // Don't replace an already active PHP exception.
      if (!EG(exception)) zend_throw_exception(NULL, swig_msg.c_str(), code);
    }

    virtual ~DirectorException() SWIG_NOEXCEPT {
    }

    const char *what() const SWIG_NOEXCEPT {
      return swig_msg.c_str();
    }

    static void raise(int code, const char *hdr, const char *msg) {
      throw DirectorException(code, hdr, msg);
    }
  };

  /* attempt to call a pure virtual method via a director method */
  class DirectorPureVirtualException : public DirectorException {
  public:
    DirectorPureVirtualException(const char *msg)
      : DirectorException(E_ERROR, "SWIG director pure virtual method called", msg) {
    }

    static void raise(const char *msg) {
      throw DirectorPureVirtualException(msg);
    }
  };

  /* any php exception that occurs during a director method call */
  class DirectorMethodException : public DirectorException {
  public:
    DirectorMethodException()
      : DirectorException(E_ERROR, "SWIG director method error", NULL) {
    }

    DirectorMethodException(const char *msg)
      : DirectorException(E_ERROR, "SWIG director method error", msg) {
    }

    static void raise(const char *msg) {
      throw DirectorMethodException(msg);
    }
  };
}

#endif
