// Copyright 1995 Barbara Liskov

#include <assert.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <iostream.h>
#include <stddef.h>

#include "6170.h"
#include "generator.h"
#include "str.h"
#include "array.h"

typedef generator<char> IC;
typedef array<char>     AC;

class flat_rep;

// Abstract definition of a string rep.  Multiple implementations of
// this type are used.  Since these reps are never mutated, we can
// share them when constructing new strings.

class string_rep {
  public:
    virtual int  length() = 0;
    virtual char fetch(int) = 0;
    virtual IC*  chars(int start, int len) = 0;

    virtual flat_rep* flat();
    // effects	Return a flattened representation of "this".
    //		We provide a default definition here that is inherited
    //		by all subclasses of "string_rep", unless the subclass
    //		provides its own implementation
};

// Empty string rep
class empty_rep : public string_rep {
  public:
    empty_rep();
    virtual int  length();
    virtual char fetch(int);
    virtual IC*  chars(int, int);
};

// A flat array of characters.
class flat_rep : public string_rep {
  public:
    flat_rep(char* s, int l);

    virtual int  length();
    virtual char fetch(int);
    virtual IC*  chars(int, int);
    virtual flat_rep* flat();
    int     hash();

    char const* ptr();
    // Used to access flattened rep
  private:
    int size;
    char* str;
};

// A concatenation of two string reps.
class concat_rep : public string_rep {
  public:
    concat_rep(string_rep* s1, string_rep* s2);

    virtual int  length();
    virtual char fetch(int);
    virtual IC*  chars(int, int);
  private:
    int size1, size2;
    string_rep* rep1;
    string_rep* rep2;
};

// A substring of another string rep.
class substring_rep : public string_rep {
  public:
    substring_rep(string_rep* r, int s, int l);

    virtual int  length();
    virtual char fetch(int);
    virtual IC*  chars(int, int);
  private:
    int start;
    int len;
    string_rep* rep;
};

// Default definition of "flat" allocates a new flat_rep and fills it in
flat_rep* string_rep::flat() {
    int num = length();
    char* buf = new(atomic) char[num];
    IC* iter = chars(0, num);
    for (int i = 0; i < num; i++)
	iter->get(buf[i]);
    return new flat_rep(buf, num);
}

empty_rep::empty_rep() {
}

int empty_rep::length() {
    return 0;
}

char empty_rep::fetch(int index) {
    // Not allowed to fetch out of range...
    check(FALSE, "fetch on empty string");
    return 0;
}

class Empty_Iter : public IC {
  public:
    virtual bool get(char& c) {
	// Nothing to yield
	return FALSE;
    }
};

IC* empty_rep::chars(int s, int l) {
    // Generator representation
    return new Empty_Iter;
}

flat_rep::flat_rep(char* s, int l) {
    size = l;
    str = s;
}

int flat_rep::length() {
    return size;
}

char flat_rep::fetch(int index) {
    return str[index];
}

IC* flat_rep::chars(int s, int l) {
    // Generator representation
    class Flat_Iter : public IC {
      public:
	char* str;		// The base string
	int   index;		// Current index into string
	int   limit;		// Stop iterating when this limit is reached

	virtual bool get(char& c) {
	    if (index < limit) {
		c = str[index];
		index++;
		return TRUE;
	    }
	    return FALSE;
	}
    };

    Flat_Iter* rep = new Flat_Iter;
    rep->str = str;
    rep->index = s;
    rep->limit = s + l;
    return rep;
}

// Fast definition of "flat".
flat_rep *flat_rep::flat() {
    return this;
}

inline char const* flat_rep::ptr() {
    return str;
}

int flat_rep::hash()
{
    char *p = str;
    int n = size;
    int sum = 0;
    if ((size_t)n >= sizeof(int)) {
	sum = *(int *)p;
    } else {
        while (n-- > 0) {
            sum += sum << 4;
            sum += *p++;
        }
        return sum;
    }
    if ((size_t)n >= 2*sizeof(int)) {
	int *end = (int *)((ptrdiff_t)(p + n - 4) & -sizeof(int));
	sum += (*end << 3);
    }
    return sum;
}

concat_rep::concat_rep(string_rep* s1, string_rep* s2) {
    size1 = s1->length();
    size2 = s2->length();
    rep1 = s1;
    rep2 = s2;
}

int concat_rep::length() {
    return size1 + size2;
}

char concat_rep::fetch(int index) {
    if (index < size1)
	return rep1->fetch(index);
    else
	return rep2->fetch(index - size1);
}

IC* concat_rep::chars(int s, int l) {
    // Need full rep
    class Concat_Iter : public IC {
      public:
	bool in_first;
	IC*  i1;
	IC*  i2;

	virtual bool get(char& c) {
	    if (in_first) {
		if (i1->get(c)) return TRUE;
		in_first = FALSE;
	    }
	    return i2->get(c);
	}
    };

    // See if we are iterating over just one of the two parts
    if (s + l <= size1)	return rep1->chars(s, l);
    if (s >= size1)	return rep2->chars(s - size1, l);

    Concat_Iter* rep = new Concat_Iter;
    rep->in_first = TRUE;
    rep->i1 = rep1->chars(s, size1 - s);
    rep->i2 = rep2->chars(0, l - (size1 - s));
    return rep;
}

substring_rep::substring_rep(string_rep* r, int s, int l) {
    start = s;
    len = l;
    rep = r;
}

int substring_rep::length() {
    return len;
}

char substring_rep::fetch(int index) {
    return rep->fetch(index + start);
}

IC* substring_rep::chars(int s, int l) {
    return rep->chars(s + start, l);
}

// *** String operations ***

static empty_rep eRep;

string::string() {
    rep = &eRep;
}

string::string(char c) {
    char* s = new(atomic) char[1];
    s[0] = c;
    rep = new flat_rep(s, 1);
}

string::string(char const* s) {
    int l = strlen(s);
    if (l != 0) {
	char* d = new(atomic) char[l];
	memcpy(d, s, l);
	rep = new flat_rep(d, l);
    } else {
	rep = &eRep;
    }
}

string::string(char const* s, int l) {
    char* d = new(atomic) char[l];
    memcpy(d, s, l);
    rep = new flat_rep(d, l);
}

string::string(string const &s) {
    rep = s.rep;
}

string::string(IC* iter) {
    // Store stuff in an extensible array while reading from the iterator
    AC list;
    char c;
    while (iter->get(c))
	list.append(c);

    // Now allocate a flat rep
    int num = list.length();
    char* buf = new(atomic) char[num];
    for (int i = 0; i < num; i++)
	buf[i] = list[i];

    rep = new flat_rep(buf, num);
}

string::string(IC* iter, int num) {
    char* str = new(atomic) char[num];
    for (int i = 0; i < num; i++)
	iter->get(str[i]);
    rep = new flat_rep(str, num);
}

string::string(string_rep* r) {
    rep = r;
}

string const &string::operator = (string const &s) {
    rep = s.rep;
    return *this;
}

int string::length() const {
    return rep->length();
}

char string::operator[](int index) const {
    assert(index >= 0);
    assert(index < length());

    return rep->fetch(index);
}

string string::append(char c) const {
    char *s = new(atomic) char[1];
    s[0] = c;
    flat_rep *f1 = new flat_rep(s, 1);
    return string(new concat_rep(rep, f1));
}

int string::hash() const {
    flat_rep* f = rep->flat();
    ((string*) this)->rep = f;
    return f->hash();
}

int string::compare(string s2) const {
    string const &s1 = *this;
    int l1 = s1.length();
    int l2 = s2.length();
    int min_length = (l1 < l2) ? l1 : l2;

    IC* i1 = s1.chars();
    IC* i2 = s2.chars();
    for (int i = 0; i < min_length; i++) {
	char c1, c2;
	i1->get(c1);
	i2->get(c2);

	if (c1 != c2) return (c1 - c2);
    }

    return l1 - l2;
}

bool string::operator == (string s) const {
    // Fast check
    if (rep == s.rep) return TRUE;
    if (length() != s.length()) return FALSE;
    return (compare(s) == 0);
}

bool string::operator != (string s) const {
    // Fast check
    if (length() != s.length()) return TRUE;

    return (compare(s) != 0);
}

bool string::operator <  (string s) const {return (compare(s) <  0);}
bool string::operator <= (string s) const {return (compare(s) <= 0);}
bool string::operator >  (string s) const {return (compare(s) >  0);}
bool string::operator >= (string s) const {return (compare(s) >= 0);}

string string::operator + (string s) const {
    return string(new concat_rep(rep, s.rep));
}

string string::substring(int start, int len) const {
    int my_length = length();

    check(start >= 0, "invalid start in string::substring");
    check(start + len <= my_length &&
          len >= 0, "invalid length in string::substring");

    if (len > 0)
	return string(new substring_rep(rep, start, len));
    else
	return string();
}

bool string::find_char(char c, int start, int& index) const {
    int len = length();
    if (start >= len) return false;
    if (start < 0) start = 0;
    IC* iter = rep->chars(start, len);
    char x;
    while (iter->get(x)) {
	if (c == x) {
	    index = start;
	    return true;
	}
	start++;
    }
    return false;
}


IC* string::chars() const {
    return rep->chars(0, length());
}

IC* string::range(int start, int len) const {
    int my_length = length();

    // Range checking
    if (start < 0) start = 0;
    if ((start + len) > my_length) len = my_length - start;
    if (len < 0) len = 0;

    return rep->chars(start, len);
}

char const *string::get_chars() const {
   flat_rep *f = rep->flat();
   ((string*) this)->rep = f;
   int len = length();
   char *ret = new(atomic) char[len + 1];
   memcpy(ret, f->ptr(), len);
   ret[len] = 0;
   return ret;
}

ostream& operator << (ostream& o, string s) {
    char c;
    IC* iter = s.chars();
    while (iter->get(c))
	o << c;

    return o;
}

istream& operator >> (istream& in, string& s) {
    // Generator that yields non-white-space chars from the istream
    class NonWhite_Iter : public IC {
      public:
	istream& in;
	
	NonWhite_Iter(istream& i) : in(i) {}

	virtual bool get(char& c) {
	    int c1 = in.peek();
	    if (c1 == EOF || isspace(char(c1))) return FALSE;
	    c = in.get();
	    return TRUE;
	}
    };

    // Skip initial white space
    in >> ws;

    // Create an iterator and make a string out of it
    s = string(new NonWhite_Iter(in));

    return in;
}

class ReadUntil_Iter : public IC {
  public:
    istream& in;
    char term;
    
    ReadUntil_Iter(istream& i, char t) : in(i), term(t) {}

    virtual bool get(char& c) {
	char tmp;
	if (in.get(tmp) && (tmp != term)) {
	    c = tmp;
	    return TRUE;
	}
	return FALSE;
    }
};

bool read_string(istream& in, string& s, char term) {
    // Generator that yields characters until term
    // Check for end of file
    if (in.peek() == EOF) return FALSE;

    // Create an iterator and make a string out of it
    s = string(new ReadUntil_Iter(in, term));
    return TRUE;
}

bool read_line(istream& in, string& s) {
    return read_string(in, s, '\n');
}

#ifndef NDEBUG
void sdump(string s) {
    cerr << s << '\n';
}
#endif
