/*
 * 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 Venice Web Community System.
 * 
 * The Initial Developer of the Original Code is Eric J. Bowersox <erbo@silcom.com>,
 * for Silverwrist Design Studios.  Portions created by Eric J. Bowersox are
 * Copyright (C) 2001 Eric J. Bowersox/Silverwrist Design Studios.  All Rights Reserved.
 * 
 * Contributor(s): 
 */
package com.silverwrist.venice.htmlcheck.dict;

import com.silverwrist.venice.htmlcheck.ModSpellingDictionary;

public class TernaryLexicon implements ModSpellingDictionary
{
  /*--------------------------------------------------------------------------------
   * Inner class representing the nodes in the ternary search tree
   *--------------------------------------------------------------------------------
   */

  protected class TLNode
  {
    protected static final int PARENT = 0;
    protected static final int LT_CHILD = 1;
    protected static final int EQ_CHILD = 2;
    protected static final int GT_CHILD = 3;

    protected char splitchar;
    protected TLNode[] relatives = new TLNode[4];
    protected boolean term = false;

    protected TLNode(char splitchar, TLNode parent)
    {
      this.splitchar = splitchar;
      relatives[PARENT] = parent;

    } // end constructor

  } // end class TLNode

  /*--------------------------------------------------------------------------------
   * Attributes
   *--------------------------------------------------------------------------------
   */

  private TLNode root_node;
  private int num_entries;

  /*--------------------------------------------------------------------------------
   * Constructor
   *--------------------------------------------------------------------------------
   */

  public TernaryLexicon()
  {
    root_node = null;
    num_entries = 0;

  } // end constructor

  /*--------------------------------------------------------------------------------
   * Internal functions
   *--------------------------------------------------------------------------------
   */

  private TLNode getNode(String key, TLNode start)
  {
    if ((key==null) || (start==null) || (key.length()==0))
      return null;  // no node can be found

    TLNode current = start;
    int ndx = 0;
    char c = key.charAt(0);
    for (;;)
    { // look for 
      if (current==null)
	break;  // fell off the tree...

      if (c==current.splitchar)
      { // found an equal character - may be at end of string
	if (++ndx==key.length())
	  return current;  // found the node!
	current = current.relatives[TLNode.EQ_CHILD];
	c = key.charAt(ndx);

      } // end if
      else if (c<current.splitchar)
	current = current.relatives[TLNode.LT_CHILD];
      else
	current = current.relatives[TLNode.GT_CHILD];

    } // end for (ever)

    return null;  // not found

  } // end getNode

  private TLNode getOrCreateNode(String key)
  {
    // Do necessary shorts-checking of the input.
    if (key==null)
      throw new NullPointerException("getOrCreateNode(null)");
    if (key.length()==0)
      throw new IllegalArgumentException("getOrCreateNode(\"\")");

    if (root_node==null)  // create the root node
      root_node = new TLNode(key.charAt(0),null);

    TLNode current = root_node;
    int ndx = 0;
    char c = key.charAt(0);
    for (;;)
    { // cycle around adding or traversing nodes as needed
      if (c==current.splitchar)
      { // go to equal branch, add if necessary
	if (++ndx==key.length())
	  return current;  // all done!
	c = key.charAt(ndx);
	if (current.relatives[TLNode.EQ_CHILD]==null)
	  current.relatives[TLNode.EQ_CHILD] = new TLNode(c,current);
	current = current.relatives[TLNode.EQ_CHILD];

      } // end if
      else if (c<current.splitchar)
      { // go to lesser branch, add if necessary
	if (current.relatives[TLNode.LT_CHILD]==null)
	  current.relatives[TLNode.LT_CHILD] = new TLNode(c,current);
	current = current.relatives[TLNode.LT_CHILD];

      } // end else if
      else
      { // go to greater branch, add if necessary
	if (current.relatives[TLNode.GT_CHILD]==null)
	  current.relatives[TLNode.GT_CHILD] = new TLNode(c,current);
	current = current.relatives[TLNode.GT_CHILD];

      } // end else

    } // end for (ever)

  } // end getOrCreateNode

  private TLNode deleteNodeRecursion(TLNode node)
  {
    if (node==null)
      return null;  // no node
    if ((node.relatives[TLNode.EQ_CHILD]!=null) || node.term)
      return null;  // can't delete the node if it has an equal child or is a terminal

    // get our current parent node
    TLNode parent = node.relatives[TLNode.PARENT];

    // are either our low or high children null?
    boolean low_null = (node.relatives[TLNode.LT_CHILD]==null);
    boolean high_null = (node.relatives[TLNode.GT_CHILD]==null);

    // what kind of child are we of our parent?
    int childtype;
    if (parent.relatives[TLNode.LT_CHILD]==node)
      childtype = TLNode.LT_CHILD;
    else if (parent.relatives[TLNode.GT_CHILD]==node)
      childtype = TLNode.GT_CHILD;
    else if (parent.relatives[TLNode.EQ_CHILD]==node)
      childtype = TLNode.EQ_CHILD;
    else
    { // if this executes, then current node is root node
      root_node = null;
      return null;

    } // end else

    if (low_null && high_null)
    { // we have no children, we can just kill ourselves
      parent.relatives[childtype] = null;
      return parent;

    } // end if

    if (low_null)
    { // no low tree, just chain the high tree in in our place
      parent.relatives[childtype] = node.relatives[TLNode.GT_CHILD];
      node.relatives[TLNode.GT_CHILD].relatives[TLNode.PARENT] = parent;
      return parent;

    } // end if

    if (high_null)
    { // no high tree, just chain the low tree in in our place
      parent.relatives[childtype] = node.relatives[TLNode.LT_CHILD];
      node.relatives[TLNode.LT_CHILD].relatives[TLNode.PARENT] = parent;
      return parent;

    } // end if

    // OK, we need to move a node from either the low tree or the high tree into our
    // place...but which one?
    int delta_hi = node.relatives[TLNode.GT_CHILD].splitchar - node.splitchar;
    int delta_lo = node.splitchar - node.relatives[TLNode.LT_CHILD].splitchar;
    int ndx_move;
    TLNode target;

    if (delta_hi==delta_lo)
    { // distances are equal, flip a coin to figure out who gets moved
      if (Math.random()<0.5)
	delta_hi++;
      else
	delta_lo++;

    } // end if

    if (delta_hi>delta_lo)
    { // move the low child into the slot
      ndx_move = TLNode.GT_CHILD;
      target = node.relatives[TLNode.LT_CHILD];

    } // end if
    else
    { // move the high child into the slot
      ndx_move = TLNode.LT_CHILD;
      target = node.relatives[TLNode.GT_CHILD];

    } // end else

    // find the edge of the subtree that doesn't get moved, and put the other subtree there
    while (target.relatives[ndx_move]!=null)
      target = target.relatives[ndx_move];
    target.relatives[ndx_move] = node.relatives[ndx_move];
    node.relatives[ndx_move].relatives[TLNode.PARENT] = target;

    // put the target node where the current one used to be
    parent.relatives[childtype] = target;
    target.relatives[TLNode.PARENT] = parent;

    // clean up the current node
    node.relatives[TLNode.LT_CHILD] = node.relatives[TLNode.GT_CHILD] = null;

    return parent;

  } // end deleteNodeRecursion

  private void deleteNode(TLNode node)
  {
    if (node==null)
      return;
    node.term = false;
    while (node!=null)
      node = deleteNodeRecursion(node);

  } // end deleteNode

  private boolean getEntry(String word)
  {
    TLNode node = getNode(word,root_node);
    if (node!=null)
      return node.term;
    else
      return false;

  } // end getEntry

  private boolean checkHyphenates(String word)
  {
    if (word.indexOf('-')<0)
      return false;  // non-hyphenated

    int st = 0;
    int p;
    boolean ok = true;
    do
    { // break the word up into hyphenated compounds
      p = word.indexOf('-',st);
      String frag;
      if (p>=0)
      { // get the middle fragment of the word
	frag = word.substring(st,p);
	st = p + 1;

      } // end if
      else  // get it from the end
	frag = word.substring(st);

      // check this fragment...
      if (frag.length()<=1)
	ok = true;   // fragments of length 0 or 1 are always OK
      else  // anything else goes through getEntry
	ok = getEntry(frag);

    } while (ok && (p>=0));

    return ok;

  } // end checkHyphenates

  /*--------------------------------------------------------------------------------
   * Implementations from interface SpellingDictionary
   *--------------------------------------------------------------------------------
   */

  public synchronized int size()
  {
    return num_entries;

  } // end size

  public synchronized boolean checkWord(String word)
  {
    if (word.length()<=1)
      return true;  // words of length 1 get a free pass
    String real_word = word.toLowerCase();
    if (getEntry(real_word))
      return true;  // word is in lexicon - we're OK to go

    if ((real_word.indexOf('\'')==(real_word.length()-2)) && (real_word.charAt(real_word.length()-1)=='s'))
    { // drop the apostrophe-s from the end of the word and retry
      String base = real_word.substring(0,real_word.length()-2);
      if (getEntry(base))
	return true;
      return checkHyphenates(base);

    } // end if
    else // try hyphenated forms
      return checkHyphenates(real_word);

  } // end checkWord

  /*--------------------------------------------------------------------------------
   * Implementations from interface ModSpellingDictionary
   *--------------------------------------------------------------------------------
   */

  public synchronized void addWord(String word)
  {
    TLNode node = getOrCreateNode(word.toLowerCase());
    node.term = true;
    num_entries++;

  } // end addWord

  public synchronized void delWord(String word)
  {
    TLNode node = getNode(word.toLowerCase(),root_node);
    if (node!=null)
    { // delete the node and any unnecessary subnodes
      deleteNode(node);
      num_entries--;

    } // end if

  } // end delWord

  public synchronized void clear()
  {
    root_node = null;
    num_entries = 0;

  } // end clear

} // end class TernaryLexicon
