/** @file

    $Id$

    $Date$
    $Author$

    @copy &copy; Marc W&auml;ckerlin
    @license LGPL, see file <a href="license.html">COPYING</a>

*/
#ifndef __MRW_AUTO_HPP__
#define __MRW_AUTO_HPP__

#include <sys/types.h> // size_t
#include <sys/mman.h> // munmap, PROT_READ, MAP_SHARED
#include <unistd.h> // close
#include <stdexcept>
#include <cassert>

// forward
class bfd;
extern "C" int bfd_close(bfd*);

namespace mrw {

  /** @defgroup AutoTools Classes for Automated Resource Handling

      For pointers that have been allocated with @c new, you can use
      std::auto_ptr to automatically free them when you leave the
      context. Unfortunately there is no such thing for @c malloc
      (except @c malloca that only works for a subset of problems: if
      you and not a system call allocates memory), @c open and so on.
      These classes can take over the resource ownership.

      The following ressource handler are predefined:
       - mrw::AutoPtr<> is the same as std::auto_ptr, but can be stored
         in STL containers
         @code
         mrw::AutoPtr<ClassName> xyz(new ClassName(a, b, c));
         @endcode
       - mrw::MMapHandle frees an @c mmap handle with @c munmap
       - mrw::Auto<>::Free frees @c malloc allocated memory with @c free
         @code
         mrw::Auto<char*>::Free xxx((char*)malloc(15));
         @endcode
       - mrw::SmartPointer<> is a shared pointer that deletes memory
         when all owners have died
         @code
         mrw::SmartPointer<ClassName> xyz(new ClassName(a, b, c));
         @endcode
       - mrw::Pipe handles UNIX pipes and closes them on exit
       - mrw::AutoFile automatically closes open files
       - mrw::AutoMapper calls @c munmap on memory mapped files
       - mrw::AutoBfd automatically calls @c bfd_close

     If this is not enough, you can @c typedef your own ressource
     handler from the template class mrw::AutoResource<>. For example,
     mrw::AutoFile is defined as:
     @code
     typedef mrw::AutoResource<int, int(*)(int), &close, int, -1> mrw::AutoFile;
     @endcode
  */
  //@{
  
  /** @brief Automatically frees a resource when destructed.
      @pre \#include <mrw/auto.hpp>

      AutoResource works exactly like std::auto_ptr, but for any
      resource instead of new allocated pointers. Whenever the context
      of AutoResource is left, the resource is freed by a call to the
      given function. This way, resources are freed even in case of
      exceptions.

      Declare an automated file descriptor handler, a BFD handler and
      a @c malloc-/ @c free handler (all these typedefs are already
      part of the library):

      @code
      typedef mrw::AutoResource<int, int(*)(int), &close, int, -1> AutoFile;
      typedef mrw::AutoResource<bfd*, int(*)(bfd*), &bfd_close, int> AutoBfd;
      template<class T> class Auto {
        public:
          typedef mrw::AutoResource<T, void(*)(void*), &free, int, 0> Free;
      };
      @endcode
      @param RESOURCE_TYPE type of the resource to manage
      @param FUNCTION_PTR type of the function that frees the resource
      @param FREE_FUNCTION the function that frees the resource
      @param INITIAL_VALUE_TYPE type of the initial value
             (pointers may have to be initialized by an integer)
      @param INITIAL_VALUE value when the resource is unassigned
      @param FREE_TYPE type to free, if cast is needed
  */
  template<typename RESOURCE_TYPE,
           typename FUNCTION_PTR,
           FUNCTION_PTR FREE_FUNCTION,
           typename INITIAL_VALUE_TYPE = RESOURCE_TYPE,
           INITIAL_VALUE_TYPE INITIAL_VALUE = 0,
           typename FREE_TYPE = RESOURCE_TYPE>
  class AutoResource {
    public:
      /// @brief Construct from an allocated resource.
      /// The resource is freed if necessary.
      /// AutoResource takes over ownership of the resource.
      explicit AutoResource(FREE_TYPE res = INITIAL_VALUE)
        throw(std::bad_exception): _res((RESOURCE_TYPE)res) {
      }
      /// @brief Takeover ownership from another AutoResource.
      AutoResource(AutoResource& o) throw(std::bad_exception):
        _res(o.release()) {
      }
      //! @brief Do not use this method!
      //! This method had to be introduced for the @c std::map, where
      //! @c operator[] first creates an empty element, then assigns it
      //! the real value. Because that empty element is temporary, gcc can
      //! not use @c AutoResource(AutoResource&) since release 4.0.
      AutoResource(const AutoResource& o) throw(std::bad_exception):
        _res(INITIAL_VALUE) {
        assert(o._res==INITIAL_VALUE);
        if (o._res!=INITIAL_VALUE)
          _res = const_cast<AutoResource&>(o).release();
      }
      /// @brief Free resource. Calls @c reset().
      ~AutoResource() throw(std::bad_exception) {reset();}
      /// @brief Assign new resource. Calls @c reset().
      /// The resource is freed if necessary.
      AutoResource& operator=(RESOURCE_TYPE res) throw(std::bad_exception) {
        return reset(res);
      }
      /// @brief Takeover ownership from another AutoResource.
      /// Calls @c reset() from @c this and @c release() from @c other.
      AutoResource& operator=(AutoResource& other) throw(std::bad_exception) {
        return reset(other.release());
      }
      /// @brief Get the resource.
      operator const RESOURCE_TYPE&() const throw(std::bad_exception) {
        return _res;
      }
      /// @brief find out, if a value is set
      /// @return @c true: resource is valid
      operator bool() const throw(std::bad_exception) {
        return _res!=INITIAL_VALUE;
      }
      /// @brief get the resetted resource for resetting it.
      /// Calls @c reset and returns the cleaned resource.
      /// The intention is, that you can safely assign it a new value
      /// (e.g. in an expression).
      RESOURCE_TYPE& getClean() throw(std::bad_exception) {
        reset();
        return _res;
      }
      /// @brief Give away ownership of the resource.
      /// @return old resource
      RESOURCE_TYPE release() throw(std::bad_exception) {
        RESOURCE_TYPE res(_res); _res=INITIAL_VALUE;
        return res;
      }
      /// @brief Assign a new resource.
      /// The old resource of @c this is freed if necessary.
      AutoResource& reset(RESOURCE_TYPE res = INITIAL_VALUE)
        throw(std::bad_exception) {
        if (_res!=INITIAL_VALUE) (*FREE_FUNCTION)((FREE_TYPE)_res);
        _res = res;
        return *this;
      }
    private:
      RESOURCE_TYPE _res;       ///< the resource to be managed
  };

  /** @brief Automatically deletes a pointer when destructed.
      @pre \#include <mrw/auto.hpp>

      mrw::AutoPtr is a replacement for std::auto_ptr. The problem with
      standard std::auto_ptr is, that it cannot be stored in a std::map.

      @warning Use this class with prudence! Should I ever find out,
      how to work around the std::auto_ptr&nbsp;/ std::map problem,
      then this class may become deprecated.

      @param T type of the pointer to manage
  */
  template <typename T>
  class AutoPtr {
    public:
      /// @brief Construct from an allocated resource.
      /// The resource is freed if necessary.
      /// AutoPtr takes over ownership of the resource.
      explicit AutoPtr(T* res = 0)
        throw(std::bad_exception): _res(res) {
          res = 0;
      }
      /// @brief Takeover ownership from another AutoPtr.
      AutoPtr(AutoPtr& o) throw(std::bad_exception):
        _res(o.release()) {
      }
      //! @brief Do not use this method!
      //! This method had to be introduced for the @c std::map, where
      //! @c operator[] first creates an empty element, then assigns it
      //! the real value. Because that empty element is temporary, gcc can
      //! not use @c AutoResource(AutoResource&) since release 4.0.
      AutoPtr(const AutoPtr& o) throw(std::bad_exception): _res(0) {
        assert(o._res==0);
        if (o._res!=0)
          _res = const_cast<AutoPtr&>(o).release();
      }
      /// @brief Free resource. Calls @c reset().
      ~AutoPtr() throw(std::bad_exception) {reset();}
      /// @brief Assign new resource. Calls @c reset().
      /// The resource is freed if necessary.
      AutoPtr& operator=(T* res) throw(std::bad_exception) {
        return reset(res);
      }
      /// @brief Takeover ownership from another AutoResource.
      /// Calls @c reset() from @c this and @c release() from @c other.
      AutoPtr& operator=(AutoPtr& other) throw(std::bad_exception) {
        return reset(other.release());
      }
      /// @brief Get the resource.
      operator T* const() const throw(std::bad_exception) {
        return _res;
      }
      /// @brief find out, if a value is set
      /// @return @c true: resource is valid
      operator bool() const throw(std::bad_exception) {
        return _res!=0;
      }
      /// @brief Access the AutoPtr like a normal pointer.
      T*const operator->() {return _res;}
      /// @brief Dereference the AutoPtr like a normal pointer.
      T& operator*() {return *_res;}
      /// @brief get the resetted resource for resetting it.
      /// Calls @c reset and returns the cleaned resource.
      /// The intention is, that you can safely assign it a new value
      /// (e.g. in an expression).
      T* getClean() throw(std::bad_exception) {
        reset();
        return _res;
      }
      /// @brief Give away ownership of the resource.
      /// @return old resource
      T* release() throw(std::bad_exception) {
        T* res(_res); _res=0;
        return res;
      }
      /// @brief Assign a new resource.
      /// The old resource of @c this is freed if necessary.
      AutoPtr& reset(T* res = 0)
        throw(std::bad_exception) {
        delete _res;
        _res = res;
        return *this;
      }
    private:
      T* _res;       ///< the resource to be managed
  };

  /** @brief Automatically deletes an array pointer when destructed.
      @pre \#include <mrw/auto.hpp>

      mrw::AutoPtrAry is a replacement for std::auto_ptr. Other than
      mrw::AutoPtr it is for arrays, that means, it calls @c delete[]
      instead of @c delete to free the memory. The problem with
      standard std::auto_ptr is, that it cannot be stored in a
      std::map and it does not call @c delete[].

      @warning Use this class with prudence! Should I ever find out,
      how to work around the std::auto_ptr&nbsp;/ std::map problem,
      then this class may become deprecated.

      @param T type of the pointer to manage
  */
  template <typename T>
  class AutoPtrAry {
    public:
      /// @brief Construct from an allocated resource.
      /// The resource is freed if necessary.
      /// AutoPtrAry takes over ownership of the resource.
      explicit AutoPtrAry(T* res = 0)
        throw(std::bad_exception): _res(res) {
          res = 0;
      }
      /// @brief Takeover ownership from another AutoPtrAry.
      AutoPtrAry(AutoPtrAry& o) throw(std::bad_exception):
        _res(o.release()) {
      }
      //! @brief Do not use this method!
      //! This method had to be introduced for the @c std::map, where
      //! @c operator[] first creates an empty element, then assigns it
      //! the real value. Because that empty element is temporary, gcc can
      //! not use @c AutoResource(AutoResource&) since release 4.0.
      AutoPtrAry(const AutoPtrAry& o) throw(std::bad_exception): _res(0) {
        assert(o._res==0);
        if (o._res!=0)
          _res = const_cast<AutoPtrAry&>(o).release();
      }
      /// @brief Free resource. Calls @c reset().
      ~AutoPtrAry() throw(std::bad_exception) {reset();}
      /// @brief Assign new resource. Calls @c reset().
      /// The resource is freed if necessary.
      AutoPtrAry& operator=(T* res) throw(std::bad_exception) {
        return reset(res);
      }
      /// @brief Takeover ownership from another AutoResource.
      /// Calls @c reset() from @c this and @c release() from @c other.
      AutoPtrAry& operator=(AutoPtrAry& other) throw(std::bad_exception) {
        return reset(other.release());
      }
      /// @brief Get the resource.
      operator T* const() const throw(std::bad_exception) {
        return _res;
      }
      /// @brief find out, if a value is set
      /// @return @c true: resource is valid
      operator bool() const throw(std::bad_exception) {
        return _res!=0;
      }
      /// @brief Access the AutoPtrAry like a normal pointer.
      T*const operator->() {return _res;}
      /// @brief Dereference the AutoPtrAry like a normal pointer.
      T& operator*() {return *_res;}
      /// @brief get the resetted resource for resetting it.
      /// Calls @c reset and returns the cleaned resource.
      /// The intention is, that you can safely assign it a new value
      /// (e.g. in an expression).
      T* getClean() throw(std::bad_exception) {
        reset();
        return _res;
      }
      /// @brief Give away ownership of the resource.
      /// @return old resource
      T* release() throw(std::bad_exception) {
        T* res(_res); _res=0;
        return res;
      }
      /// @brief Assign a new resource.
      /// The old resource of @c this is freed if necessary.
      AutoPtrAry& reset(T* res = 0)
        throw(std::bad_exception) {
        delete[] _res;
        _res = res;
        return *this;
      }
    private:
      T* _res;       ///< the resource to be managed
  };

  /** @brief Automatically closes a file when destructed.
      @pre \#include <mrw/auto.hpp>

      AutoFile works exactly like std::auto_ptr, but for files
      instead of pointers. Whenever the context of AutoFile is left,
      the opened file is close. This way, resources are freed even in
      case of exceptions.
  */
  typedef mrw::AutoResource<int, int(*)(int), &close, int, -1> AutoFile;

  /** @brief Resource handle for @c mmap.
      
      It integrates pointer and size of a memory mapped file similar
      to a @c std::pair.
  */
  class MMapHandle {
  public:
    void* first;
    size_t second;
    /// @brief Constructor that initializes the values through a call to mmap.
    MMapHandle(int, size_t=0, void* = 0,
               int = PROT_READ, int = MAP_SHARED, off_t = 0)
      throw(std::bad_exception);
    /// @brief Reset to zero, value of int is ignored. 
    MMapHandle& operator=(int) throw(std::bad_exception) {
      first = 0; second = 0;
      return *this;
    }
    /// @brief Compare to zero, returns true, if i and handle are both zero.
    bool operator==(int i) const throw(std::bad_exception) {
      return i==0 && first==0 && second==0;
    }
  };
  inline void munmap(MMapHandle& res) throw(std::bad_exception) {
    if (res.first!=0 && res.second>0)
      ::munmap((char*)res.first, res.second); // char* conversion for windoze cygwin compatibility
  }
  /** @brief Automatically calls @c munmap on destruction for mmaped files.
      @pre \#include <mrw/auto.hpp>
      
      It's the same as std::auto_ptr, but for @c mmap instead of @c
      new.  When the context of @c AutoMapper is left, @c munmap is
      called.
  */
  typedef mrw::AutoResource<MMapHandle, void(*)(MMapHandle&), &mrw::munmap, int>
    AutoMapper;

  /** @brief Automatically calls @c bfd_close for @c bfd*.
      @pre \#include <mrw/auto.hpp>
      
      It acts like a @c std::auto_ptr, but for @c bfd*, that means it
      calls @c bfd_close whenever the context is left.
  */
  typedef mrw::AutoResource<bfd*, int(*)(bfd*), &bfd_close, int> AutoBfd;

  /** @brief Automatically calls @c free for @c malloc allocated memory.
             Instanciate it as @c mrw::Auto<TYPE*>::Free.
      @pre \#include <mrw/auto.hpp>

      It works like a @c std::auto_ptr, but for memory that was
      allocated with @c malloc, not @c new. Memory is freed, whenever
      the context od @c AutoFree is left.
      
      @code
      { // enter context
        Auto<char*>::Free cp = (char*)malloc(5);
        cp.getClean() = (char*)malloc(10); // old storage of 5 bytes is freed
      } // memory is freed on destruction of cp
      @endcode
  */
  template<class T> class Auto {
  public:
    typedef mrw::AutoResource<T, void(*)(void*), &free, int, 0, void*> Free;
  private:
    /** @internal
        work around compiler warning:
        "only defines private constructors and has no friends" */
    friend class ThisIsADummyToEliminateWarnings;
    /// Forbidden to instanciate.
    Auto(); Auto(const Auto&);
  };
  
  //@}
}
#endif
