/* Copyright (c) 2003-2006 MySQL AB

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; version 2 of the License.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA */

#include <ndb_global.h>
#include <SimpleProperties.hpp>
#include <NdbOut.hpp>
#include <NdbTCP.h>
#include <UtilBuffer.hpp>

bool
SimpleProperties::Writer::first(){
  return reset();
}

bool 
SimpleProperties::Writer::add(Uint16 key, Uint32 value){
  Uint32 head = Uint32Value;  
  head <<= 16;
  head += key;
  if(!putWord(htonl(head)))
    return false;
  
  return putWord(htonl(value));
}

bool
SimpleProperties::Writer::add(const char * value, int len){
  const Uint32 valLen = (len + 3) / 4;

  if ((len % 4) == 0)
    return putWords((Uint32*)value, valLen);

  const Uint32 putLen= valLen - 1;
  if (!putWords((Uint32*)value, putLen))
    return false;

  // Special handling of last bytes
  union {
    Uint32 lastWord;
    char lastBytes[4];
  } tmp;
  tmp.lastWord =0 ;
  memcpy(tmp.lastBytes,
         value + putLen*4,
         len - putLen*4);
  return putWord(tmp.lastWord);
}

bool
SimpleProperties::Writer::add(Uint16 key, const char * value){
  Uint32 head = StringValue;
  head <<= 16;
  head += key;
  if(!putWord(htonl(head)))
    return false;
  Uint32 strLen = strlen(value) + 1; // Including NULL-byte
  if(!putWord(htonl(strLen)))
    return false;

  return add(value, (int)strLen);

}

bool
SimpleProperties::Writer::add(Uint16 key, const void* value, int len){
  Uint32 head = BinaryValue;
  head <<= 16;
  head += key;
  if(!putWord(htonl(head)))
    return false;
  if(!putWord(htonl(len)))
    return false;

  return add((const char*)value, len);
}

SimpleProperties::Reader::Reader(){
  m_itemLen = 0;
}

bool 
SimpleProperties::Reader::first(){
  reset();
  m_itemLen = 0;
  return readValue();
}

bool
SimpleProperties::Reader::next(){
  return readValue();
}
    
bool
SimpleProperties::Reader::valid() const {
  return m_type != InvalidValue;
}

Uint16
SimpleProperties::Reader::getKey() const{
  return m_key;
}

Uint16
SimpleProperties::Reader::getValueLen() const {
  switch(m_type){
  case Uint32Value:
    return 4;
  case StringValue:
  case BinaryValue:
    return m_strLen;
  case InvalidValue:
    return 0;
  }
  return 0;
}

SimpleProperties::ValueType
SimpleProperties::Reader::getValueType() const {
  return m_type;
}
    
Uint32
SimpleProperties::Reader::getUint32() const {
  return m_ui32_value;
}

char * 
SimpleProperties::Reader::getString(char * dst) const {
  if(peekWords((Uint32*)dst, m_itemLen))
    return dst;
  return 0;
}

bool
SimpleProperties::Reader::readValue(){
  if(!step(m_itemLen)){
    m_type = InvalidValue;
    return false;
  }
  
  Uint32 tmp;
  if(!getWord(&tmp)){
    m_type = InvalidValue;
    return false;
  }

  tmp = ntohl(tmp);
  m_key = tmp & 0xFFFF;
  m_type = (SimpleProperties::ValueType)(tmp >> 16);
  switch(m_type){
  case Uint32Value:
    m_itemLen = 1;
    if(!peekWord(&m_ui32_value))
      return false;
    m_ui32_value = ntohl(m_ui32_value);
    return true;
  case StringValue:
  case BinaryValue:
    if(!getWord(&tmp))
      return false;
    m_strLen = ntohl(tmp);
    m_itemLen = (m_strLen + 3)/4;
    return true;
  default:
    m_itemLen = 0;
    m_type = InvalidValue;
    return false;
  }
}

SimpleProperties::UnpackStatus 
SimpleProperties::unpack(Reader & it, void * dst, 
			 const SP2StructMapping _map[], Uint32 mapSz,
			 bool ignoreMinMax,
			 bool ignoreUnknownKeys){
  do {
    if(!it.valid())
      break;
    
    bool found = false;
    Uint16 key = it.getKey();
    for(Uint32 i = 0; i<mapSz; i++){
      if(key == _map[i].Key){
	found = true;
	if(_map[i].Type == InvalidValue)
	  return Break;
	if(_map[i].Type != it.getValueType())
	  return TypeMismatch;
	
	char * _dst = (char *)dst;
	_dst += _map[i].Offset;
	
	switch(it.getValueType()){
	case Uint32Value:{
	  const Uint32 val = it.getUint32();
	  if(!ignoreMinMax){
	    if(val < _map[i].minValue)
	      return ValueTooLow;
	    if(val > _map[i].maxValue)
	      return ValueTooHigh;
	  }
	  * ((Uint32 *)_dst) = val;
	  break;
        }
	case BinaryValue:
        case StringValue:{
	  unsigned len = it.getValueLen();
	  if(len < _map[i].minValue)
	    return ValueTooLow;
	  if(len > _map[i].maxValue)
	    return ValueTooHigh;
          it.getString(_dst);
          break;
	}
	default:
	  abort();
	}
	break;
      }
    }
    if(!found && !ignoreUnknownKeys)
      return UnknownKey;
  } while(it.next());
  
  return Eof;
}

SimpleProperties::UnpackStatus 
SimpleProperties::pack(Writer & it, const void * __src, 
		       const SP2StructMapping _map[], Uint32 mapSz,
		       bool ignoreMinMax){

  const char * _src = (const char *)__src;

  for(Uint32 i = 0; i<mapSz; i++){
    bool ok = false;
    const char * src = _src + _map[i].Offset;
    switch(_map[i].Type){
    case SimpleProperties::InvalidValue:
      ok = true;
      break;
    case SimpleProperties::Uint32Value:{
      Uint32 val = * ((Uint32*)src);
      if(!ignoreMinMax){
	if(val < _map[i].minValue)
	  return ValueTooLow;
	if(val > _map[i].maxValue)
	  return ValueTooHigh;
      }
      ok = it.add(_map[i].Key, val);
    }
      break;
    case SimpleProperties::BinaryValue:{
      const char * src_len = _src + _map[i].Length_Offset;
      Uint32 len = *((Uint32*)src_len);
      if(!ignoreMinMax){
	if(len > _map[i].maxValue)
	  return ValueTooHigh;
      }
      ok = it.add(_map[i].Key, src, len);
      break;
    }
    case SimpleProperties::StringValue:
      if(!ignoreMinMax){
	size_t len = strlen(src);
	if(len > _map[i].maxValue)
	  return ValueTooHigh;
      }
      ok = it.add(_map[i].Key, src);
      break;
    }
    if(!ok)
      return OutOfMemory;
  }
  
  return Eof;
}

void
SimpleProperties::Reader::printAll(NdbOut& ndbout){
  char tmp[1024];
  for(first(); valid(); next()){
    switch(getValueType()){
    case SimpleProperties::Uint32Value:
      ndbout << "Key: " << getKey()
             << " value(" << getValueLen() << ") : " 
             << getUint32() << endl;
      break;
    case SimpleProperties::BinaryValue:
    case SimpleProperties::StringValue:
      if(getValueLen() < 1024){
	getString(tmp);
	ndbout << "Key: " << getKey()
	       << " value(" << getValueLen() << ") : " 
	       << "\"" << tmp << "\"" << endl;
      } else {
	ndbout << "Key: " << getKey()
	       << " value(" << getValueLen() << ") : " 
	       << "\"" << "<TOO LONG>" << "\"" << endl;
	
      }
      break;
    default:
      ndbout << "Unknown type for key: " << getKey() 
             << " type: " << (Uint32)getValueType() << endl;
    }
  }
}

SimplePropertiesLinearReader::SimplePropertiesLinearReader
(const Uint32 * src, Uint32 len){
  m_src = src;
  m_len = len;
  m_pos = 0;
  first();
}

void 
SimplePropertiesLinearReader::reset() { 
  m_pos = 0;
}

bool 
SimplePropertiesLinearReader::step(Uint32 len){
  m_pos += len;
  return m_pos < m_len;
}
  
bool
SimplePropertiesLinearReader::getWord(Uint32 * dst) { 
  if(m_pos<m_len){
    * dst = m_src[m_pos++];
    return true;
  } 
  return false;
}

bool 
SimplePropertiesLinearReader::peekWord(Uint32 * dst) const {
  if(m_pos<m_len){
    * dst = m_src[m_pos];
    return true;
  } 
  return false;
}

bool 
SimplePropertiesLinearReader::peekWords(Uint32 * dst, Uint32 len) const {
  if(m_pos + len <= m_len){
    memcpy(dst, &m_src[m_pos], 4 * len);
    return true;
  }
  return false;
}

LinearWriter::LinearWriter(Uint32 * src, Uint32 len){
  m_src = src;
  m_len = len;
  reset();
}

bool LinearWriter::reset() { m_pos = 0; return m_len > 0;}

bool 
LinearWriter::putWord(Uint32 val){
  if(m_pos < m_len){
    m_src[m_pos++] = val;
    return true;
  }
  return false;
}

bool 
LinearWriter::putWords(const Uint32 * src, Uint32 len){
  if(m_pos + len <= m_len){
    memcpy(&m_src[m_pos], src, 4 * len);
    m_pos += len;
    return true;
  }
  return false;
}

Uint32
LinearWriter::getWordsUsed() const { return m_pos;}

UtilBufferWriter::UtilBufferWriter(UtilBuffer & b)
  : m_buf(b)
{
  reset();
}

bool UtilBufferWriter::reset() { m_buf.clear(); return true;}

bool 
UtilBufferWriter::putWord(Uint32 val){
  return (m_buf.append(&val, 4) == 0);
}

bool 
UtilBufferWriter::putWords(const Uint32 * src, Uint32 len){
  return (m_buf.append(src, 4 * len) == 0);
}


Uint32
UtilBufferWriter::getWordsUsed() const { return m_buf.length() / 4;}

#if 0
LinearPagesReader::LinearPagesReader(const Uint32 * base, 
				     Uint32 pageSize, 
				     Uint32 headerSize,
				     Uint32 noOfPages, 
				     Uint32 len){
  m_base = base;
  m_pageSz = pageSize;
  m_noOfPages = noOfPages;
  m_pageHeaderSz = headerSize;
  m_len = len;
  reset();
}

void 
LinearPagesReader::reset() { m_pos = 0;}

bool 
LinearPagesReader::step(Uint32 len){
  m_pos += len;
  return m_pos < m_len;
}

bool 
LinearPagesReader::getWord(Uint32 * dst) { 
  if(m_pos<m_len){
    * dst = m_base[getPos(m_pos++)];
    return true;
  } 
  return false;
}

bool 
LinearPagesReader::peekWord(Uint32 * dst) const {
  if(m_pos<m_len){
    * dst = m_base[getPos(m_pos)];
    return true;
  } 
  return false;
}

bool 
LinearPagesReader::peekWords(Uint32 * dst, Uint32 len) const {
  if(m_pos + len <= m_len){
    for(Uint32 i = 0; i<len; i++)
      * (dst + i) = m_base[getPos(m_pos + i)];
    return true;
  }
  return false;
}

Uint32 
LinearPagesReader::getPos(Uint32 pos) const {
  const Uint32 sz = (m_pageSz - m_pageHeaderSz);
  Uint32 no = pos / sz;
  Uint32 in = pos % sz;
  return no * m_pageSz + m_pageHeaderSz + in;
}

LinearPagesWriter::LinearPagesWriter(Uint32 * base, 
				     Uint32 pageSize, 
				     Uint32 noOfPages, 
				     Uint32 headerSize){
  m_base = base;
  m_pageSz = pageSize;
  m_noOfPages = noOfPages;
  m_pageHeaderSz = headerSize;
  m_len = noOfPages * (pageSize - headerSize);
  reset();
}

bool 
LinearPagesWriter::putWord(Uint32 val){
  if(m_pos < m_len){
    m_base[getPos(m_pos++)] = val;
    return true;
  }
  return false;
}

bool 
LinearPagesWriter::putWords(const Uint32 * src, Uint32 len){
  if(m_pos + len <= m_len){
    for(Uint32 i = 0; i<len; i++)
      m_base[getPos(m_pos++)] = src[i];
    return true;
  }
  return false;
}

#if 0
Uint32 
LinearPagesWriter::getWordsUsed() const { 
  return getPos(m_pos);
}
#endif

Uint32 
LinearPagesWriter::getPagesUsed() const { 
  return m_pos / (m_pageSz - m_pageHeaderSz);
}

Uint32 
LinearPagesWriter::getPos(Uint32 pos) const {
  const Uint32 sz = (m_pageSz - m_pageHeaderSz);
  Uint32 no = pos / sz;
  Uint32 in = pos % sz;
  return no * m_pageSz + m_pageHeaderSz + in;
}
#endif
