/**********************************************************************
 *
 * dbclass.cpp -- 
 * Copyright (C) 1999-2008  The New Zealand Digital Library Project
 *
 * A component of the Greenstone digital library software
 * from the New Zealand Digital Library Project at the
 * University of Waikato, New Zealand.
 *
 * 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; either version 2 of the License, or
 * (at your option) any later version.
 *
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *********************************************************************/

#include "dbclass.h"
#include "OIDtools.h"
#include "unitool.h"


dbclass::~dbclass() {}


// May be overwritten by subclasses; returns true if exists
bool dbclass::exists (const text_t& key)
{
  text_t data;
  return getkeydata (key, data);
}


// May be overwritten by subclasses; returns true on success
bool dbclass::getinfo (const text_t& key, infodbclass &info)
{
  text_t data;
  if (!getkeydata(key, data)) return false;

  // Use getinfoline() to parse the data value into the infodbclass object
  text_t::iterator data_iterator = data.begin();
  text_t ikey, ivalue;
  info.clear();
  while (getinfoline(data_iterator, data.end(), ikey, ivalue))
  {
    info.addinfo(ikey, ivalue);
  }

  return true;
}


// returns true on success
bool dbclass::getinfoline (text_t::iterator &here, text_t::iterator end, 
			   text_t &key, text_t &value)
{
  key.clear();
  value.clear();

  // ignore white space
  while (here != end && is_unicode_space (*here)) ++here;

  // get the '<'
  if (here == end || *here != '<') return false;
  ++here;
  
  // get the key
  while (here != end && *here != '>') {
    key.push_back(*here);
    ++here;
  }
  
  // get the '>'
  if (here == end || *here != '>') return false;
  ++here;
  
  // get the value
  while (here != end && *here != '\n') {
    if (*here == '\\') { 
      // found escape character
      ++here;
      if (here != end) {
	if (*here == 'n') value.push_back ('\n');
	else if (*here == 'r') value.push_back ('\r');
	else value.push_back(*here);
      }

    } else {
      if(*here != '\r') {
	// a normal character
	value.push_back(*here);
      }
    }

    ++here;
  }

  return true;
}


// returns true on success
bool dbclass::setinfo (const text_t &key, const infodbclass &info)
{
  text_t subkey;
  text_t data;

  // get all the keys and values
  infodbclass::const_iterator info_here = info.begin();
  infodbclass::const_iterator info_end = info.end();
  while (info_here != info_end) {
    // add the key
    subkey.clear();
    subkey.push_back('<');
    text_t::const_iterator subkey_here = (*info_here).first.begin();
    text_t::const_iterator subkey_end = (*info_here).first.end();
    while (subkey_here != subkey_end) {
      if (*subkey_here == '>') {
	subkey.push_back('\\'); subkey.push_back('>');
      } else if (*subkey_here == '\n') {
	subkey.push_back('\\'); subkey.push_back('n');
      } else if (*subkey_here == '\r') {
	subkey.push_back('\\'); subkey.push_back('r');
      } else if (*subkey_here == '\\') {
	subkey.push_back('\\'); subkey.push_back('\\');
      } else {
	subkey.push_back (*subkey_here);
      }
      ++subkey_here;
    }
    subkey.push_back('>');

    // add the values
    text_tarray::const_iterator subvalue_here = (*info_here).second.begin();
    text_tarray::const_iterator subvalue_end = (*info_here).second.end();
    while (subvalue_here != subvalue_end) {
      data += subkey;
      
      text_t::const_iterator thissubvalue_here = (*subvalue_here).begin();
      text_t::const_iterator thissubvalue_end = (*subvalue_here).end();
      while (thissubvalue_here != thissubvalue_end) {
	if (*thissubvalue_here == '>') {
	  data.push_back('\\'); data.push_back('>');
	} else if (*thissubvalue_here == '\n') {
	  data.push_back('\\'); data.push_back('n');
	} else if (*thissubvalue_here == '\r') {
	  data.push_back('\\'); data.push_back('r');
	} else if (*thissubvalue_here == '\\') {
	  data.push_back('\\'); data.push_back('\\');
	} else {
	  data.push_back (*thissubvalue_here);
	}
	
	++thissubvalue_here;
      }
      
      data.push_back('\n');
      ++subvalue_here;
    }

    ++info_here;
  }

  return setkeydata(key, data);
}


// replaces the .fc, .lc, .pr, .rt, .ns and .ps syntax (first child, 
// last child, parent, root, next sibling, previous sibling) 
// it expects child, parent, etc. to exist if syntax has been used
// so you should test before using
text_t dbclass::translate_OID (const text_t &inOID, infodbclass &info)
{
  if (inOID.size() < 4) return inOID;
  if (findchar (inOID.begin(), inOID.end(), '.') == inOID.end()) return inOID;

  text_t OID = inOID;
  text_tarray tailarray;
  text_t tail = substr (OID.end()-3, OID.end());
  if (tail == ".rt") {
    get_top (inOID, OID);
    return OID;
  }
  while (tail == ".fc" || tail == ".lc" || tail == ".pr" || 
	 tail == ".ns" || tail == ".ps") {
    tailarray.push_back(tail);
    OID.erase (OID.end()-3, OID.end());

	if(OID.size() <= 3) {
		break;
	} else {
		tail = substr (OID.end()-3, OID.end());
	}
    if (tail == ".rt") {
      get_top (inOID, OID);
      return OID;
    }
  }

  if (tailarray.empty()) return inOID;
  text_tarray::const_iterator begin = tailarray.begin();
  text_tarray::const_iterator here = tailarray.end() - 1;

  while (here >= begin) {

    if (*here == ".fc")
      get_first_child (OID, info);
    else if (*here == ".lc")
      get_last_child (OID, info);
    else if (*here == ".pr")
      OID = get_parent (OID);
    else if (*here == ".ns")
      get_next_sibling (OID, info);
    else if (*here == ".ps")
      get_previous_sibling (OID, info);
    
    if (here == begin)
      break;
    --here;
  }

  return OID;
}


void dbclass::get_first_child (text_t &OID, infodbclass &info)
{
  text_t firstchild;
  if (getinfo (OID, info)) {
    text_t &contains = info["contains"];
    if (!contains.empty()) {
      text_t parent = OID;
      getdelimitstr (contains.begin(), contains.end(), ';', firstchild);
      if (firstchild.empty()) OID = contains;
      else OID = firstchild;
      if (*(OID.begin()) == '"') translate_parent (OID, parent);
    }
  }
}


void dbclass::get_last_child (text_t &OID, infodbclass &info)
{
  text_tarray children;
  if (getinfo (OID, info)) {
    text_t &contains = info["contains"];
    if (!contains.empty()) {
      text_t parent = OID;
      splitchar (contains.begin(), contains.end(), ';', children);
      OID = children.back();
      if (*(OID.begin()) == '"') translate_parent (OID, parent);
    }
  }
}

  
void dbclass::get_next_sibling (text_t &OID, infodbclass &info)
{
  text_tarray siblings;
  text_t parent = get_parent (OID);
	     
  if (getinfo (parent, info)) {
    text_t &contains = info["contains"];
    if (!contains.empty()) {
      splitchar (contains.begin(), contains.end(), ';', siblings);
      text_tarray::const_iterator here = siblings.begin();
      text_tarray::const_iterator end = siblings.end();
      text_t shrunk_OID = OID;
      shrink_parent (shrunk_OID);
      while (here != end) {
	if (*here == shrunk_OID && (here+1 != end)) {
	  OID = *(here+1);
	  if (*(OID.begin()) == '"') translate_parent (OID, parent);
	  break;
	}
	++here;
      }
    }
  }
}


void dbclass::get_previous_sibling (text_t &OID, infodbclass &info)
{
  text_tarray siblings;
  text_t parent = get_parent (OID);

  if (getinfo (parent, info)) {
    text_t &contains = info["contains"];
    if (!contains.empty()) {
      splitchar (contains.begin(), contains.end(), ';', siblings);
      text_tarray::const_iterator here = siblings.begin();
      text_tarray::const_iterator end = siblings.end();
      text_t shrunk_OID = OID;
      shrink_parent (shrunk_OID);
      while (here != end) {
	if (*here == shrunk_OID && (here != siblings.begin())) {
	  OID = *(here-1);
	  if (*(OID.begin()) == '"') translate_parent (OID, parent);
	  break;
	}
	++here;
      }
    }
  }
}
