/*
 * Copyright (C) 1992 by Software Research Associates, Inc.
 *      Author: Y. Kawabe <kawabe@sra.co.jp>
 *
 * Permission to use, copy, modify, and distribute, and sell this software
 * and its documentation for any purpose and without fee is hereby granted,
 * provided that the above copyright notice appear in all copies and that
 * both that copyright notice and this permission notice appear in supporting
 * documentation, and that the name of Software Research Associates not be
 * used in advertising or publicity pertaining to distribution of the software
 * without specific, written prior permission.  Software Research Associates
 * makes no representations about the suitability of this software for any
 * purpose.  It is provided "as is" without express or implied warranty.
 *
 */

#include <NLS/wchar.h>
#include <NLS/charset.h>
#include <NLS/wstring.h>
#include <NLS/wcharbuf.h>
#include <NLS/wregexp.h>
#include <NLS/nlsfile.h>
#include <NLS/locale.h>
#include <hyperg/OS/list.h>
#include <ctype.h>
#include <stdio.h>

static const char NEWLINE = '\n';

static boolean is_space (const WChar& ch) {
   return ((ch.value() & 0xff) == ch.value()) && isspace(ch.value());
}

/*
 * class WCharList
 */

declareList(WCharList, WChar);
implementList(WCharList, WChar);

/*
 * class WCharBuffer
 */

WCharBuffer::WCharBuffer (int size) {
    prefix_ = new WCharList(size);
    suffix_ = new WCharList();
    work_   = new WCharList();
    word_ = WRegexp::Word();
    Clear();
}

WCharBuffer::~WCharBuffer () {
    delete prefix_;
    delete suffix_;
    delete work_;
    delete word_;
}

int WCharBuffer::Dot(int i) {
    if (i < 0) {
	dot_ = 0;
    } else if (i > Length()) {
        dot_ =  Length();
    } else {
	dot_ = i;
    }
    return dot_;
}

int WCharBuffer::PreviousCharacter (int i) {
    int mark = dot_;

    dot_ -= i;
    if (i > 0 && dot_ < 0) {
	dot_ = 0;
    } else if (i < 0 && dot_ > Length()) {
	dot_ = Length();
    }
    return Dot();
}

int WCharBuffer::NextCharacter (int i) {
    int mark = dot_;

    dot_ += i;
    if (i < 0 && dot_ < 0) {
	dot_ = 0;
    } else if (i > 0 && dot_ > Length()) {
	dot_ = Length();
    }
    return Dot();
}

int WCharBuffer::PreviousLine (int i) {
    int offset = LineOffset ();
    while (i-- > 0) {
	EndOfPreviousLine ();
    }
    int end = dot_;
    int start = BeginningOfLine();

    if (end - start > offset) {
	return NextCharacter (offset);
     } else {
	return Dot (end);
     }
}

int WCharBuffer::NextLine (int i) {
    int offset = LineOffset ();
    while (i-- > 0) {
	BeginningOfNextLine ();
    }
    int start = dot_;
    int end = EndOfLine();

    if (end - start > offset) {
        Dot (start);
	return NextCharacter (offset);
     } else {
	return Dot (end);
     }
}

int WCharBuffer::BeginningOfLine () {
    while (dot_ > 0 && Item(dot_ - 1) != NEWLINE) {
        --dot_;
    }
    return Dot();
}

int WCharBuffer::EndOfLine () {
    while (dot_ < Length() && Item(dot_) != NEWLINE) {
        ++dot_;
    }
    return Dot();
}

int WCharBuffer::BeginningOfPreviousLine () {
    EndOfPreviousLine ();
    return BeginningOfLine ();
}

int WCharBuffer::EndOfPreviousLine () {
    while (dot_ > 0 && Item(dot_ - 1) != NEWLINE) {
        --dot_;
    }
    if (dot_ > 0) dot_ --;
    return Dot();
}

int WCharBuffer::BeginningOfNextLine () {
    while (dot_ < Length() && Item(dot_) != NEWLINE) {
        ++dot_;
    }
    if (dot_ < Length()) dot_ ++;
    return Dot();
}

int WCharBuffer::EndOfNextLine () {
    BeginningOfNextLine ();
    return EndOfLine ();
}

int WCharBuffer::BeginningOfBuffer () {
    return Dot(0);
}

int WCharBuffer::EndOfBuffer () {
    return Dot(Length());
}

/*
 * Word Function
 */

void WCharBuffer::DefineWord (const WString& word) {
    delete word_;
    word_ = new WRegexp(word);
}

int WCharBuffer::BeginningOfWord () {
    while (dot_ > 0 && is_space(Item())) dot_--;

    int end = dot_;
    int beg = BeginningOfLine();
    CopyWString str(Text (beg, end - beg));
    str.reverse();
    int r = word_->Match(str, 0);
    if (r >= 0) {
        return Dot(end - word_->EndOfMatch());
    }
    return Dot(end); 
}

int WCharBuffer::EndOfWord () {
    while (dot_ > 0 && is_space(Item())) dot_--;

    int beg = dot_;
    int end = EndOfLine();
    const WString &str = Text (beg, end - beg);
    int r = word_->Match(str, 0);

    if (r >= 0) {
        return Dot(beg + word_->EndOfMatch());
    }
    return Dot(beg); 
}

int WCharBuffer::BeginningOfPreviousWord () {
    BeginningOfWord ();
    BeginningOfWord ();
    return Dot();
}

int WCharBuffer::EndOfPreviousWord () {
    BeginningOfWord ();
    while (dot_ > 0 && is_space(Item())) dot_--;
    return Dot();
}

int WCharBuffer::BeginningOfNextWord () {
    EndOfWord ();
    while (dot_ < Length() && is_space(Item())) dot_++;
    return Dot();
}

int WCharBuffer::EndOfNextWord () {
    EndOfWord ();
    EndOfWord ();
    return Dot();
}

/*
 * Editing Function
 */

void WCharBuffer::Insert (const WChar& ch) {
    FixGap();
    prefix_->append((WChar&) ch);
    dot_++, length_++;
}

void WCharBuffer::Insert (const WString& s) {
    FixGap();
    int mark = dot_;
    int len = s.length();
    for (int i = 0; i < len; i++) {
        prefix_->append((WChar&) s[i]);
    }
    length_ += len, dot_ += len;
    lastnum_ += LinesBetween(lastdot_, dot_); lastdot_ = dot_;
}

void WCharBuffer::Clear () {
    prefix_->remove_all();
    suffix_->remove_all();
    length_ = dot_ = lastdot_ = lastnum_ = 0;
}

void WCharBuffer::Delete (int count) {
    if (count > 0) {
	if (dot_ + count >= Length()) {
	    if (dot_ == 0) {
	        /* clear All buffer */
		Clear();
	    } else {
	        /* kill to end of buffer */
	        FixGap();
        	length_ -= int (suffix_->count());
		suffix_->remove_all();
	    }
	} else {
    	    Dot(dot_ + count); FixGap();
	    for (int i = count; i > 0; i--) {
		prefix_->remove(dot_ - 1);
		--dot_;
	    }
            length_ -= count;
	}
    } else if (count < 0) {
	if (dot_ + count <= 0) {
	    if (dot_ == Length()) {
	        /* clear all buffer */
		Clear();
	    } else {
	        /* kill to beginning of buffer */
	        FixGap();
        	length_ -= int (prefix_->count());
    		dot_ = lastdot_ = lastnum_ = 0;
		prefix_->remove_all();
	    }
	} else {
	    FixGap();
	    int num = LinesBetween(lastdot_, dot_ + count);
	    for (int i = count; i < 0; i++) {
		prefix_->remove(dot_ - 1);
		dot_--;
	    }
            length_ += count;
	    lastnum_ += num ; lastdot_ = dot_;
	}
    }
}

static void swap(WCharList *&list1, WCharList* &list2) {
     WCharList *tmp = list2;
     list2 = list1;
     list1 = tmp;
}
 
void WCharBuffer::FixGap () {
    int	gap = int (prefix_->count());
    if (gap == dot_) {
	return;
    }
    if (gap == 0 && dot_ == length_) {
	swap(suffix_, prefix_);
    } else if (gap == length_ && dot_ == 0) {
	swap(suffix_, prefix_);
    } else if (gap < dot_) {
	ListItr(WCharList) i(*suffix_);
	for (; gap < dot_ && i.more(); i.next()) {
	    prefix_->append(i.cur());
	    gap++;
	}
	while (i.more()) {
	    work_->append(i.cur());
	    i.next();
	}
	swap(suffix_, work_);
	work_->remove_all();
    } else {
        int j;
	for (j = dot_; j < gap; j++) {
	    work_->append(prefix_->item(j));
	}
	for (ListItr(WCharList) i(*suffix_); i.more(); i.next()) {
	    work_->append(i.cur());
	}
	swap(suffix_, work_);
	work_->remove_all();
        for (j = gap - dot_; j > 0 ;j --) {
  	    prefix_->remove(gap - 1);
	    --gap;
        }
    }
}

/*
 * Read Function
 */

WChar WCharBuffer::Item () {
    return Item (dot_);
}

WChar WCharBuffer::Item (int i) {

    if (i >= Length()) {
	return WChar(0);
    }
    int	gap = int (prefix_->count());
    if (i < gap) {
	return prefix_->item(i);
    } else {
	return suffix_->item(i - gap);
    }
}

const WString WCharBuffer::Text() {
    return Text(0, -1);
}

const WString WCharBuffer::Text(int dot) {
    return Text(dot, -1);
}

const WString WCharBuffer::Text (int dot, int length) {
    Dot (dot);
    if (length < 0 || length + dot_ > Length()) {
	length = Length() - dot_;
    }
    int	gap = int (prefix_->count());
    if (length == 0) {
	return WString();
    } else if (dot_ >= gap) {
        const WChar *w = suffix_->array(dot_ - gap, length);
	return WString(w, length);
    } else if (dot_ + length < gap) {
	const WChar *w = prefix_->array(dot_, length);
	return WString(w, length);
    } else {
	FixGap();
	const WChar* w = suffix_->array(0, length);
	return WString(w, length);
    }
}

/*
 * Search Function
 */

int WCharBuffer::SearchForward(WRegexp& regexp) {
    int mark = dot_;
    const WString text = Text();
    int p = regexp.Search(text, mark, Length() - mark);
    if (p >= 0) {
	dot_ = p;	/* pattern found , then move */
	return Dot();
    } else {
	dot_ = mark;
	return -1;
    }
};

int WCharBuffer::SearchBackward(WRegexp& regexp) {
    int mark = dot_;
    const WString text = Text();
    int p = regexp.Search(text, mark, -mark);
    if (p >= 0) {
	dot_ = p;	/* pattern found , then move */
	return Dot();
    } else {
	dot_ = mark;
	return -1;
    }
}

/*
 * Line Number Function
 */

int WCharBuffer::LineNumber() {
    lastnum_ += LinesBetween(lastdot_, dot_); lastdot_ = dot_;
    return lastnum_;
}

int WCharBuffer::LineOffset() {
    int mark = dot_;
    BeginningOfLine ();
    int offset = mark - dot_;
    Dot (mark);
    return offset;
}

int WCharBuffer::LinesBetween (int index1, int index2) {
    if (index1 == index2) {
        return 0;
    }

    if (index1 > index2) {
        return -LinesBetween(index2, index1);
    }

    int lines = 0;
    for (int i = index1; i < index2; i++) {
	if (Item (i) == NEWLINE)
	    lines++;
    }
    return lines;
}

int WCharBuffer::GotoBeginningOfLine(int line) {
    if (line == lastnum_) {
	Dot(lastdot_);
	return BeginningOfLine();
    } else if (line > lastnum_) {
	Dot(lastdot_);
	for (line -= lastnum_; line > 0; line--) {
       	    BeginningOfNextLine();
        }
	lastnum_ += LinesBetween(lastdot_, dot_); lastdot_ = dot_;
        return dot_;
    } else if (line * 2 > lastnum_) {
	Dot(lastdot_);
	for (line -= lastnum_; line < 0; line++) {
       	    BeginningOfPreviousLine();
        }
	lastnum_ += LinesBetween(lastdot_, dot_); lastdot_ = dot_;
	return dot_;
    } else {
	BeginningOfBuffer ();
	while (line-- > 0) {
	    BeginningOfNextLine();
        }
	lastnum_ = LinesBetween(0, dot_); lastdot_ = dot_;
        return dot_;
    }
}

int WCharBuffer::GotoEndOfLine(int i) {
    BeginningOfBuffer ();
    while (--i > 0) {
       BeginningOfNextLine();
    }
    return EndOfLine() ;
}

