/* 
 * The contents of this file are subject to the Mozilla Public
 * License Version 1.1 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.mozilla.org/MPL/
 * 
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 * 
 * The Original Code is the Sablotron XSLT Processor.
 * 
 * The Initial Developer of the Original Code is Ginger Alliance Ltd.
 * Portions created by Ginger Alliance are Copyright (C) 2000 Ginger
 * Alliance Ltd. All Rights Reserved.
 * 
 * Contributor(s):
 * 
 * Alternatively, the contents of this file may be used under the
 * terms of the GNU General Public License Version 2 or later (the
 * "GPL"), in which case the provisions of the GPL are applicable 
 * instead of those above.  If you wish to allow use of your 
 * version of this file only under the terms of the GPL and not to
 * allow others to use your version of this file under the MPL,
 * indicate your decision by deleting the provisions above and
 * replace them with the notice and other provisions required by
 * the GPL.  If you do not delete the provisions above, a recipient
 * may use your version of this file under either the MPL or the
 * GPL.
 */

#include "numbering.h"
#include "verts.h"
#include "expr.h"
#include "context.h"

Bool similarVerts(Vertex *v, Vertex *w)
{
    assert(v && w);
    VTYPE typeV = (VTYPE) basetype(v);
    // vertices of different types don't match
    if (typeV != basetype(w))
	return FALSE;
    switch(typeV)
    {
    case VT_ROOT:
    case VT_TEXT:
    case VT_COMMENT:
	// no expanded name, they match
	return TRUE;
    case VT_ELEMENT:
	return toE(v) -> getName() == toE(w) -> getName();
    case VT_NAMESPACE:
	return toNS(v) -> getName() == toNS(w) -> getName();
    case VT_ATTRIBUTE:
	return toA(v) -> getName() == toA(w) -> getName();
    case VT_PI:
	return toPI(v) -> getName() == toPI(w) -> getName();
	default:
	// do this to keep compiler happy
	return FALSE;
    }
}

Vertex *gotoPreceding(Vertex *v, Bool siblingOnly)
{
    assert(v);
    switch(basetype(v))
    {
    case VT_ROOT:
    case VT_ATTRIBUTE:
    case VT_NAMESPACE:
	// no preceding nodes according to XPath spec
	return NULL;
    default:
    {
      Vertex *par = v -> parent;

      if (siblingOnly) 
	{
	  if (v -> ordinal)
	    return toD(par) -> contents[v -> ordinal - 1];
	  else
	    return NULL;
	}
      else //preceding and ancestor-or-self union
	{
	  //if we have preceding sibling we switch to it and dril
	  if (v -> ordinal) 
	    {
	      for (v = toD(par) -> contents[v -> ordinal - 1];
		   v && isDaddy(v) && toD(v) -> contents.number(); 
		   v = toD(v) -> contents.last());
	      return v;
	    }
	  else //go to parent
	    {
	      return isRoot(par) ? NULL : par;
	    }
	}

      /*
	Vertex *par = v -> parent;
	if (v -> ordinal)
	    return toD(par) -> contents[v -> ordinal - 1];
	else if (siblingOnly)
	    return NULL;

	//we're in mode, when number of all preceding nodes
	//is asked (including ancestors)
	if ( !isRoot(par) )
	  return par;
	else
	  return NULL;
      */

	/*
	// find ancestor of v which has a left sibling

	while (par && !toD(par) -> ordinal)
	    par = par -> parent;
	if (!par)
	    return NULL;
	assert(par -> parent); // root must have ordinal == 0
	// let v be the node to the left of par
	v = toD(par -> parent) -> contents[toD(par) -> ordinal - 1];
	// dive down the tree to find the last node under v
	for (v = par; v && isDaddy(v) && toD(v) -> contents.number(); 
	     v = toD(v) -> contents.last());
	return v;
	*/
    }}
}

eFlag countMatchingSiblings(Sit S, int& num, Vertex *v, Expression *count)
{
    num = 0;
    Vertex *w;
    Bool doesMatch;
    Context c(NULL); //_cn_ we're matching a pattern
    for (w = v; w; w = gotoPreceding(w, /* siblingOnly = */ TRUE))
    {
	if (count)
	{
	    c.deppendall();
	    c.set(w);
	    E( count -> matchesPattern(S, &c, doesMatch) );
	}
	else
	    doesMatch = similarVerts(v, w);
	if (doesMatch)
	    num++;
    }
    return OK;
}

eFlag xslNumberCount(
    Sit S, NumberingLevel level, 
    Expression* count, Expression* from, 
    Vertex* curr, ListInt& result)
{
    result.deppendall();
    int num;
    Vertex *w = NULL;
    List<Vertex*> matchingList;
    Bool doesMatch;
    Context c(NULL); //_cn_ we're matching a pattern
    // construct the list of matching ancestors/preceding nodes
    for (w = curr; w; )
    {
	c.deppendall();
	c.set(w);
	if  (from)
	{
	    E( from -> matchesPattern(S, &c, doesMatch) );
	    if (doesMatch) break; // leave the for loop
	}
	if (count)
	    E( count -> matchesPattern(S, &c, doesMatch) )
	else
	    doesMatch = similarVerts(curr, w);
	if (doesMatch)
	{
	    matchingList.append(w);
	    if (level == NUM_SINGLE) break; // leave the for loop after finding a match
	}
	if (level == NUM_ANY)
	    w = gotoPreceding(w, /* siblingOnly = */ FALSE);
	else
	    w = w -> parent;
    }
    // construct the integer list out of matchingList
    if (level == NUM_ANY)
	result.append(matchingList.number());
    else
	for (int i = matchingList.number() - 1; i >= 0; i--)
	{
	    E( countMatchingSiblings(S, num, matchingList[i], count) );
	    result.append(num);
	}
    return OK;
}

Bool isAlnumFToken(const Str& s)
{
    unsigned long c = utf8CharCode((const char*)s);
    return utf8IsDigit(c) || utf8IsLetter(c);
}

Bool getFToken(const char *&p, Str& fmt)
{
    if (!*p)
	return FALSE;
    const char* pOrig = p;
    Bool alnum = isAlnumFToken(p);
    do
	p += utf8SingleCharLength(p);
    while(*p && isAlnumFToken(p) == alnum);
    fmt.nset(pOrig, (int)(p - pOrig));
    return TRUE;
}

void getFTokenParams(const Str& fmt, char& type, int& width)
{
    // defaults:
    type = '1';
    width = 1;
    int len = utf8StrLength(fmt);
    assert(len);
    if (len > 1 && fmt[0] != '0') return; // with default values
    switch(fmt[0])
    {
    case 'A':
    case 'a':
    case 'I':
    case 'i':
    {
	type = fmt[0];
	return;
    }
    case '0':
	break;
    default:
	return;
    }
    // it remains to take care of the '0':
    for (int i = 1; i < len - 1; i++)
	if (fmt[i] != '0') return;
    if (fmt[len - 1] != '1') return;
    width = len;
}

void appendABC(int num, Bool uppercase, DStr& result)
{
    DStr reversed;
    do
    {
	num--;
	reversed += (char)((uppercase ? 'A' : 'a') + num % 26);
	num /= 26;
    }
    while (num > 0);
    for (int i = reversed.length() - 1; i >= 0; i--)
	result += reversed[i];
}

struct RomanDef
{
    int value;
    char symbol[3];
};

RomanDef romans[] =
{
    { 1000, "mM"},
    { 500, "dD"},
    { 100, "cC"},
    { 50, "lL"},
    { 10, "xX"},
    { 5, "vV"},
    { 1, "iI"},
    { 0, "oO"}
};

void appendRoman(int num, Bool uppercase, DStr& result)
{
    int step = 0,
	prefix,
	val;
    if (uppercase != 0)
	uppercase = 1;
    while (num > 0)
    {
	if (num >= (val = romans[step].value))
	{
	    result += romans[step].symbol[uppercase];
	    num -= val;
	}
	else 
	{
	    prefix = step + 2 - step % 2;
	    if (val > 1 && num >= (val - romans[prefix].value))
	    {
		result += romans[prefix].symbol[uppercase];
		result += romans[step].symbol[uppercase];
		num -= (val - romans[prefix].value);
	    }
	    else
		step++;
	}
    }
}

void appendArabic(
    int num, int width, 
    const Str& groupingSep, int groupingSize, DStr& tempResult)
{
    // just put separators and leading zeroes in the number
    DStr sprFmt = DStr("%0") + width + "d";
    char buff[32],
	*p = buff;
    int len = snprintf(buff, 32, (char*)sprFmt, num);
    if (!groupingSize)
	tempResult += buff;
    else
    {
	int first = len % groupingSize;
	if (first)
	{
	    tempResult.nadd(p, first);
	    p += first;
	    len -= first;
	    if (len)
		tempResult += groupingSep;
	}
	for (; len > 0; len -= groupingSize, p += groupingSize)
	{
	    tempResult.nadd(p, groupingSize);
	    if (len > groupingSize)
		tempResult += groupingSep;
	}
    }
}

void formatSingleNumber(
    Sit S, int num, const Str& fmt, 
    const Str& lang, NumberingLetterValue letterValue, 
    const Str& groupingSep, int groupingSize, DStr& tempResult)
{
    // we only add to tempResult, do not initialize it
    char type;
    int width;
    // check value of num
    if (num <= 0)
    {
	S.message(MT_WARN, W_NUMBER_NOT_POSITIVE, (char*)NULL, (char*)NULL);
	num = num ? abs(num) : 1;
    }
    getFTokenParams(fmt, type, width);
    switch(type)
    {
    case 'A':
    case 'a':
	appendABC(num, /* uppercase = */ type == 'A', tempResult);
	break;
    case 'I':
    case 'i':
	appendRoman(num, /* uppercase = */ type == 'I', tempResult);
	break;
    default:
	appendArabic(num, width, groupingSep, groupingSize, tempResult);
    }
}

eFlag xslNumberFormat(
    Sit S, ListInt& nums, const Str& format, 
    const Str& lang, NumberingLetterValue letterValue, 
    const Str& groupingSep, int groupingSize, Str& result)
{
    DStr tempResult;
    Str sep = ".", 
	sepRightmost, 
	alpha = "1",
	firstToken;

    const char *p = (const char*) format;
    int ndx = 0;

    if (getFToken(p, firstToken))
    {
	if (isAlnumFToken(firstToken) && nums.number())
	{
	    alpha = firstToken;
	    formatSingleNumber(
		S, nums[0], alpha, lang, letterValue,
		groupingSep, groupingSize, tempResult);
	    ndx = 1;
	}
	else
	{
	    // reset p to the beginning
	    p = (const char*) format;
	    if (!nums.number())
		tempResult += sepRightmost = firstToken;;
	}
    }
    // p points at the first separator (if any)
    Bool readAllFormat = *p ? FALSE : TRUE;
    for (; ndx < nums.number(); ndx++)
    {
	if (!readAllFormat)
	{
	    // always update the rightmost separator
	    if (getFToken(p, sepRightmost))
	    {
		if (getFToken(p, alpha))
		{
		    // alpha token found
		    // use the current separator and reset the rightmost one
		    sep = sepRightmost;
		    sepRightmost.empty();
		}
		else
		{
		    // no alpha token, use the same separator as last time
		    // keep current separator in sepRightmost
		    readAllFormat = TRUE;
		    if (!ndx)
			sep = sepRightmost;
		}
		
	    }
	    else
		// both empty, use the same separator and alpha as last time
		readAllFormat = TRUE;
	} // if (!readAllFormat)
	// sep and alpha are the last valid tokens of each kind
	tempResult += sep;
	formatSingleNumber(
	    S, nums[ndx], alpha, lang, letterValue,
	    groupingSep, groupingSize, tempResult);
    } // for loop
    // get the real rightmost separator
    if (!readAllFormat)
    {
	while(getFToken(p, sepRightmost));
	if (isAlnumFToken(sepRightmost))
	    sepRightmost.empty();
    }
    tempResult += sepRightmost;
    result = tempResult;
    return OK;
}


