// Viostream.h
//
// Classes for inputting/outputting to/from virtual streams 
//
// Nov 2001 Janet Barnes
//

#ifndef VIOSTREAM_H
#define VIOSTREAM_H

#include <cstdio>
#ifdef WINDOWS
#include "qxdr.h"
#else
#include <rpc/rpc.h>
#endif
#include <iostream>
#include <fstream>
#include <valarray>
#include <vector>
#include <map>
#include <complex>
#include <string>
#include "Std.h"
#include "Exception.h"

namespace QDOS{

class Estream
{
public:
  typedef enum Endian_e {HostEndian, LittleEndian, BigEndian} \
    Endian;
  
  Estream(Endian endian_fmt=HostEndian):_endian_fmt(endian_fmt){;}

  ~Estream(){;}
  
  int big_endian()
  { short a=0x0001;
    return (int)*((unsigned char*)(&a));
  }

  template<class T>
  void byteswap(T& t)
  {
    T cpy(t);
    const unsigned char *p=(const unsigned char*)&cpy;
    unsigned char dst[sizeof(T)];

    for(register int i=0;i<sizeof(T);i++)
      dst[i]=p[sizeof(T)-1-i];
   
    t=*((T*)dst);
  }

  template<class T>
  void convert(T& t)
  {
    bool big=big_endian();
    if(big && _endian_fmt==LittleEndian )
      byteswap(t);
  }

protected:
  Endian _endian_fmt;
 
};

class Vistream
{
public:

  virtual int getpos()=0;
  virtual void setpos(int)=0;

  virtual Vistream& operator>>(bool& b)
  { int i; (*this)>>i; b=i; return *this;}
  virtual Vistream& operator>>(int& i)=0;
  virtual Vistream& operator>>(size_t& i)=0;
  virtual Vistream& operator>>(float& f)=0;
  virtual Vistream& operator>>(double& d)=0;
  virtual Vistream& operator>>(string& str)=0;
  virtual Vistream& operator>>(char* c)
  { string s; (*this)>>s; c=strdup(s.c_str());return *this;}

  virtual Vistream& operator>>(double_complex& c)
  { 
     double r,i;
     (*this) >> r;
     (*this) >> i;
     c=double_complex(r,i);
     return *this;
  }

  template<class T>
  Vistream& operator>>(T*& ptr)
  { ptr=new T; (*this)>>*ptr; return *this;}

  template<class Key, class T>
  Vistream& operator>>(std::multimap<Key,T>& m)
  {
     int sz;
     (*this) >> sz;
     for(int i=0;i<sz;i++)
     {
        Key k;
        (*this) >> k;
        T t;
        (*this) >> t;
        m.insert(make_pair(k,t));
     }
     return *this;
  }


  template<class Key, class T>
  Vistream& operator>>(std::map<Key,T>& m)
  {
     int sz;
     (*this) >> sz;
     for(int i=0;i<sz;i++)
     {
        Key k;
        (*this) >> k;
        T t;
        (*this) >> t;
        m.insert(make_pair(k,t));
     }
     return *this;
  }

  template<class T>
  Vistream& operator>>(vector<T>& v)
  {
    int sz;
    (*this)>> sz;
    v.resize(sz);
    for(int i=0;i<sz;i++)
      (*this) >> v[i];
    return *this;
  }

  template<class T>
  Vistream& operator>>(valarray<T>& v)
  {
    int sz;
    (*this) >> sz;
    v.resize(sz); // is this a valid valarray method?
    for(int i=0;i<sz;i++)
      (*this) >> v[i];
    return *this;
  }

  virtual Vistream& operator>>(gslice& g)
  { 
    int start;
    (*this) >> start;
    valarray<size_t> sz;
    (*this) >> sz;
    valarray<size_t> st;
    (*this) >> st;
    g=gslice(start,sz,st); 
    return *this;
  }

};

class VBistream:public Vistream
{
  ifstream _fstr;
public:
  VBistream(const string& file_name)
  {
     _fstr.open(file_name.c_str(),ios::binary | ios::in);
     if(!_fstr.is_open())
     {
        ExceptionMsg m;
        m << "VBistream Error: Could not open " << file_name <<  " in  read mode";
        throw Exception(m);
     }

  }

  virtual ~VBistream()
  {
     _fstr.close();
  }

  virtual int getpos()
  { return _fstr.tellg();}

  virtual void setpos(int pos)
  { _fstr.seekg(pos);}

  template<class T>
  void get(T& obj)
  { _fstr.read((char*)&obj,sizeof(obj));}

  virtual Vistream& operator>>(int& i)
  { this->get(i); return *this;}

  virtual Vistream& operator>>(size_t& i)
  { this->get(i); return *this;}

  virtual Vistream& operator>>(float& f)
  { this->get(f); return *this;}

  virtual Vistream& operator>>(double& d)
  { this->get(d); return *this;}

  virtual Vistream& operator>>(string& str)
  {
    int sz;
    this->get(sz);
    char *c_data=(char *)(malloc((unsigned)(sz+1)));
    c_data[sz]='\0';
    for(int i=0;i<sz;i++)
	{
      this->get(c_data[i]);
	}
    str=c_data;
    free(c_data);
    return *this;
  }

};

class VEistream:public VBistream, public Estream
{
public:
  
  VEistream(const string& file_name,Endian endian_fmt=HostEndian):\
    VBistream(file_name),Estream(endian_fmt){;}

  virtual ~VEistream(){;}

  template<class T>
  void get(T& obj)
  {
    VBistream::get(obj);
    convert(obj);
  } 
    
  virtual Vistream& operator>>(int& i)
  { this->get(i); return *this;}

  virtual Vistream& operator>>(size_t& i)
  { this->get(i); return *this;}

  virtual Vistream& operator>>(float& f)
  { this->get(f); return *this;}

  virtual Vistream& operator>>(double& d)
  { this->get(d); return *this;}

};

class VXDRistream:public Vistream
{
  XDR _xdr;
  FILE* _infile;

public:
  VXDRistream(const string& file_name)
  {
    _infile=fopen(file_name.c_str(),"rb");
    if(_infile==NULL)
    {
      ExceptionMsg m;
      m << "QXDRistream Error: Could not open " << file_name << \
        " in  read mode";
      throw Exception(m);
    }
    xdrstdio_create(&_xdr,_infile,XDR_DECODE);
  }

  virtual ~VXDRistream()
  {
     (void)fclose(_infile);
      xdr_destroy(&_xdr);
  }

  virtual int getpos()
  { return xdr_getpos(&_xdr);}

  virtual void setpos(int pos)
  { (void) xdr_setpos(&_xdr, pos);}

  virtual Vistream& operator>>(int& i) 
  { (void) xdr_int(&_xdr,&i); return *this;}

  virtual Vistream& operator>>(size_t& i) 
  { 
    if(sizeof(size_t)==sizeof(long unsigned int))
    {
      long unsigned int* p=reinterpret_cast<long unsigned int*>(&i);
      (void) xdr_u_long(&_xdr,p);
    }
    else 
    {
      unsigned int* p=reinterpret_cast<unsigned int*>(&i);
      (void) xdr_u_int(&_xdr,p);
    }
    return *this;
  }

  virtual Vistream& operator>>(float& f) 
  { (void) xdr_float(&_xdr,&f); return *this;}

  virtual Vistream& operator>>(double& d) 
  { (void) xdr_double(&_xdr,&d); return *this;}

  virtual Vistream& operator>>(string& str) 
  {
    short sz;
    xdr_short(&_xdr,&sz);
	if(sz < 0) {
		char c_empty[1] ="";
		str=&(c_empty[0]);
		return *this;
	}
   // cout << "sz is " << sz << endl;
    char *c_data=static_cast<char *>(malloc((unsigned)(sz+1)));
    c_data[sz]='\0';
    xdr_string(&_xdr,&c_data,sz);
    str=c_data;
    free(c_data);
    return *this;
  }
};

class Vostream
{
public:
  virtual Vostream& operator<<(bool b)
  { (*this)<<(int)b;return *this;}
  virtual Vostream& operator<<(int i)=0;
  virtual Vostream& operator<<(size_t i)=0;
  virtual Vostream& operator<<(float f)=0;
  virtual Vostream& operator<<(double d)=0;
  virtual Vostream& operator<<(const string& str)=0;
  virtual Vostream& operator<<(const char* c)
  { string s(c);
    (*this) << s;
    return *this;
  }

  virtual Vostream& operator<<(double_complex c)
  {
    double r=c.real();
    double i=c.imag();
    (*this) << r;
    (*this) << i;
    return *this;
  }

  template<class T>
  Vostream& operator<<(T* ptr)
  { if(ptr) 
      (*this)<<*ptr; 
    return *this;
  }

  template<class Key, class T>
  Vostream& operator<<(const std::map<Key,T>& m)
  {
   typename std::map<Key,T>::const_iterator itr;
   int sz=distance(m.begin(),m.end());
   (*this) << sz;
   for(itr=m.begin();itr!=m.end();itr++)
   {
     const Key& key=itr->first;
     (*this) << key;
     const T& t=itr->second;
     (*this) << t;
   }
   return *this;
  }

  template<class Key, class T>
  Vostream& operator<<(const std::multimap<Key,T>& m)
  {
   typename std::multimap<Key,T>::const_iterator itr;
   int sz=distance(m.begin(),m.end());
   (*this) << sz;
   for(itr=m.begin();itr!=m.end();itr++)
   {
     const Key& key=itr->first;
     (*this) << key;
     const T& t=itr->second;
     (*this) << t;
   }
   return *this;
  }

  template<class T>
  Vostream& operator<<(const vector<T>& v)
  {
    int sz=v.size();
    (*this)<< sz;
    for(int i=0;i<sz;i++)
      (*this) << v[i];
    return *this;
  }

  template<class T>
  Vostream& operator<<(const valarray<T>& v)
  {
    int sz=v.size();
    (*this) << sz;
    for(int i=0;i<sz;i++)
      (*this) << v[i];
    return *this;
  }

  virtual Vostream& operator<<(const gslice& g)
  { 
    int start=g.start();
    (*this) << start;
    const valarray<size_t>& sz=g.size();
    (*this) << sz;
    const valarray<size_t>& st=g.stride();
    (*this) << st;
    return *this;
  }
 
};

class VCostream:public Vostream
{
  ostream& _str;

public:
  VCostream(ostream& str):_str(str){;}
  virtual Vostream& operator<<(int i)
  { _str << i;return *this;}
  virtual Vostream& operator<<(size_t i)
  { _str << i; return *this;}
  virtual Vostream& operator<<(float f)
  { _str << f; return *this;}
  virtual Vostream& operator<<(double d)
  { _str << d; return *this;}
  virtual Vostream& operator<<(const string& str)
  { _str << str;return *this;}

};

class VBostream:public Vostream
{
  ofstream _fstr;
public:
  VBostream(const string& file_name)
  {
     _fstr.open(file_name.c_str(),ios::binary | ios::out);
     if(!_fstr.is_open())
     {
        ExceptionMsg m;
        m << "VBostream Error: Could not open " << file_name <<  " in  write mode";
        throw Exception(m);
     }
  }

  virtual ~VBostream()
  {
     _fstr.close();
  }

  template<class T>
  void put(const T& obj)
  { _fstr.write((char*)&obj, sizeof(obj));}

  virtual Vostream& operator<<(int i)
  { this->put(i); return *this;}

  virtual Vostream& operator<<(size_t i)
  {this->put(i); return *this;}

  virtual Vostream& operator<<(float f)
  {  this->put(f); return *this;}

  virtual Vostream& operator<<(double d)
  {  this->put(d); return *this;}

  virtual Vostream& operator<<(const string& str)
  {
    const char *c_data=str.c_str();
    int sz=strlen(c_data);
    this->put(sz);  

    for(int i=0;i<sz;i++)
	{
      this->put(c_data[i]);
	}
    return *this;
  }

};

class VEostream:public VBostream, public Estream
{
public:
  VEostream(const string& file_name,Endian endian_fmt=HostEndian):\
    VBostream(file_name),Estream(endian_fmt){;}

  virtual ~VEostream(){;}

  template<class T>
  void put(const T& obj)
  {
    T cpy(obj);
    convert(cpy);
    VBostream::put(cpy);
  } 
    
  virtual Vostream& operator<<(int& i)
  { this->put(i); return *this;}

  virtual Vostream& operator<<(size_t& i)
  { this->put(i); return *this;}

  virtual Vostream& operator<<(float& f)
  { this->put(f); return *this;}

  virtual Vostream& operator<<(double& d)
  { this->put(d); return *this;}

};
     
class VXDRostream:public Vostream
{
  XDR _xdr;
  FILE* _outfile;

public:
  VXDRostream(const string& file_name)
  {
    _outfile=fopen(file_name.c_str(),"wb");
    if(_outfile==NULL)
    {
      ExceptionMsg m;
      m << "VXDRostream Error: Could not open " << file_name << \
        " in  write mode";
      throw Exception(m);
    }
    xdrstdio_create(&_xdr,_outfile,XDR_ENCODE);
  }

  virtual ~VXDRostream()
  {
     (void)fclose(_outfile);
      xdr_destroy(&_xdr);
  }

  virtual Vostream& operator<<(int i)
  { (void) xdr_int(&_xdr,&i); return *this;}

  virtual Vostream& operator<<(size_t i)
  { 
    if(sizeof(size_t)==sizeof(long unsigned int))
    {
      long unsigned int* p=reinterpret_cast<long unsigned int*>(&i);
      (void) xdr_u_long(&_xdr,p);
    }
    else
    {
      unsigned int* p=reinterpret_cast<unsigned int*>(&i);
      (void) xdr_u_int(&_xdr,p);
    }

    return *this;
  }

  virtual Vostream& operator<<(float f)
  { (void) xdr_float(&_xdr,&f); return *this;}

  virtual Vostream& operator<<(double d)
  { (void) xdr_double(&_xdr,&d); return *this;}

  virtual Vostream& operator<<(const string& str)
  {
    //char *c_data=strdup(str.c_str());
    char *c_data=const_cast<char*>(str.c_str());
    short sz=strlen(c_data);
    xdr_short(&_xdr,&sz);
    xdr_string(&_xdr,&c_data,sz);
    return *this;
  }


};

} // end namespace QDOS

#endif
