// ---------------------------------------------------------------------------
// - Strvec.cpp                                                              -
// - standard object library - string vector class implementation            -
// ---------------------------------------------------------------------------
// - This program is free software;  you can redistribute it  and/or  modify -
// - it provided that this copyright notice is kept intact.                  -
// -                                                                         -
// - 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.  In no event shall -
// - the copyright holder be liable for any  direct, indirect, incidental or -
// - special damages arising in any way out of the use of this software.     -
// ---------------------------------------------------------------------------
// - copyright (c) 1999-2008 amaury darsch                                   -
// ---------------------------------------------------------------------------

#include "Stdsid.hxx"
#include "Strvec.hpp"
#include "Buffer.hpp"
#include "Vector.hpp"
#include "Unicode.hpp"
#include "Integer.hpp"
#include "Exception.hpp"

namespace afnix {

  // -------------------------------------------------------------------------
  // - private section                                                       -
  // -------------------------------------------------------------------------

  // check that a character is in the c-string. 
  static bool match_break_sequence (const t_quad c, const t_quad* str) {
    long size = Unicode::strlen (str);
    // loop and compare
    for (long i = 0; i < size; i++)
      if (c == str[i]) return true;
    return false;
  }

  // -------------------------------------------------------------------------
  // - public section                                                        -
  // -------------------------------------------------------------------------

  // split this string with a sequence of characters

  Strvec Strvec::split (const String& name, const String& sbrk) {
    Strvec result;
    Buffer buffer;
    
    // first thing first - do we have a nil string
    if (name.length () == 0) return result;
    
    // get a unicode string representation
    t_quad* data = name.toquad ();
    t_quad* cptr = data;
    // fix the break sequence in case it is nil
    const t_quad* cbrk = (sbrk.length () == 0) ? Unicode::strdup (" \t\n") : 
                                                 sbrk.toquad ();
    // loop and accumulate - if a character match the break sequence
    // the buffer is inserted into the vector
    t_quad c = nilq;
    buffer.reset ();
    while ((c = *data++) != nilp) {
      if (match_break_sequence (c, cbrk) == true) {
	result.add (buffer.tostring());
	buffer.reset ();
	continue;
      }
      buffer.add (c);
    }
    // check if the buffer is not empty
    if (buffer.length () != 0) result.add (buffer.tostring());
    // clean the break sequence and return
    delete [] cbrk;
    delete [] cptr;
    return result;
  }

  // split this string with a default break sequence

  Strvec Strvec::split (const String& name) {
    return Strvec::split (name, "");
  }

  // -------------------------------------------------------------------------
  // - class section                                                         -
  // -------------------------------------------------------------------------

  // create an empty string vector

  Strvec::Strvec (void) {
    d_size   = 0;
    d_length = 0;
    p_vector = nilp;
  }

  // create a string vector with an original size

  Strvec::Strvec (const long size) {
    if (size < 0) throw Exception ("size-error","negative strvec size");
    d_size   = size;
    d_length = 0;
    p_vector = new String[d_size];
  }

  // copy constructor for this string vector

  Strvec::Strvec (const Strvec& that) {
    that.rdlock ();
    // copy arguments
    d_size   = that.d_length;
    d_length = that.d_length;
    p_vector = nilp;
    // create a new vector of strings and copy them
    if ((d_length > 0) && (that.p_vector != nilp)) {
      p_vector = new String[d_length];
      for (long i = 0; i < d_length; i++) p_vector[i] = that.p_vector[i];
    }
    that.unlock ();
  }

  // destroy this string vector
  
  Strvec::~Strvec (void) {
    delete [] p_vector;
  }

  // return the class name

  String Strvec::repr (void) const {
    return "Strvec";
  }

  // return the string vector serial code

  t_byte Strvec::serialid (void) const {
    return SERIAL_STRV_ID;
  }

  // serialize this string vector

  void Strvec::wrstream (Output& os) const {
    rdlock ();
    try {
      // write the vector length
      Integer vlen (d_length);
      vlen.wrstream (os);
      // write the strings
      for (long i = 0; i < d_length; i++) {
	p_vector[i].wrstream (os);
      }
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // deserialize this string vector

  void Strvec::rdstream (Input& is) {
    wrlock ();
    try {
      // reset the string vector
      reset  ();
      // get the vector length
      Integer vlen;
      vlen.rdstream (is);
      long len = vlen.tointeger ();
      // read in each object
      for (long i = 0; i < len; i++) {
	String data;
	data.rdstream (is);
	add (data);
      }
      unlock ();
    } catch (...) {
      reset  ();
      unlock ();
      throw;
    }
  }

  // assign a string vector to this one
  
  Strvec& Strvec::operator = (const Strvec& that) {
    // check againt equal equal
    if (this == &that) return *this;
    // lock everything
    wrlock ();
    that.rdlock ();
    // delete old value
    delete [] p_vector;
    // copy arguments
    d_size   = that.d_length;
    d_length = that.d_length;
    p_vector = nilp;
    // create a new string vector of strings and copy them
    if ((d_length > 0) && (that.p_vector != nilp)) {
      p_vector = new String[d_size];
      for (long i = 0; i < d_length; i++) p_vector[i] = that.p_vector[i];
    }
    that.unlock ();
    unlock ();
    return *this;
  }

  // reset this vector

  void Strvec::reset (void) {
    wrlock ();
    delete [] p_vector;
    d_size   = 0;
    d_length = 0;
    p_vector = nilp;
    unlock ();
  }
  
  // return true if the vector is empty

  bool Strvec::empty (void) const {
    rdlock ();
    bool result = (d_length == 0);
    unlock ();
    return result;
  }

  // check that a string exists in this vector

  bool Strvec::exists (const String& name) const {
    rdlock ();
    try {
      if (d_length == 0) {
	unlock ();
	return false;
      }
      for (long i = 0; i < d_length; i++) {
	if (p_vector[i] == name) {
	  unlock ();
	  return true;
	}
      }
      unlock ();
      return false;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // get the number of element in this string vector

  long Strvec::length (void) const {
    rdlock ();
    long result = d_length;
    unlock ();
    return result;
  }

  // add a new element in this string vector
  
  void Strvec::add (const String& str) {
    wrlock ();
    try {
      // check if we have to resize the Strvec
      if (d_length + 1 >= d_size) {
	long size = (d_size <= 0) ? 1 : d_size * 2;
	String* vector = new String[size];
	for (long i = 0; i < d_length; i++) vector[i] = p_vector[i];
	delete [] p_vector;
	d_size   = size;
	p_vector = vector;
      }
      // set the string in this Strvec
      p_vector[d_length++] = str;
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // set an string at a certain position in this vector
  // the old string is destroyed

  void Strvec::set (const long index, const String& str) {
    wrlock ();
    try {
      // check that we are bounded
      if (index >= d_length) 
	throw Exception ("index-error","in string vector set");
      p_vector[index] = str;
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // pop the first string from this vector

  String Strvec::pop (void) {
    wrlock ();
    try {
      // check that the vector is not empty
      if (d_length == 0) {
	throw Exception ("pop-error", "pop request with empty vector");
      }
      // save first string
      String result = p_vector[0];
      // compress the vector
      for (long i = 1; i < d_length; i++) {
	p_vector[i-1] = p_vector[i];
      }
      // clear last element
      p_vector[--d_length] = "";
      // unlock and return
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // pop the last string from this vector

  String Strvec::rml (void) {
    wrlock ();
    try {
      // check that the vector is not empty
      if (d_length == 0) {
	throw Exception ("rml-error", "rml request with empty vector");
      }
      // save last string
      String result = p_vector[d_length-1];
      // clear last element
      p_vector[--d_length] = "";
      // unlock and return
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // get an string at a certain position

  String Strvec::get (const long index) const {
    rdlock ();
    try {
      // check that we are bounded
      if (index >= d_length) 
	throw Exception ("index-error","in string vector set");
      const String& result = p_vector[index];
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // return the first string in this vector

  String Strvec::first (void) const {
    rdlock ();
    try {
      String result = get (0);
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // return the last string in this vector

  String Strvec::last (void) const {
    rdlock ();
    try {
      String result = get (d_length-1);
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // return the index of a key or -1

  long Strvec::find (const String& key) const {
    rdlock ();
    try {
      for (long i = 0; i < d_length; i++) {
	if (p_vector[i] == key) {
	  unlock ();
	  return i;
	}
      }
      unlock ();
      return -1;
    } catch (...) {
      unlock () ;
      throw;
    }
  }

  // return the index of a key in this string vector

  long Strvec::lookup (const String& key) const {
    rdlock ();
    try {
      for (long i = 0; i < d_length; i++) {
	if (p_vector[i] == key) {
	  unlock ();
	  return i;
	}
      }
      throw Exception ("key-error", "key not found", key);
    } catch (...) {
      unlock () ;
      throw;
    }
  }
  
  // remove an entry by index and repack the vector

  void Strvec::remove (const long index) {
    wrlock ();
    try {
      if ((index < 0) || (index >= d_length)) {
	throw Exception ("index-error","index is out of range");
      }
      // repack the vector
      long mark = d_length - 1;
      for (long i = index; i < mark; i++) {
	p_vector[i] = p_vector[i+1];
      }
      p_vector[mark] = "";
      d_length = mark;
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // remove an entry by value if it exists

  void Strvec::remove (const String& key) {
    wrlock ();
    try {
      // get the key index
      long index = find (key);
      // remove the entry
      if (index != -1) remove (index);
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }
  // return the maximum string length in this vector

  long Strvec::maxlen (void) const {
    rdlock ();
    try {
      long result = 0;
      for (long i = 0; i < d_length; i++) {
	long len = p_vector[i].length ();
	if (len > result) result = len;
      }
      unlock ();
      return result;
    } catch (...) {
      unlock () ;
      throw;
    }
  }

  // return the minimum string length in this vector

  long Strvec::minlen (void) const {
    rdlock ();
    try {
      long result = 0;
      for (long i = 0; i < d_length; i++) {
	long len = p_vector[i].length ();
	if (len < result) result = len;
      }
      unlock ();
      return result;
    } catch (...) {
      unlock () ;
      throw;
    }
  }

  // return an array of quarks for this vector

  long* Strvec::toquarks (void) const {
    rdlock ();
    try {
      if (d_length == 0) {
	unlock ();
	return nilp;
      }
      long* result = new long[d_length];
      for (long i = 0; i < d_length; i++) result[i] = p_vector[i].toquark ();
      unlock ();
      return result;
    } catch (...) {
      unlock () ;
      throw;
    }
  }

  // retrun a vector of strings

  Vector* Strvec::tovector (void) const {
    rdlock ();
    Vector* result = new Vector;
    try {
      if (d_length == 0) {
	unlock ();
	return nilp;
      }
      for (long i = 0; i < d_length; i++) {
	result->append (new String (p_vector[i]));
      }
      unlock ();
      return result;
    } catch (...) {
      unlock () ;
      throw;
    } 
  }
}
