// This may look like C code, but it is really -*- C++ -*-
// 
// <copyright> 
//  
//  Copyright (c) 1993 
//  Institute for Information Processing and Computer Supported New Media (IICM), 
//  Graz University of Technology, Austria. 
//  
// </copyright> 
// 
// 
// <file> 
// 
// Name:        hgheader.C
// 
// Purpose:     header sent with documents from/to the document server
// 
// Created:     27 Oct 93   Joerg Faschingbauer
// 
// Modified:    
// 
// Description: 
// 
// 
// </file> 

// $Log: hgheader.C,v $
// Revision 1.17  1997/03/04 00:00:09  jfasch
// mytcp, yourtcp
//
// Revision 1.16  1997/03/03 19:10:39  jfasch
// yourtcp, mytcp
//
// Revision 1.15  1997/01/07 16:17:12  gorasche
// string.h for Windows strcasecmp
//
// Revision 1.14  1996/12/17 23:43:01  jfasch
// HWDcHeader::write() got const
//
// Revision 1.13  1996/12/09 14:22:43  jfasch
// can go from INIT and WSPRE into TERM with a null byte
//
// Revision 1.12  1996/12/09 12:38:04  jfasch
// forgot to init the "backward compatibility members" of HWDcHeader
//
// Revision 1.11  1996/12/09 12:33:51  jfasch
// added HWDcHeader::write()
//
// Revision 1.10  1996/12/09 06:22:38  jfasch
// initial buffer sizes from 16 to 8 (should be enough).
//
// Revision 1.9  1996/12/06 15:58:20  jfasch
// link time assertion for HWDcHeader
//
// Revision 1.8  1996/12/06 15:56:43  jfasch
// implemented HWDcHeader
//
// Revision 1.7  1996/10/15 15:45:43  jfasch
// verbose and assert movings
//
// Revision 1.6  1996/06/25 09:51:22  gpani
// WIN32
//
// Revision 1.5  1995/10/02 11:05:47  jfasch
// fut
//
// Revision 1.4  1995/10/02 10:58:07  jfasch
// int HgHeader :: read (const char*, int length)
//
// became
//
// int HgHeader :: read (char*, int length)
//
// because the underlying HgRegexp did so.
//
// Revision 1.3  1995/08/24 10:16:06  gpani
// $Log: hgheader.C,v $
// Revision 1.17  1997/03/04 00:00:09  jfasch
// mytcp, yourtcp
//
// Revision 1.16  1997/03/03 19:10:39  jfasch
// yourtcp, mytcp
//
// Revision 1.15  1997/01/07 16:17:12  gorasche
// string.h for Windows strcasecmp
//
// Revision 1.14  1996/12/17 23:43:01  jfasch
// HWDcHeader::write() got const
//
// Revision 1.13  1996/12/09 14:22:43  jfasch
// can go from INIT and WSPRE into TERM with a null byte
//
// Revision 1.12  1996/12/09 12:38:04  jfasch
// forgot to init the "backward compatibility members" of HWDcHeader
//
// Revision 1.11  1996/12/09 12:33:51  jfasch
// added HWDcHeader::write()
//
// Revision 1.10  1996/12/09 06:22:38  jfasch
// initial buffer sizes from 16 to 8 (should be enough).
//
// Revision 1.9  1996/12/06 15:58:20  jfasch
// link time assertion for HWDcHeader
//
// Revision 1.8  1996/12/06 15:56:43  jfasch
// implemented HWDcHeader
//
// Revision 1.7  1996/10/15 15:45:43  jfasch
// verbose and assert movings
//
// Revision 1.6  1996/06/25 09:51:22  gpani
// WIN32
//
// Revision 1.5  1995/10/02 11:05:47  jfasch
// fut
//
// Revision 1.4  1995/10/02 10:58:07  jfasch
// int HgHeader :: read (const char*, int length)
//
// became
//
// int HgHeader :: read (char*, int length)
//
// because the underlying HgRegexp did so.
// inserted
//
#include "hgheader.h"

#include "dbcontr_utils.h"

#include <hyperg/utils/assert.h>
#include <hyperg/utils/hgregexp.h>
#include <hyperg/utils/str.h>
#include <hyperg/utils/verbose.h>

#include <strstream.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#ifdef WIN32
#  include <strings.h>
#endif


// --------------------------------------------------------------------
const int HgHeader::maxsize = 1024 ;
const int HgHeader::minsize = 7 ; // the empty header: HGHDR\n\0
const char HgHeader::terminator = '\0' ;
const RString HgHeader::tag ("HGHDR") ;
const int HgHeader::notinit = -100 ;

const int HgHeader::version_ = 1 ;
const char* HgHeader::format_ = "%s\nver=%d\ntype=%d\nsz=%ld\nref=%ld\nZ=%d\n" ;

HgHeader :: HgHeader() {
   nil_out_() ;
}

HgHeader :: HgHeader (Def) {
   nil_out_() ;
   setDefaults() ;
}

HgHeader :: HgHeader (const HgHeader& h) {
   operator = (h) ;
}

HgHeader& HgHeader :: operator = (const HgHeader& h) {
   type_ = h.type_ ;
   size_ = h.size_ ;
   refno_ = h.refno_ ;
   compressed_ = h.compressed_ ;
   usedversion_ = h.usedversion_ ;
   return *this ;
}

int HgHeader :: read (char* s, int length) {
   static HgRegexp re_size ("^sz=(-?[0-9]+)$") ;
   static HgRegexp re_refno ("^ref=(-?[0-9]+)$") ;
   static HgRegexp re_type ("^type=([0-9]+)$") ;
   static HgRegexp re_compressed ("^Z=([0,1])$") ;
   static HgRegexp re_usedversion ("^ver=([0-9]+)$") ;

   nil_out_() ;
   
   if (length < minsize)
      return -1 ;

   char* run = s ;
   const char* end = s + length ;
   int linelen ;

   if ((linelen = get_line_(run, end)) <=0)
      return -1 ;
   if (tag != RString(run,linelen))
      return -1 ;
   
   run += linelen + 1 ;

   while ((linelen = get_line_(run, end)) > 0) {
      if (0 <= re_size.Match (run, linelen, 0))
         size_ = ::atol (run + re_size.BeginningOfMatch(1)) ;
      else if (0 <= re_refno.Match (run, linelen, 0))
         refno_ = ::atol (run + re_refno.BeginningOfMatch(1)) ;
      else if (0 <= re_type.Match (run, linelen, 0)) {
         int t = ::atoi (run + re_type.BeginningOfMatch(1)) ;
         if (t>=0 && t<NTYPES)
            type_ = HgHdrType (t) ;
         else 
            type_ = NTYPES ;
      }
      else if (0 <= re_compressed.Match (run, linelen, 0))
         compressed_ = ::atoi (run + re_compressed.BeginningOfMatch(1)) ;
      else if (0 <= re_usedversion.Match (run, linelen, 0))
         usedversion_ = ::atoi (run + re_usedversion.BeginningOfMatch(1)) ;

      run += linelen + 1 ;
   }

   if (linelen < 0) return -1 ; // no header 

   return run - s + 1 ;
}

void HgHeader :: write (RString& s) const {
   static char tmp[maxsize] ;
   int size ;
   write (tmp, size) ;
   s = RString (tmp, size) ;
}

void HgHeader :: write (char* s, int& size) const {
   hgassert (ok(), "HgHeader::write(): this not ok") ;
   // have to set default values since old versions of HgHeader::read() 
   // wont let values of notinit pass through
   HgHeader h (*this) ;
   h.setDefaults() ;
   ::sprintf (s, format_, 
              tag.string(), h.version(), h.type(), h.size(), h.refno(), h.compressed()) ;
   size = strlen (s) + 1 ;
}

void HgHeader :: setDefaults() {
   if (type_ < 0  ||  type_ >= NTYPES)
      type_ = UNKNOWN ; // 
   if (size_ == notinit)
      // server only checks for >= 0, but that means: "is initialized"
      size_ = -1 ; 
   if (refno_ == notinit) 
      // as above
      refno_ = -1 ; 
   if (compressed_ == notinit)
      // not compressed by default
      compressed_ = 0 ; 
   if (usedversion_ == notinit) 
      usedversion_ = version_ ;
}

boolean HgHeader :: possiblePrefix (const RString& str) {
   const char* h = tag.string() ;
   const char* s = str.string() ;
   int len = (str.length() <= tag.length()) ?  str.length() :  tag.length() ;
   
   for (int i=0 ; i<len ; i++, s++, h++) 
      if (*s != *h)
         return false ;
   return true ;
}

int HgHeader :: get_line_(const char* s, const char* end) {
   hgassert (s<=end, "HgHeader::get_line_(): already passed end of data") ;
   if (*s == '\0')
      return 0 ;
   const char* run = s ;
   while (run < end) {
      if (*run == '\n') {
         return run-s ;
      }
      run++ ;
   }
   return -1 ;
}

void HgHeader :: nil_out_() {
   type_ = NTYPES ;
   size_ = notinit ;
   refno_ = notinit ;
   compressed_ = notinit ;
   usedversion_ = version_ ;
}

boolean HgHeader :: is_nil_() const {
   return type_ == NTYPES &&
          size_ == notinit && 
         refno_ == notinit && 
    compressed_ == notinit ;
}

// --------------------------------------------------------------------
const char* HWDcHeader :: version4 = "HWDcHeader: $Id: hgheader.C,v 1.17 1997/03/04 00:00:09 jfasch Exp $" ;

HWDcHeader :: HWDcHeader() 
: type_(HgHeader::UNKNOWN),
  compressed_(false),
  usedversion_(1),
  size_(-1),
  refno_(-1),
  tag_(8), // initial size
  value_(8), // initial size
  state_(INIT) {}

HWDcHeader :: HWDcHeader (Def) 
: type_(HgHeader::UNKNOWN),
  compressed_(false),
  usedversion_(1),
  size_(-1),
  refno_(-1),
  state_(TERM) {}

HWDcHeader& HWDcHeader :: size (long s) {
   hgassert (state_==TERM, "HWDcHeader::size(long): this not ok") ;
   size_ = s ;
   return *this ;
}
HWDcHeader& HWDcHeader :: refno (long r) {
   hgassert (state_==TERM, "HWDcHeader::refno(long): this not ok") ;
   refno_ = r ;
   return *this ;
}

HWDcHeader& HWDcHeader :: yourTCP (const INETAddress& a) {
   hgassert (state_==TERM, "HWDcHeader::yourTCP(const INETAddress&): this not ok") ;
   your_tcp_ = a ;
   return *this ;
}

HWDcHeader& HWDcHeader :: myTCP (const INETAddress& a) {
   hgassert (state_==TERM, "HWDcHeader::myTCP(const INETAddress&): this not ok") ;
   my_tcp_ = a ;
   return *this ;
}

int HWDcHeader :: add (const char* s, int l) {
   int accepted = 0 ;
   while (ok() && !complete() && l--) {
      accepted += add(*s++)? 1: 0 ;
   }
   return accepted ;
}

bool HWDcHeader :: add (char c) {
   switch (state_) {
     case INIT:
        if (isspace(c))
           state_ = WSPRE ;
        else if (tolower(c) == 'h')
           state_ = H1 ;
        else if (c == '\0') 
           state_ = TERM ;
        else {
           DEBUGNL ("HWDcHeader::add() (INIT): invalid character ("<<c<<')') ;
           state_ = MYOWNERROR ;
        }
        break ;
     case WSPRE:
        if (tolower(c) == 'h')
           state_ = H1 ;
        else if (c == '\0') 
           state_ = TERM ;
        else if (!isspace(c)) {
           DEBUGNL ("HWDcHeader::add() (WSPRE): invalid character ("<<c<<')') ;
           state_ = MYOWNERROR ;
        }
        break ;
     case H1:
        if (tolower(c) == 'g') 
           state_ = G ;
        else {
           DEBUGNL ("HWDcHeader::add() (H1): invalid character ("<<c<<')') ;
           state_ = MYOWNERROR ;
        }
        break ;
     case G:
        if (tolower(c) == 'h') 
           state_ = H2 ;
        else {
           DEBUGNL ("HWDcHeader::add() (G): invalid character ("<<c<<')') ;
           state_ = MYOWNERROR ;
        }
        break ;
     case H2:
        if (tolower(c) == 'd') 
           state_ = D ;
        else {
           DEBUGNL ("HWDcHeader::add() (H2): invalid character ("<<c<<')') ;
           state_ = MYOWNERROR ;
        }
        break ;
     case D:
        if (tolower(c) == 'r') 
           state_ = R ;
        else {
           DEBUGNL ("HWDcHeader::add() (D): invalid character ("<<c<<')') ;
           state_ = MYOWNERROR ;
        }
        break ;
     case R:
        if (c == '\r') 
           state_ = CRB ;
        else if (c == '\n') 
           state_ = LFB ;
        else if (isspace(c)) 
           state_ = WSAFT ;
        else if (c == '\0')
           state_ = TERM ;
        else {
           DEBUGNL ("HWDcHeader::add() (R): invalid character ("<<c<<')') ;
           state_ = MYOWNERROR ;
        }
        break ;
     case WSAFT:
        if (c == '\r') 
           state_ = CRB ;
        else if (c == '\n')
           state_ = LFB ;
        else if (istag_(c)) {
           tag_ << c ;
           state_ = TAG ;
        }
        else if (c == '\0')
           state_ = TERM ;
        else if (! isspace(c)) {
           DEBUGNL ("HWDcHeader::add() (WSAFT): invalid character ("<<c<<')') ;
           state_ = MYOWNERROR ;
        }
        break ;
     case CRB:
        if (c == '\n') 
           state_ = LFB ;
        else if (isvalue_(c)) {
           tag_ << c ;
           state_ = TAG ;
        }
        else if (isspace(c))
           state_ = WS0 ;
        else if (c == '\0')
           state_ = TERM ;
        else {
           DEBUGNL ("HWDcHeader::add() (CRB): invalid character ("<<c<<')') ;
           state_ = MYOWNERROR ;
        }
        break ;
     case LFB:
        if (istag_(c)) {
           tag_ << c ;
           state_ = TAG ;
        }
        else if (isspace(c))
           state_ = WS0 ;
        else if (c == '\0')
           state_ = TERM ;
        else {
           DEBUGNL ("HWDcHeader::add() (LFB): invalid character ("<<c<<')') ;
           state_ = MYOWNERROR ;
        }
        break ;
     case WS0:
        if (istag_(c)) {
           tag_ << c ;
           state_ = TAG ;
        }
        else if (c == '\0')
           state_ = TERM ;
        else if (! isspace(c)) {
           DEBUGNL ("HWDcHeader::add() (WS0): invalid character ("<<c<<')') ;
           state_ = MYOWNERROR ;
        }
        break ;
     case TAG:
        if (istag_(c))
           tag_ << c ;
        else if (c == '=')
           state_ = EQ ;
        else if (isspace(c))
           state_ = WS1 ;
        else if (c == '\0') {
           state_ = TERM ;
           set_() ;
           term_() ;
        }
        else {
           DEBUGNL ("HWDcHeader::add() (TAG): invalid character ("<<c<<')') ;
           state_ = MYOWNERROR ;
        }
        break ;
     case WS1:
        if (c == '=')
           state_ = EQ ;
        else if (c == '\0') {
           set_() ;
           term_() ;
           state_ = TERM ;
        }
        else if (! isspace(c)) {
           DEBUGNL ("HWDcHeader::add() (WS1): invalid character ("<<c<<')') ;
           state_ = MYOWNERROR ;
        }
        break ;
     case EQ:
        if (isvalue_(c)) {
           value_ << c ;
           state_ = VAL ;
        }
        else if (c == '\r') {
           set_() ;
           state_ = CR ;
        }
        else if (c == '\n') {
           set_() ;
           state_ = LF ;
        }
        else if (isspace(c)) 
           state_ = WS2 ;
        else if (c == '\0') {
           set_() ;
           term_() ;
           state_ = TERM ;
        }
        else {
           DEBUGNL ("HWDcHeader::add() (EQ): invalid character ("<<c<<')') ;
           state_ = MYOWNERROR ;
        }
        break ;
     case WS2:
        if (isvalue_(c)) {
           value_ << c ;
           state_ = VAL ;
        }
        else if (c == '\0') {
           set_() ;
           term_() ;
           state_ = TERM ;
        }
        else if (! isspace(c)) {
           DEBUGNL ("HWDcHeader::add() (WS2): invalid character ("<<c<<')') ;
           state_ = MYOWNERROR ;
        }
        break ;
     case VAL:
        if (isvalue_(c))
           value_ << c ;
        else if (c == '\r') {
           set_() ;
           state_ = CR ;
        }
        else if (c == '\n') {
           set_() ;
           state_ = LF ;
        }
        else if (isspace(c)) {
           set_() ;
           state_ = WS3 ;
        }
        else if (c == '\0') {
           set_() ;
           term_() ;
           state_ = TERM ;
        }
        else {
           DEBUGNL ("HWDcHeader::add() (VAL): invalid character ("<<c<<')') ;
           state_ = MYOWNERROR ;
        }
        break ;
     case WS3:
        if (c == '\r')
           state_ = CR ;
        else if (c == '\n')
           state_ = LF ;
        else if (! isspace(c)) {
           DEBUGNL ("HWDcHeader::add() (WS3): invalid character ("<<c<<')') ;
           state_ = MYOWNERROR ;
        }
        break ;
     case CR:
        if (c == '\n') 
           state_ = LF ;
        else if (istag_(c)) {
           tag_ << c ;
           state_ = TAG ;
        }
        else if (isspace(c))
           state_ = WS0 ;
        else if (c == '\0') {
           term_() ;
           state_ = TERM ;
        }
        break ;
     case LF:
        if (istag_(c)) {
           tag_ << c ;
           state_ = TAG ;
        }
        else if (isspace(c))
           state_ = WS0 ;
        else if (c == '\0') {
           term_() ;
           state_ = TERM ;
        }
        else {
           DEBUGNL ("HWDcHeader::add() (LF): invalid character ("<<c<<')') ;
           state_ = MYOWNERROR ;
        }
        break ;
     case TERM:
        hgassert (false, "HWDcHeader::add() (TERM): didn\'t see that state?") ;
        break ;
     case MYOWNERROR:
        hgassert (false, "HWDcHeader::add() (MYOWNERROR): didn\'t see that state?") ;
        break ;
   }
   return state_ != MYOWNERROR ;
}

void HWDcHeader :: write (OBuffer& buf) const {
   hgassert (state_==TERM, "HWDcHeader::write(): attempt to write an incomplete header") ;

   static const char hghdr[] = "HGHDR\n" ;
   static const int hghdrlen = 6 ;
   char tmp[128] ; // should be enough ++++

   buf.write (hghdr, hghdrlen) ;
   ::sprintf (tmp, "ver=%d\n", usedversion_) ;
   buf << tmp ;
   ::sprintf (tmp, "type=%d\n", type_) ;
   buf << tmp ;
   ::sprintf (tmp, "sz=%ld\n", size_) ;
   buf << tmp ;
   ::sprintf (tmp, "ref=%ld\n", refno_) ;
   buf << tmp ;
   ::sprintf (tmp, "Z=%d\n", compressed_) ;
   buf << tmp ;
   const char* host = your_tcp_.host() ;
   int port = your_tcp_.port() ;
   ::sprintf (tmp, "yourtcp=%s,%d\n", host, port) ;
   buf << tmp ;
   host = my_tcp_.host() ;
   port = my_tcp_.port() ;
   ::sprintf (tmp, "mytcp=%s,%d\n", host, port) ;
   buf << tmp ;
   buf << '\0' ;
}

void HWDcHeader :: set_() {
   hgassert (tag_, "HWDcHeader::set_(): tag does not contain any data yet") ;
   tag_ << '\0' ;
   if (value_.count())
      value_ << '\0' ;
   if (::strcasecmp (tag_.data(), "sz") == 0) {
      if (value_.count())
         size_ = ::atoi (value_.data()) ;
   }
   else if (::strcasecmp (tag_.data(), "ref") == 0) {
      if (value_.count()) 
         refno_ = ::atoi (value_.data()) ;
   }
   else if (::strcasecmp (tag_.data(), "type") == 0) {
      // ignore
   }
   else if (::strcasecmp (tag_.data(), "z") == 0) {
      // ignore
   }
   else if (::strcasecmp (tag_.data(), "ver") == 0) {
      // ignore
   }
   else if (::strcasecmp (tag_.data(), "yourtcp") == 0) {
      INETAddress a ;
      if (! is_tcp_(RString (value_.data(), value_.count()), a)) {
         DEBUGNL ("HWDcHeader::set_(): (yourtcp): not a valid TCP endpoint") ;
      }
      else 
         your_tcp_ = a ;
   }
   else if (::strcasecmp (tag_.data(), "mytcp") == 0) {
      INETAddress a ;
      if (! is_tcp_(RString (value_.data(), value_.count()), a)) {
         DEBUGNL ("HWDcHeader::set_(): (mytcp): not a valid TCP endpoint") ;
      }
      else 
         my_tcp_ = a ;
   }
   else {
      DEBUGNL ("HWDcHeader::set_(): unknown field \""<<tag_.data()<<'"') ;      
   }
   tag_.reset() ;
   value_.reset() ;
}

void HWDcHeader :: term_() {
   tag_.free() ;
   value_.free() ;
}

bool HWDcHeader :: istag_(char c) {
   return isalpha(c) ;
}

bool HWDcHeader :: isvalue_(char c) {
   return isalnum(c) || 
      // '-' (minus) sign
      c == '-' ||
      // for TCP endpoints
      c==',' || c=='.' ;
}

bool HWDcHeader :: is_tcp_(const RString& s, INETAddress& a) {
   static HgRegexp re ("^"
                       "([^,]+)" /* host */
                       ","
                       "([0-9]+)" /* port */
                       "$") ;
   if (0 >= re.Match (s, s.length(), 0)) {
      DEBUGNL ("HWDcHeader::is_tcp_(): did not match") ;
      return false ;
   }
   else {
      RString hoststr (s.string() + re.BeginningOfMatch(1),
                       re.EndOfMatch(1) - re.BeginningOfMatch(1)) ;
      RString portstr (s.string() + re.BeginningOfMatch(2),
                       re.EndOfMatch(2) - re.BeginningOfMatch(2)) ;
      INETAddress atmp (hoststr.string(), ::atoi (portstr.string())) ;
      if (atmp) {
         a = atmp ;
         return true ;
      }
      else
         return false ;
   }
}
