#ifndef MIDAS_LONGSTRING_HH_

#include <sstream>
#include <iomanip>
#include <string>
#include <iterator>

#include "fcinterface.h"

// Definition of an longstring, or "long string".  Designed for the
// purpose of bridging the gap between a fixed-size string that
// must exist within a structure shared between C, Fortran, and C++,
// and which must be able to overflow into the heap.  When a string
// that is too large for the preallocated storage is assigned,
// a special pattern is written into the string along with the hex
// value of the pointer to the dynamic memory.
//
// Because this string must be readable when possible within Fortran,
// the ``longstring`` is essentially a superset of an ``fstring``.

// Helper functions, which could also be usable from C or Fortran.

namespace midas {


  // Helper functions for the longstring<n> template below.
  // These functions have exactly the same type for all longstrings,
  // and so I don't want to put them in the template.  But neither
  // are they really public functions.
  namespace longstring_ {

    // Return the real size of the longstring, following and
    // measuring the size of the larger string if one is encoded.
    std::string::size_type size (const char* carray,
				 std::string::size_type carray_len);

    // Initialize the longstring.  Like clearing, except that
    // it does not check the memory first looking for an encoded
    // heap string.
    void init (char* carray, std::string::size_type carray_len);

    // Clone a longstring.  Both must be the same size.
    void clone (char* new_string, const char* old_string,
		std::string::size_type len);

    // If the given longstring contains a handle, AND that handle has
    // been inadvertently shared (it points back to a different
    // longstring), clone that handle.  This allows a longstring to
    // maintain one of its invariants, which is that each longstring
    // instance should point to one and only one handle (if it has
    // one). Return 'true' if this was true and this operation took
    // place.
    bool cloned_if_shared (char* carray, std::string::size_type carray_len);

    // Convert the given longstring to a std::string.
    std::string str (const char* carray, std::string::size_type carray_len);

    // Read and write access to individual characters.
    char at (const char* buf, std::string::size_type nchar, int subscript);
    char& at (char* buf, std::string::size_type nchar, int subscript);


    // Given a character array and its maximum length, assign the
    // given string into it, encoding it as a longstring if 
    // ``new_string`` is too large.  If it isn't too large, assign
    // it into (carray, carray_len) as a space-padded string.
    void assign (char* carray, std::string::size_type carray_len,
		 const std::string& new_string);

    // Clear out the given longstring, including the deallocation
    // of any encoded dynamically allocated memory.
    void clear (char* carray, std::string::size_type carray_len);

    // Push another level on the longstring stack, meaning any new
    // longstrings created will be cleaned up when this level is popped.
    void push_stack (void);

    // Pop the topmost level on the longstring stack, cleaning up
    // all longstrings contained within.
    void pop_stack (void);

    // Clear the entire longstring registry.
    void registry_destruct ();

    // Perform integrity check over the whole registry, searching
    // for handles that do not have longstrings pointing to them,
    // and clean them up.  Returns the number of handles thus reaped.
    // Search is constrained to the given primitive stack depth.
    int registry_reap_handles (size_t for_depth);

  } // longstring_

  template <std::string::size_type nchar> struct longstring {

    char buf[nchar];

    // ///// Methods

    longstring ()
    { longstring_::init(buf, nchar); }

    longstring (const longstring<nchar>& other)
    {
      longstring_::clone(buf, other.buf, nchar);
    }

    longstring (const std::string& s)
    {
      longstring_::init(buf, nchar);
      longstring_::assign(buf, nchar, s);
    }

    ~longstring ()
    { longstring_::clear(buf, nchar); }

    void init ()
    { longstring_::init(buf, nchar); }

    std::ostream& dump (std::ostream& out) const
    { std::copy(buf, buf + nchar,
		std::ostream_iterator<char>(out)); return out; }

    std::string substr (std::string::size_type start = 0,
			std::string::size_type len =
			std::string::npos) const
    { return str().substr(start, len); }

    std::string str () const
    {
      return longstring_::str(buf, nchar);
    }

    const char* data () const { return buf; }

    std::string::size_type length () const
    {
      return longstring_::size(buf, nchar);
    }

    std::string::size_type size () const
    { return length(); }

    bool empty () const
    {
      std::string::size_type i = 0;
      while (i < nchar && buf[i] <= ' ') ++i;
      return i == nchar;
    }

    std::string::size_type capacity () const { return nchar; }

    void clear ()
    { longstring_::clear(buf, nchar); }

    longstring& operator= (char c)
    { longstring_::assign(buf, nchar, std::string(1, c)); return *this; }
  
    longstring& operator= (const char* s)
    { longstring_::assign(buf, nchar, std::string(s)); return *this; }

    longstring& operator= (const std::string& s)
    { longstring_::assign(buf, nchar, s); return *this; }

    longstring& operator= (const longstring<nchar>& s)
    { longstring_::assign(buf, nchar, s.str()); return *this; }

    longstring& assign (const char* s)
    { longstring_::assign(buf, nchar, std::string(s)); return *this; }

    longstring& operator+= (const char* c2)
    { return *this = str() + c2; }

    longstring& operator+= (const std::string& s)
    { return *this = str() + s; }

    operator std::string () const
    { return str(); }

    char operator[] (int subscript) const
    { return longstring_::at(buf, nchar, subscript); }

    char& operator[] (int subscript)
    { return longstring_::at(buf, nchar, subscript); }

    operator fstring_buf ()
    { return fstring_buf(buf, nchar, C2F_FLAG_LONGSTRING); }

    operator fstring_out_buf ()
    { return fstring_out_buf(buf, nchar, C2F_FLAG_LONGSTRING); }

  }; // longstring

  template <std::string::size_type nchar>
  inline bool operator== (const longstring<nchar>& ls, const char* s)
  {
    return std::string(ls) == std::string(s);
  }

  template <std::string::size_type nchar>
  inline bool operator== (const char* s, const longstring<nchar>& ls)
  {
    return std::string(s) == std::string(ls);
  }

  template <std::string::size_type nchar>
  inline bool operator== (const longstring<nchar>&ls, const std::string& s)
  {
    return std::string(ls) == s;
  }

  template <std::string::size_type nchar>
  inline bool operator== (const std::string& s, const longstring<nchar>&ls)
  {
    return s == std::string(ls);
  }


  template <std::string::size_type nchar>
  inline bool operator== (const longstring<nchar>& ls1,
			  const longstring<nchar>& ls2)
  {
    return std::string(ls1) == std::string(ls2);
  }


  template <std::string::size_type nchar>
  inline bool operator!= (const longstring<nchar>& ls, const char* s)
  {
    return !(std::string(ls) == std::string(s));
  }

  template <std::string::size_type nchar>
  inline bool operator!= (const char* s, const longstring<nchar>& ls)
  {
    return !(std::string(s) == std::string(ls));
  }

  template <std::string::size_type nchar>
  inline bool operator!= (const longstring<nchar>&ls, const std::string& s)
  {
    return !(std::string(ls) == s);
  }

  template <std::string::size_type nchar>
  inline bool operator!= (const std::string& s, const longstring<nchar>&ls)
  {
    return !(s == std::string(ls));
  }


  template <std::string::size_type nchar>
  inline bool operator!= (const longstring<nchar>& ls1,
			  const longstring<nchar>& ls2)
  {
    return !(std::string(ls1) == std::string(ls2));
  }


  template <std::string::size_type nchar>
  inline std::ostream& operator<< (std::ostream& out,
				   const longstring<nchar>& fin)
  { out << fin.str(); return out; }

  template <std::string::size_type nchar>
  inline std::string operator+ (const longstring<nchar>& s1,
				const std::string& s2)
  { return s1.str() + s2; }

  template <std::string::size_type nchar>
  inline std::string operator+ (const std::string& s1,
				const longstring<nchar>& s2)
  { return s1 + s2.str(); }

} // namespace midas

#define MIDAS_LONGSTRING_HH_
#endif

