/**********************************************************************
This file is part of Crack dot Com's free source code release of Golgotha.
for information about compiling & licensing issues visit this URL
 If that doesn't help, contact Jonathan Clark at 
  golgotha_source@usa.net (Subject should have "GOLG" in it) 
***********************************************************************/

// String Classes
//
//   Strings are hidden behind the two classes :
//
//       i4_const_str   (constant strings.. cannot be changed)
//   and i4_str         (modifiable strings)
//
//
//   i4_strs can only be created from i4_const_strs or other i4_strs and i4_const_strs can
//   only be created through the i4_string_manager.  All strings gotten from i4_string_manager
//   are loaded from an external file.  This ensures that all strings are stored external and
//   UNICODE or wide-chars can be added in without any code changes outside this module.
//   Because for most applications only one string manager is needed I have created a global
//   one called i4_string_man.  
//
//   To see if a string is available from the string manager the call
//
//   const i4_const_str *s=&i4_string_man.get(str);
//
//   is made.  If s->null()==i4_T then the string was not loaded.
//   Since this is a common operation the short-named function i4gets is provided 
//   (see end of this file).
//
//
//   example :
//
//   resource.res
//   ---------------
//   hello "Hello World"
//
//   ---------------
//
//
//   i4gets("hello") ->  i4_const_str("Hello World")
//
//
//   So, you might ask, how do I create a string quick and dirty for debugging purposes?
//
//   You have to do it the hard way for now.......

#ifndef __STRING_HPP_
#define __STRING_HPP_

#include "arch.hh"
#include "init/init.hh"
#include "error/error.hh"
#include "memory/malloc.hh"

#include 
#include 

class i4_char
{
protected:
  w8 ch;
public:
  i4_char(w8 ch) : ch(ch) {}
  w16 value() const { return ch; }
  i4_bool is_space() const { return (i4_bool)(ch==' ' || ch=='\r' || ch=='\n' || ch=='\t'); }
  i4_bool is_slash() const { return (i4_bool)(ch=='/'); }
  i4_bool is_backslash() const { return (i4_bool)(ch=='\\'); }
  i4_bool is_period() const { return (i4_bool)(ch=='.'); }

  i4_char to_lower() const 
  { return (ch>='A' && ch<='Z') ? i4_char(ch-'A'+'a') : i4_char(ch); }

  i4_char to_upper() const 
  { return (ch>='a' && ch<='z') ? i4_char(ch-'a'+'A') : i4_char(ch); }

  i4_bool operator==(const i4_char &b) const { return (ch == b.ch); }
  i4_bool operator!=(const i4_char &b) const { return (ch != b.ch); }
  i4_bool operator>(const i4_char &b) const { return  (ch > b.ch); }
  i4_bool operator<(const i4_char &b) const { return  (ch < b.ch); }
  i4_bool operator>=(const i4_char &b) const { return (ch >= b.ch); }
  i4_bool operator<=(const i4_char &b) const { return (ch <= b.ch); }

  // this should only be called if you know the string you are looking at has ascii chars in it
  w8 ascii_value() const { return ch; }
};


class i4_str;
class i4_string_manager_class;
class i4_file_class;


class i4_const_str
{
protected:
  typedef char char_type;

  char_type *ptr;
  w16   len;

  i4_const_str() : ptr(0), len(0) {}

  friend class i4_string_manager_class;

public:
  i4_const_str(const char *ptr) : ptr((char *)ptr) 
  { 
    if (ptr) 
      len=strlen(ptr); 
    else 
      len=0;
  }

  i4_const_str(char *ptr, w16 len) : ptr(ptr), len(len) {}

  i4_const_str(const i4_const_str &str) : ptr(str.ptr), len(str.len) {}


  class iterator
  {
    friend class i4_const_str;
    friend class i4_str;
  protected:
    char_type *node;

  public:
    iterator() : node(0) {}
    iterator(const iterator &p) : node(p.node) {}
    iterator(char_type *p) : node(p) {}

    int operator==(const iterator &p) const { return (node == p.node); }
    int operator!=(const iterator &p) const { return (node != p.node); }
    
    iterator& operator++() {     node++;  return *this; }
    iterator& operator++(int) { node++; return *this; }
    iterator& operator--()  {     node--;  return *this; }
    iterator operator+(const sw32 other)    {  return iterator(node+other); }
    iterator operator+=(const sw32 len) {   node+=len;   return *this;}

    
    i4_char get() const { return i4_char(*node); }

    i4_char operator* () const { return get(); }
    i4_str *read_string();
    w32     read_ascii(char *buffer, w32 buffer_size);  // returns bytes read
    sw32    read_number();
    double  read_float();
  };
  

  const iterator end()   const { return ptr+len; }
  const iterator begin() const { return ptr; }

  // number of characters in string
  w32 length() const { return len; }

  // ascii length includes null-terminator and be eventaully be longer than length()+1
  // when kanji support is added and escape characters are used
  w32 ascii_length() const { return len+1; } 

  sw32 ptr_diff(const iterator first, const iterator last) const
  { return last.node-first.node; }

  i4_bool operator== (const i4_const_str &other) const 
  { 
    if (len!=other.len)
      return 0;
    else return ::strncmp(other.ptr,ptr,len)==0; 
  }

  i4_bool operator!= (const i4_const_str &other) const { return !(other==*this); }
  int strncmp(const i4_const_str &other, w32 max_cmp) const
  {
    return ::strncmp(ptr,other.ptr,max_cmp); 
  }
  
  i4_bool null() const { return (i4_bool)(ptr==0); }

  // max_length is the maximum expanded length sprintf will create
  // sprintf uses it's own internal string as the format, and returns the
  // result of the sprintf operation as a new i4_str
  // example : i4_str *new_str=i4gets("format_str").sprintf(100,some_number);
  i4_str *sprintf(w32 max_length, ...) const;
  i4_str *vsprintf(w32 max_length, va_list &ap) const;

  iterator strstr(const i4_const_str &needle_to_find) const;
};


class i4_str : public i4_const_str
{
  friend i4_str *i4_from_ascii(const char *buf);
protected:
  w16 buf_len;

  i4_str(w16 _buf_len) { alloc(_buf_len); }

  void alloc(w16 _buf_len);  
  void init_from_string(const i4_const_str &str, w16 _buf_len);

public:
  class iterator : public i4_const_str::iterator
  {
  protected:
  public:
    iterator(const iterator &p) : i4_const_str::iterator(p) {}
    iterator(char_type *p) : i4_const_str::iterator((char_type *)p) {}
    void set(i4_char ch) { *((char *)node)=(char_type)ch.value(); }

  };
  
  i4_str(const i4_str &str) : i4_const_str(0) { init_from_string(str, (w16)str.length()); }
  i4_str(const i4_const_str &str) : i4_const_str(0) { init_from_string(str, (w16)str.length()); }
  i4_str(const i4_const_str &str, w16 _len) : i4_const_str(0) { init_from_string(str, _len); }

  // copies from start to end-1 characters (does not include end)
  i4_str(const i4_const_str::iterator start, const i4_const_str::iterator end, w16 buf_len);
  
  void insert(i4_str::iterator p, const i4_const_str &other);   // insert other before p
  void insert(i4_str::iterator p, const i4_char ch);            // insert ch before p

  void remove(i4_str::iterator start, i4_str::iterator last);

  void to_upper();   // converts all the chars in this string to upper case
  void to_lower();   // converts all the chars in this string to lower case

  const iterator end()   const { return ptr+len; }
  const iterator begin() const { return ptr; }

  void set_length(int l) { len=l; }

  ~i4_str();
};


class i4_linear_allocator;
class i4_grow_heap_class;
class i4_string_manager_class : public i4_init_class
{  

private:
  i4_grow_heap_class *string_heap;

  char  *alloc_str(char *string);
  void add_node(char *token, char *string);
  void add_array_node(char *token, char **array, w32 total);

  friend class i4_string_manager_saver_class;

  class node
  {
  public:
    typedef i4_linear_allocator node_allocator;
    static node_allocator *nodes;
    static w32 nodes_ref;           // number of string managers using 'nodes'

    char *str_token;
    i4_const_str value;
    node *left,*right;

#ifndef i4_NEW_CHECK
    void *operator new(size_t size);
    void operator delete(void *ptr);
#endif

    node(char *token, const i4_const_str &value) : str_token(token), value(value)
    {
      left=0;
      right=0;
    }
    
    ~node();
  };
  node *root;

#ifdef __MAC__
  node *new_node(char *token, const i4_const_str &value) 
  { 
    node *p = (node *)node::nodes->alloc();
    p->str_token = token;
    p->value = value;
    p->left = 0;
    p->right = 0;

    return p;
  }
  
  void delete_node(node *n)
  {
    if (n->left)
      delete_node(n->left);

    if (n->right)
      delete_node(n->right);

    node::nodes->free(n);
  }
#else
  node *new_node(char *token, const i4_const_str &value) { return new node(token,value); }
  void delete_node(node *n) { delete n; }
#endif

  class array_node
  {
  public:
    typedef i4_linear_allocator node_allocator;
    static node_allocator *nodes;

    char *str_token;
    char **value;
    array_node *left,*right;



#ifndef i4_NEW_CHECK
    void *operator new(size_t size);
    void operator delete(void *ptr);
#endif

    array_node(char *token, char **value) : str_token(token),value(value)
    {
      left=0;
      right=0;
    }

    ~array_node();
  } *array_root;
  
#ifdef __MAC__
  array_node *new_array_node(char *token, char **value) 
  { 
    array_node *p = (array_node *)array_node::nodes->alloc();
    p->str_token = token;
    p->value = value;
    p->left = 0;
    p->right = 0;

    return p;
  }
  
  void delete_array_node(array_node *n)
  {
    if (n->left)
      delete_array_node(n->left);

    if (n->right)
      delete_array_node(n->right);

    array_node::nodes->free(n);
  }
#else
  array_node *new_array_node(char *token, char **value) 
  { 
    return new array_node(token,value); 
  }
  void delete_array_node(array_node *n) { delete n; }
#endif

  void get_token(char *&s, char *&buf, w32 &line_on, char *error_prefix);
  void expand_macro(char *&s, char *&buf, w32 &line_on, char *error_prefix);
  void read_array(char *&s, 
                  char **array, 
                  w32 &total,
                  w32 &line_on, 
                  char *error_prefix, 
                  char *token_buf);

  void get_char(char *&s, char *&buf, w32 &line_on, char *error_prefix);

  void show_node(node *who);
  void show_nodes();

public:
  i4_string_manager_class();
  ~i4_string_manager_class();
  
  int init_type() { return I4_INIT_TYPE_STRING_MANAGER; }
  void init();
  void uninit();

  i4_bool       load(char *filename);
  i4_bool       load(const i4_const_str &filename);
  i4_bool       load_buffer(void *internal_buffer, char *error_prefix);

  const i4_const_str &get(const char *internal_name);
  const i4_const_str &get(const i4_const_str &internal_name);

  i4_const_str *get_array(const char *internal_name);
  i4_const_str *get_array(const i4_const_str &internal_name);
};

// "the" main string manager for a program
extern i4_string_manager_class i4_string_man;

const i4_const_str &i4gets(char *str, i4_bool barf_on_error=i4_T);
int i4getn(char *str, i4_bool barf_on_error=i4_T);

// converts an i4_const_str to an 8 bit ascii string (where possible)
extern char *i4_os_string(const i4_const_str &name, char *buffer, int buflen);

// converts from an ascii string to an i4_str
extern i4_str *i4_from_ascii(const char *buf);

#endif