#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "rkconv.h"

int malloc_count = 0;

Rule::Rule()
{
    lhs = NULL;
    rhs = NULL;
    follow = NULL;
}

Rule::~Rule()
{
    if (lhs){
	delete[] lhs;
    }
    if (rhs){
	delete[] rhs;
    }
    if (follow){
	delete[] follow;
    }
}

Rule &Rule::operator=(RkRule &r)
{
    if (r.lhs){
	lhs = new char[strlen(r.lhs)+1];
	strcpy (lhs, r.lhs);
    } else {
	lhs = NULL;
    }
    if (r.rhs){
	rhs = new char[strlen(r.rhs)+1];
	strcpy (rhs, r.rhs);
    } else {
	rhs = NULL;
    }
    if (r.follow){
	follow = new char[strlen(r.follow)+1];
	strcpy (follow, r.follow);
    } else {
	follow = NULL;
    }
  
    return *this;
}

RuleSet::RuleSet(RkRule *rule)
{
    int i;

    for (nr_rules = 0; rule[nr_rules].lhs; nr_rules++)
	;
    this->rule = new Rule [nr_rules];
    for (i = 0; i < nr_rules; i++){
	this->rule[i] = rule[i];
    }
}

RuleSet::~RuleSet()
{
    delete[] rule;
}

RKMap::RKMap(RkRule *rule)
{
    rs = new RuleSet(rule);
    cl = new SlrClosure(rs, rs->get_nr_rules());
    refcount = 0;
}

RKMap::~RKMap()
{
    delete rs;
}

void RKMap::print(FILE *fp,char *prefix)
{
    cl->print(fp, prefix);
}

void RKMap::convert_batch (char* src, char* dst)
{
    cl->convert_batch(src, dst);
}

int RKMap::get_refcount()
{
    return refcount;
}

int RKMap::inc_refcount()
{
    return ++refcount;
}

int RKMap::dec_refcount()
{
    return --refcount;
}

SlrClosure* RKMap::get_init(void){
    return cl;
}

SlrClosure::SlrClosure (RuleSet* rule, int nr_rule,
			Rule* cur_rule,size_t pflen)
{
    int i;
    char c;
    Rule* r;

    malloc_count++;

    // prefix
    if (cur_rule){
	prefix = new char[pflen+1];
	strncpy(prefix, cur_rule->lhs, pflen);
	prefix[pflen] = 0;
    } else {
	prefix = "";
    }
    // r
    this->r = (Rule*)NULL;
    // is_reduction_only
    is_reduction_only = true;
    // next
    memset (next, 0, sizeof next);
	
    /* pflen == strlen(prefix) を保証しておくこと */
    for (i = 0; i < nr_rule; i++){
	r = rule->get_rule()+i;
	if (strncmp(prefix, r->lhs, pflen))
	    continue;

	if (strlen(r->lhs) == pflen){  /* 還元 */
	    this->r = r;
	    if (this->r->follow){
		is_reduction_only = false;
	    }
	} else {                       /* 継続 */
	    is_reduction_only = false;
	    c = r->lhs[pflen];
	    if (!next[c]){
		next[c] = new SlrClosure (rule, nr_rule, r, pflen+1);
	    }
	}
    }
}

SlrClosure::~SlrClosure(void)
{
    int i;
    delete[] prefix;
    for (i = 0; i < 128; i++){
	if (next[i])
	    delete next[i];
    }
}

#define PREFIX	fprintf (fp, "%s", prefix);
void SlrClosure::print(FILE* fp, char* prefix)
{
    int i;
    char buf[1000];

    PREFIX fprintf (fp, "[%p] prefix:%s\n", this, prefix);
    if (r){
	PREFIX fprintf (fp, "            reduction: %s->%s\n",
			r->lhs, r->rhs);
    } else {
	PREFIX fprintf (fp, "            reduction: (none)\n");
    }
	
    PREFIX fprintf (fp, "            reduction_only: %d\n",
		    is_reduction_only);
    for (i = 0; i < 128; i++){
	if (next[i]){
	    sprintf (buf, "%s(%c)", prefix, i);
	    next[i]->print(fp, buf);
	}
    }
}

char *SlrClosure::convert_reduction(char* dst, Rule* r)
{
    strcpy(dst, r->rhs);
    dst += strlen(dst);
    return dst;
}

void SlrClosure::convert_batch(char* src, char* dst)
{
    SlrClosure* cur = this;
    char* p = src;
    char* q = dst;

    do {

    retry:
	if (cur->next[*p]) {
	    cur = cur->next[*p];
	    if (cur->is_reduction_only){
		q = convert_reduction(q, cur->r);
		cur = this;
	    }
	} else if (cur->r &&
		   (!cur->r->follow || strchr(cur->r->follow, *p))) {
	    q = convert_reduction(q, cur->r);
	    cur = this;
	    goto retry;
	} else if (cur != this){
	    cur = this;
	    goto retry;
	} else {
	}
    } while (*(++p));

    if (cur->r && !cur->r->follow){
	convert_reduction(q, cur->r);
    }
}

SlrClosure* SlrClosure::convert_iterative(char c, char* dst, SlrClosure* cl)
{
    *dst = '\0';

    if (next[c]){
	if (next[c]->is_reduction_only){
	    dst = convert_reduction(dst, next[c]->r);
	    return cl;
	}
	return next[c];
    } else if (r && (!r->follow || strchr(r->follow, c))){
	dst = convert_reduction(dst, r);
	return cl->convert_iterative(c, dst, cl); // retry
    } else if (this != cl){
	return cl->convert_iterative(c, dst, cl); // retry
    }
    return cl;
}

/// class RKConv

RKConv::RKConv()
{
    map = NULL;
    cur = NULL;
	
    flush();
}

bool RKConv::push_key(int cin)
{
    char dst[10];
    char c = cin & 0x7F;

    if (!cur)
	return false;
    
    cur = map->convert_iterative(c, dst, cur);
    if ( !euc_to_cchar(dst) && !is_pending()){
	return false;
    }
    return true;
}

void RKConv::flush()
{
    if (map)
	cur = map->get_init();
    outQ.erase(outQ.begin(), outQ.end());
}

cchar RKConv::get_cchar()
{
    cchar ret = 0;
    std::list<cchar>::iterator i = outQ.begin();
    if (i != outQ.end()){
	ret = *i;
	outQ.pop_front();
    }
    return ret;
}

RKMap* RKConv::select_map(RKMap* m)
{
    RKMap* oldmap = map;
    const char* p = "";
  
    if (cur)
	p = cur->get_prefix();
    map = m;
    cur = m->get_init();

    while (*p)
	push_key (*(p++));

    map->inc_refcount();
    if (oldmap)
	oldmap->dec_refcount();

    return oldmap;
}

void RKConv::print_map(FILE* fp, char* prefix)
{
    map->print(fp, prefix);
}

char* RKConv::get_pending_char(char* buf, size_t size)
{
    if (!cur)
	return NULL;
    if (strlen(cur->get_prefix()) >= size)
	return NULL;
    strcpy(buf, cur->get_prefix());
    
    return buf;
}

bool RKConv::is_pending()
{
    if (!cur || strlen(cur->get_prefix()) == 0){
	return false;
    }
    return true;
}

bool RKConv::back_space()
{
    char buf[10];
    if ( get_pending_char(buf,10)){
	int l = strlen(buf);
	int i;
	flush();
	for ( i = 0 ; i < l-1 ; i++){
	    push_key(buf[i]);
	}
	return l>0;
    }
    return false;
}
// private
bool RKConv::euc_to_cchar(char* q)
{
    cchar c;
    unsigned char* p = (unsigned char*)q;
    bool bConv = false;
    while (*p){
	c = (*(p+1) | (((unsigned int) *p)<<8)) & 0x7F7F;
	outQ.push_back(c);
	p += 2;
	bConv = true;
    }
    return bConv;
}
/*
 * Local variables:
 *  c-indent-level: 4
 *  c-basic-offset: 4
 * End:
 */
