#include "varcontainer_io.h"
#include <iostream>
#include <sstream>
#include <fstream>
#include <cstring>

template<class T> VarTbuf<T>::~VarTbuf() {
    if (buf!=0) {
      //cout << "deleting VarTbuf<"<< sizeof(T)<<">,  size = " << nbuf<<endl ;
      delete[] buf ;
      buf=0 ; 
    } ;
    nbuf=0 ;
} ;

template<class T> void VarTbuf<T>::need(size_t n) {
    size_t nwant = (4*n)/3 ;   // allocate a little bit more than needed
    if (buf==0) {
        if (n>0) {
            nbuf = nwant ;
            buf  = new T[nbuf] ;
        }
    }
    else if (n>nbuf) {
        delete[] buf ;
        nbuf = nwant ;
        buf  = new T[nbuf] ;
    }
} ;

//
// ------------------ VarWorkerWriter ---------------------
//
VarWorkerWriter::VarWorkerWriter(string c, VarWriter* w) : container(c), writer(w), wasbegun(false), bsizet(), bbyte() {
    if (w==0) throw VarContainer::IOError(c,"null writer in constructor") ;
}
  
VarWorkerWriter::~VarWorkerWriter() {
    if (wasbegun) writer->error("write failure, done() not called on VarWorkerWriter") ;
    wasbegun=false ;
    //cout << "Destroying VarWorkerWriter for container " << container << endl ;
}
 
const int VarWorkerWriter::VERSION = 1 ;

void VarWorkerWriter::begin() {
    writer->begin() ;
    wasbegun=true ;
    const char* version = "VERSION" ;
    writer->send(reinterpret_cast<const Byte*>(version), 7) ;
    writer->send(reinterpret_cast<const Byte*>(&VERSION), sizeof(int)) ;
}
    
void VarWorkerWriter::done() { 
  if (wasbegun) {
    const char* fini = "DONE" ;
    writer->send(reinterpret_cast<const Byte*>(fini), 4) ;
    wasbegun=false ; 
    writer->done() ; 
  }
}  

void VarWorkerWriter::error(string msg) { 
    if (wasbegun) {
        wasbegun=false ; 
        writer->error(msg) ;
    }
}  

template<typename T> void VarWorkerWriter::write(const T* p, size_t nt) {
    writer->send(reinterpret_cast<const Byte*>(&nt), sizeof(size_t)) ;
    writer->send(reinterpret_cast<const Byte*>(p),   nt*sizeof(T)) ;
} ;

void VarWorkerWriter::write(const string* p, size_t nstring) {
    writer->send(reinterpret_cast<const Byte*>(&nstring), sizeof(size_t)) ; 
    const string* q = p ;
    for (size_t i=0 ; i<nstring ; i++) {
        const string* s = q++ ;
        size_t n = s->length() ; 
        writer->send(reinterpret_cast<const Byte*>(&n), sizeof(size_t)) ;
        writer->send(reinterpret_cast<const Byte*>(s->data()), n) ;    
    }
} ;

void VarWorkerWriter::write(const Shape* p,  size_t nshape) {
    writer->send(reinterpret_cast<const Byte*>(&nshape), sizeof(size_t)) ;
    const Shape* q = p ;
    for (size_t i=0 ; i<nshape ; i++) {
        const Shape* s = q++ ;
        
        size_t n = s->size() ;
        writer->send(reinterpret_cast<const Byte*>(&n), sizeof(size_t)) ;
        
        bsizet.need(n) ;
        for (size_t j=0 ; j<n ; j++) {
            bsizet.buf[j]=s->operator[](j) ; 
        } ;
        writer->send(reinterpret_cast<const Byte*>(bsizet.buf), n*sizeof(size_t)) ; 
    }
} ;

void VarWorkerWriter::write(const Bytes* p,  size_t nbytes) {
    writer->send(reinterpret_cast<const Byte*>(&nbytes), sizeof(size_t)) ;
    const Bytes* q = p ;
    for (size_t i=0 ; i<nbytes ; i++) {
        const Bytes* s = q++ ;
        
        size_t n = s->size() ;
        writer->send(reinterpret_cast<const Byte*>(&n), sizeof(size_t)) ;
        
        bbyte.need(n) ;
        for (size_t j=0 ; j<n ; j++) bbyte.buf[j]=s->operator[](j) ;
        writer->send(bbyte.buf, n) ; 
    }
} ;

void VarWorkerWriter::write(const fwrap_map_att* amap) {
    const char* start = "ATTS" ;
    writer->send(reinterpret_cast<const Byte*>(start), 4) ;  // mark beginning of atts

    size_t size = amap->atts.size() ;
    writer->send(reinterpret_cast<const Byte*>(&size), sizeof(size_t)) ;

    int ksize = 0 ;                // count the number of attributes actually written
    for ( map<string,fwrap_att*>::const_iterator p=amap->atts.begin() ; p!=amap->atts.end() ; ++p) {
        fwrap_int_att*    ip ;
        fwrap_double_att* dp ;
        fwrap_string_att* sp ;
        
        fwrap_att* ps = p->second ;
        
        string s = p->first ;
        int    t = ps->get_type() ;
        
        write(&s,1) ;           // name
        write<int>(&t,1) ;      // type
        
        switch (ps->get_type()) {
        case fwrap_att::itype:
            ip = dynamic_cast<fwrap_int_att*>(ps) ;
            write<int>(ip->ptr, ip->psize) ;
            break ;
        case fwrap_att::dtype:
            dp = dynamic_cast<fwrap_double_att*>(ps) ;
            write<double>(dp->ptr, dp->psize) ;
            break ;
        case fwrap_att::stype:
            sp = dynamic_cast<fwrap_string_att*>(ps) ;
            write(sp->ptr, sp->psize) ;
            break ;
        default:            
            throw VarContainer::IOError(container,"?VarWorkerWriter::write: unknown attribute type") ;
        }
        ksize++ ;
    } ;
    
    if (ksize!=size) {
        throw VarContainer::IOError(container,"?VarWorkerWriter::write: miscount in number of attributes") ;
    } ;

    const char* end = "STTA" ;
    writer->send(reinterpret_cast<const Byte*>(end), 4) ;  // mark end of atts
} ;

template<typename FP> void VarWorkerWriter::writeFwrap(const fwrapPtr<FP>& p) {
    const char* start = "DATA" ;
    writer->send(reinterpret_cast<const Byte*>(start), 4) ;  // mark beginning of data

    size_t rank = p->rank() ;
    writer->send(reinterpret_cast<const Byte*>(&rank), sizeof(size_t)) ;

    write(&(p->shape()),  1) ;
    write(&(p->pshape()), 1) ; 
    write<FP>(p->fptr(), p->pstore()) ;  // writes out size and then data
    
    const char* end = "ATAD" ;
    writer->send(reinterpret_cast<const Byte*>(end), 4) ;  // mark end of data
    write(p->att()) ;                                      // write out attributes
} ;

void VarWorkerWriter::writeFwrapBytes(const fwrapPtr<Bytes>& p) {
    const char* start = "DATA" ;
    writer->send(reinterpret_cast<const Byte*>(start), 4) ;  // mark beginning of data

    size_t rank = p->rank() ;
    writer->send(reinterpret_cast<const Byte*>(&rank), sizeof(size_t)) ;

    write(&(p->shape()),  1) ;
    write(&(p->pshape()), 1) ; 
    write(p->fptr(), p->pstore()) ;
    
    const char* end = "ATAD" ;
    writer->send(reinterpret_cast<const Byte*>(end), 4) ;  // mark end of data
    write(p->att()) ;                                      // write out attributes
}  

void VarWorkerWriter::writeFwrapLogical(const fwrapPtr<lp_32>& p) {
    const char* start = "DATA" ;
    writer->send(reinterpret_cast<const Byte*>(start), 4) ;  // mark beginning of data

    size_t rank = p->rank() ;
    writer->send(reinterpret_cast<const Byte*>(&rank), sizeof(size_t)) ;

    write(&(p->shape()),  1) ;
    write(&(p->pshape()), 1) ; 
    
    size_t total = p->pstore() ;
    
    bbyte.need(total) ;
    const lp_32* pl = p->fptr() ;
    for (size_t i=0 ; i<total ; i++) bbyte.buf[i] = (pl[i]) ? 1 : 0 ;
    
    write<Byte>(bbyte.buf, total) ;  // write out size and data
    
    const char* end = "ATAD" ;
    writer->send(reinterpret_cast<const Byte*>(end), 4) ;  // mark end of data
    write(p->att()) ;                                      // write out attributes
} 

//
// ------------------------ VarWorkerReader ----------------------
//
VarWorkerReader::VarWorkerReader(string c, VarReader* r) : container(c), reader(r), wasbegun(false),
        bsizet(), bint(), bdouble(), bstring(), bshape(), bbytes(), bchar(), bbyte() {
    if (r==0) throw VarContainer::IOError(c,"null reader in constructor") ;
} ;

VarWorkerReader::~VarWorkerReader() {
    if (wasbegun) reader->error("read failure, done() not called on VarWorkerReader") ;
    wasbegun=false ;
    //cout << "Destroying VarWorkerReader for container " << container << endl ;
} ;  

void VarWorkerReader::begin() {
    reader->begin() ;
    wasbegun=true ;
    
    char version[8] ;
    read<char>(version,7) ;
    version[7]=0 ;                           // null terminate
    if (strncmp("VERSION",version,7)!=0) {
        throw VarContainer::IOError(container,"did not receive the expected characters VERSION at the beginning") ;
    }
    
    int iv = 0 ;
    read<int>(&iv,1) ;
    if (iv!=VarWorkerWriter::VERSION) {
        ostringstream s ;
        s << "attempt to read serialized data with version " << iv << " but expected version " << VarWorkerWriter::VERSION ;
        throw VarContainer::IOError(container, s.str()) ;
    }
} ;  
    
    
void VarWorkerReader::done() { 
    char fini[5] ;
    read<char>(fini,4) ;
    fini[4]=0 ;                           // null terminate
    if (strncmp("DONE",fini,4)!=0) {
        throw VarContainer::IOError(container,"did not receive the expected characters DONE at the end") ;
    }
    
    if (wasbegun) {
        wasbegun=false ; 
        reader->done() ;
    }
}  

void VarWorkerReader::error(string msg)  { 
    if (wasbegun) {
        wasbegun=false ; 
        reader->error(msg) ;
    }
} ; 

template<typename T> void VarWorkerReader::read(T* p, size_t nt) {
    reader->receive(reinterpret_cast<Byte*>(p), nt*sizeof(T)) ;
} ;

size_t VarWorkerReader::readSizet() {
    size_t n = 0 ;
    read<size_t>(&n,1) ; toobig(n,100000,"size_t") ;
    bsizet.need(n) ;
    read<size_t>(bsizet.buf,n) ;
    return n ;
} ;  

size_t VarWorkerReader::readByte() {
    size_t n = 0 ;
    read<size_t>(&n,1) ;  toobig(n,1000000000,"byte") ;
    bbyte.need(n) ;
    read<Byte>(bbyte.buf,n) ;
    return n ;
} ;  

size_t VarWorkerReader::readInt() {
    size_t n = 0 ;
    read<size_t>(&n,1) ;  toobig(n,1000000000,"ints") ;
    bint.need(n) ;
    read<int>(bint.buf,n) ;
    return n ;
} ;  

size_t VarWorkerReader::readDouble() {
    size_t n = 0 ;
    read<size_t>(&n,1) ;  toobig(n,10000000000,"doubles") ;
    bdouble.need(n) ;
    read<double>(bdouble.buf, n) ;
    return n ;
} ;  

size_t VarWorkerReader::readString() {
    size_t n = 0 ;  // number of strings
    size_t m = 0 ;  // size of strings
    read<size_t>(&n,1) ;  toobig(n,1000000,"strings") ;
    bstring.need(n) ;
    for (size_t i=0 ; i<n ; i++) {
      read<size_t>(&m,1) ;   toobig(m,1000000,"chars in string") ;
      bchar.need(m) ;
      read<char>(bchar.buf,m) ; 
      bstring.buf[i] = string(bchar.buf,m) ;
    }
    return n ;
} ;  

size_t VarWorkerReader::readShape() {
    size_t n = 0 ;  // number of shapes
    size_t m = 0 ;  // size of shapes
    read<size_t>(&n,1) ; toobig(n,1000,"shapes") ;
    bshape.need(n) ;
    for (size_t i=0 ; i<n ; i++) {
      read<size_t>(&m,1) ;   toobig(m,1000,"shape size") ;// length of shape
      bsizet.need(m) ;
      read<size_t>(bsizet.buf, m) ;
      Shape s(m) ;
      for (size_t j=0 ; j<m ; j++) s[j]=bsizet.buf[j] ;
      bshape.buf[i].resize(s.size()) ;
      bshape.buf[i] = s ;
    }
    return n ;
} ;  

size_t VarWorkerReader::readBytes() {
    size_t n = 0 ;  // number of Bytes
    size_t m = 0 ;  // size of Bytes
    read<size_t>(&n,1) ; toobig(n,1000,"vector<byte>s") ;
    bbytes.need(n) ;
    for (size_t i=0 ; i<n ; i++) {
      read<size_t>(&m,1) ;   toobig(m,10000000000,"bytes") ; // length of Bytes
      bbyte.need(m) ;
      read<Byte>(bbyte.buf, m) ;
      Bytes s(m) ;
      for (size_t j=0 ; j<m ; j++) s[j]=bbyte.buf[j] ;
      bbytes.buf[i] = s ;
    }
    return n ;
} ;  

void VarWorkerReader::expect(size_t x, size_t g) {
    if (x!=g) {
        ostringstream s ;
        s << "expected " << x << " elements but receiving " << g << " elements" ;
        throw VarContainer::IOError(container, s.str()) ;    
    }
} ;

void VarWorkerReader::toobig(size_t g, size_t b, string msg) {
    if (g>b) {
        ostringstream s ;
        s << "number of " << msg << " to allocate = " << g << ", which is larger than max = " << b ;
        throw VarContainer::IOError(container, s.str()) ;        
    } ;
} ;

void VarWorkerReader::read(fwrap_map_att* amap) {
    char check[5] ;
    read<char>(check,4) ;
    check[4]=0 ;                           // null terminate
    if (strncmp("ATTS",check,4)!=0) {
        throw VarContainer::IOError(container,"did not receive the expected characters ATTS before reading attributes") ;
    }
    
    size_t n = 0 ;       // number of attributes
    read<size_t>(&n,1) ;
    
    for (size_t i=0 ; i<n ; i++) {
        fwrap_int_att*    ip ;
        fwrap_double_att* dp ;
        fwrap_string_att* sp ;
        
        expect(1, readString()) ;
        string s =*(ptrString()) ;
        
        expect(1, readInt()) ;
        fwrap_att::att_type t = static_cast<fwrap_att::att_type>(*(ptrInt())) ;
                
        size_t m ;
        
        switch (t) {
        case fwrap_att::itype:
            m = readInt() ;
            amap->set(s, ptrInt(), m) ;
            break ;
        case fwrap_att::dtype:
            m = readDouble() ;
            amap->set(s, ptrDouble(), m) ;
            break ;
        case fwrap_att::stype:
            m = readString() ;
            amap->set(s, ptrString(), m) ;
            break ;
        default:            
            throw VarContainer::IOError(container,"?VarWorkerReader::read: unknown attribute type") ;
        }
    } ;
    
    read<char>(check,4) ;
    check[4]=0 ;                           // null terminate
    if (strncmp("STTA",check,4)!=0) {
        throw VarContainer::IOError(container,"did not receive the expected characters STTA after reading attributes") ;
    }
} ;

template<typename FP> fwrapPtr<FP> VarWorkerReader::readFwrap() {
    char check[5] ;
    read<char>(check,4) ;
    check[4]=0 ;                           // null terminate
    if (strncmp("DATA",check,4)!=0) {
        throw VarContainer::IOError(container,"did not receive the expected characters DATA before reading data") ;
    }

    size_t rank = 0 ;       // rank of data
    read<size_t>(&rank,1) ;

    expect(1,readShape()) ;
    Shape shape = *(ptrShape()) ; expect(rank,shape.size()) ;
    
    expect(1,readShape()) ;
    Shape pshape = *(ptrShape()) ; expect(rank,pshape.size()) ;
    
    size_t total=1 ;
    for (size_t i=0 ; i<rank ; i++) total*=pshape[i] ;   // total number of elements
    
    size_t itotal = 0 ;
    read<size_t>(&itotal,1) ; expect(total, itotal) ;    // read size of streamed data
    
    fwrapPtr<FP> p = fwrapPtr<FP>::build(shape,pshape) ;  expect(total, p->pstore()) ;  // allocate data
    
    read<FP>(p->fptr(), itotal) ;          // read just the data
    
    read<char>(check,4) ;
    check[4]=0 ;                           // null terminate
    if (strncmp("ATAD",check,4)!=0) {
        throw VarContainer::IOError(container,"did not receive the expected characters ATAD after reading data") ;
    }
    
    read(p->att()) ;   // finally read the atributes
    
    return p ;
};   


fwrapPtr<Bytes> VarWorkerReader::readFwrapBytes() {
    char check[5] ;
    read<char>(check,4) ;
    check[4]=0 ;                           // null terminate
    if (strncmp("DATA",check,4)!=0) {
        throw VarContainer::IOError(container,"did not receive the expected characters DATA before reading data") ;
    }

    size_t rank = 0 ;       // rank of data
    read<size_t>(&rank,1) ;

    expect(1,readShape()) ;
    Shape shape = *(ptrShape()) ; expect(rank,shape.size()) ;
    
    expect(1,readShape()) ;
    Shape pshape = *(ptrShape()) ; expect(rank,pshape.size()) ;
    
    size_t total=1 ;
    for (size_t i=0 ; i<rank ; i++) total*=pshape[i] ;
    
    expect(total, readBytes()) ;  // read vector<Byte>'s into buffer
    
    fwrapPtr<Bytes> p = fwrapPtr<Bytes>::build(shape,pshape) ;  // allocate a bunch of empty vector<Bytes>
    
    Bytes* q = ptrBytes() ;   // pointer to each vector<Byte> read
    Bytes* s = p->fptr() ;    // pointer to destination vector<Byte> 
    
    for (size_t i=0 ; i<total ; i++) *(s++)=*(q++) ;   // copy vector<Byte>'s
    
    read<char>(check,4) ;
    check[4]=0 ;                           // null terminate
    if (strncmp("ATAD",check,4)!=0) {
        throw VarContainer::IOError(container,"did not receive the expected characters ATAD after reading data") ;
    }
    
    read(p->att()) ;
    
    return p ;
};   

fwrapPtr<lp_32> VarWorkerReader::readFwrapLogical(lp_32 lp_true, lp_32 lp_false) {
    char check[5] ;
    read<char>(check,4) ;
    check[4]=0 ;                           // null terminate
    if (strncmp("DATA",check,4)!=0) {
        throw VarContainer::IOError(container,"did not receive the expected characters DATA before reading data") ;
    }

    size_t rank = 0 ;       // rank of data
    read<size_t>(&rank,1) ;

    expect(1,readShape()) ;
    Shape shape = *(ptrShape()) ; expect(rank,shape.size()) ;
    
    expect(1,readShape()) ;
    Shape pshape = *(ptrShape()) ; expect(rank,pshape.size()) ;
    
    size_t total=1 ;
    for (size_t i=0 ; i<rank ; i++) total*=pshape[i] ;
    
    expect(total, readByte()) ;  // read Bytes's into buffer
    
    fwrapPtr<lp_32> p = fwrapPtr<lp_32>::build(shape,pshape) ; 
    
    Byte*  q = ptrByte() ;    // pointer to each Byte read
    lp_32* s = p->fptr() ;    // pointer to destination logical 
    
    for (size_t i=0 ; i<total ; i++) *(s++)=*(q++) ? lp_true : lp_false ;   // copy respecting the local logical
    
    read<char>(check,4) ;
    check[4]=0 ;                           // null terminate
    if (strncmp("ATAD",check,4)!=0) {
        throw VarContainer::IOError(container,"did not receive the expected characters ATAD after reading data") ;
    }
    
    read(p->att()) ;
    
    return p ;
} ;

// --------------- instantiate --------------
template void VarWorkerWriter::write<int>(const int* p, size_t nt) ;
template void VarWorkerWriter::write<Byte>(const Byte* p, size_t nt) ;
template void VarWorkerWriter::write<char>(const char* p, size_t nt) ;
template void VarWorkerWriter::write<size_t>(const size_t* p, size_t nt) ;
template void VarWorkerWriter::write<float>(const float* p, size_t nt) ;
template void VarWorkerWriter::write<double>(const double* p, size_t nt) ;

template void VarWorkerWriter::writeFwrap(const fwrapPtr<cp_char>&) ;
template void VarWorkerWriter::writeFwrap(const fwrapPtr<ip_32>&) ;
template void VarWorkerWriter::writeFwrap(const fwrapPtr<fp_32>&) ;
template void VarWorkerWriter::writeFwrap(const fwrapPtr<fp_64>&) ;

template void VarWorkerReader::read<int>(int* p, size_t nt) ;
template void VarWorkerReader::read<Byte>(Byte* p, size_t nt) ;
template void VarWorkerReader::read<char>(char* p, size_t nt) ;
template void VarWorkerReader::read<size_t>(size_t* p, size_t nt) ;
template void VarWorkerReader::read<float>(float* p, size_t nt) ;
template void VarWorkerReader::read<double>(double* p, size_t nt) ;

template fwrapPtr<cp_char> VarWorkerReader::readFwrap<cp_char>() ;
template fwrapPtr<ip_32>   VarWorkerReader::readFwrap<ip_32>() ;
template fwrapPtr<fp_32>   VarWorkerReader::readFwrap<fp_32>() ;
template fwrapPtr<fp_64>   VarWorkerReader::readFwrap<fp_64>() ;

// -------------------- VarVarFileWriter ------------------------
VarFileWriter::VarFileWriter(string name) : VarWriter(), fname(name), fs(0) { }; 

VarFileWriter::~VarFileWriter() {
    if (fs!=0) {
        if (fs->is_open()) fs->close() ;
        delete fs ;
        fs=0 ;
    }
} ;
    
void VarFileWriter::begin() {
    if (fs!=0) throw VarContainer::IOError("unknown","begin() called but file "+fname+" is already open?") ;
    
    ofstream* f = new ofstream(fname.c_str(), ios_base::binary) ;
    if (!f->is_open()) {
        delete f ;
        throw VarContainer::IOError("unknown","failed to open the file "+fname+" for the VarFileWriter") ;
    }
    fs = f ;
} ;  
    
void VarFileWriter::send(const Byte* p, size_t nbytes) {
    if (fs==0) throw VarContainer::IOError("unknown","attempt to write before file "+fname+" was opened") ;

    fs->write(reinterpret_cast<const char*>(p), nbytes) ;

    if (!fs->good()) throw VarContainer::IOError("unknown","error writing to file "+fname) ;
}; 
    
void VarFileWriter::done() {
    if (fs!=0) {
        if (fs->is_open()) fs->close() ;
        delete fs ;
        fs=0 ;
    }
}; 
    
void VarFileWriter::error(string msg) {
    cerr << "?VarFileWriter: error with file " << fname << ", " << msg << endl ;
    done() ;  // close it out
} ;    

// -------------------- VarFileReader ------------------------
VarFileReader::VarFileReader(string name) : VarReader(), fname(name), fs(0) { }; 

VarFileReader::~VarFileReader() {
    if (fs!=0) {
        if (fs->is_open()) fs->close() ;
        delete fs ;
        fs=0 ;
    }
} ;
    
void VarFileReader::begin() {
    if (fs!=0) throw VarContainer::IOError("unknown","begin() called but file "+fname+" is already open?") ;
    
    ifstream* f = new ifstream(fname.c_str(), ios_base::binary) ;
    if (!f->is_open()) {
        delete f ;
        throw VarContainer::IOError("unknown","failed to open the file "+fname+" for the VarFileReader") ;
    }
    fs = f ;
} ;  
    
void VarFileReader::receive( Byte* p, size_t nbytes) {
    if (fs==0) throw VarContainer::IOError("unknown","attempt to read before file "+fname+" was opened") ;

    fs->read(reinterpret_cast<char*>(p), nbytes) ;

    if (fs->bad()||fs->fail()) {
      if (fs->eof()) {
        throw VarContainer::IOError("unknown","error reading, possibly due to end of file, from file "+fname) ;
      }
      else {
        throw VarContainer::IOError("unknown","error reading from file "+fname) ;
      }
    }
}; 
    
void VarFileReader::done() {
    if (fs!=0) {
        if (fs->is_open()) fs->close() ;
        delete fs ;
        fs=0 ;
    }
}; 
    
void VarFileReader::error(string msg) {
    cerr << "?VarFileReader: error with file " << fname << ", " << msg << endl ;
    done() ;  // close it out
} ;    
