/*
 * 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-02 Eric J. Bowersox/Silverwrist Design Studios.  All Rights Reserved.
 * 
 * Contributor(s): 
 */
package com.silverwrist.venice.db;

import java.sql.*;
import com.silverwrist.util.StringUtil;
import com.silverwrist.venice.except.ValidationException;
import com.silverwrist.venice.util.IDUtils;

public class PostLinkDecoder
{
  /*--------------------------------------------------------------------------------
   * Static data members
   *--------------------------------------------------------------------------------
   */

  private static final int MAX_LINK_LEN = 130;
  private static final int MAX_COMM_LEN = 32;
  private static final int MAX_CONF_LEN = 64;

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

  private String commname = null;
  private String confname = null;
  private short topicnum = -1;
  private int first_post = -1;
  private int last_post = -1;

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

  public PostLinkDecoder(String data) throws ValidationException
  {
    if (StringUtil.isStringEmpty(data))
      throw new ValidationException("empty string");
    if (data.length()>MAX_LINK_LEN)
      throw new ValidationException("post link string too long");

    // turn the data into a StringBuffer so we can parse it
    StringBuffer work = new StringBuffer(data);

    // First Test: Bang
    int pos = work.toString().indexOf('!');
    if (pos>0)
    { // We have "community!something" ... store off the community name
      setCommunityName(work.substring(0,pos));
      work.delete(0,pos+1);
      if (work.length()==0)
	return;  // we had just "community!" - that's the syntax for a community link

    } // end if
    else if (pos==0)
      throw new ValidationException("! at beginning of string");

    // Second Test: Dot Number One
    pos = work.toString().indexOf('.');
    if (pos<0)
    { // there's no dots in here...this must have been either "postlink" or "community!conference"
      if (commname!=null)  // it's "community!conference"
	setConferenceName(work.toString());
      else   // it's "postlink"
	setPostRange(work);
      return;

    } // end if
    else if (pos==0)
      throw new ValidationException(". at beginning of string");

    // Peel off the initial substring.
    String conf_or_topic = work.substring(0,pos);
    work.delete(0,pos+1);

    if (work.length()==0)
    { // we had "conference." or "topic." (or perhaps "community!conference.")
      if (commname==null)
      { // it's either "conference." or "topic." - try topic first
	try
	{ // try it as a topic number first...
	  setTopicNumber(conf_or_topic);

	} // end try
	catch (ValidationException ve)
	{ // it's not a topic number - try it as a conference name!
	  setConferenceName(conf_or_topic);

	} // end catch

      } // end if
      else // it was "community!conference."...save it off
	setConferenceName(conf_or_topic);

      return; // all done here...

    } // end if

    // Third Test: Dot Number Two
    pos = work.toString().indexOf('.');
    if (pos<0)
    { // we had "conference.topic" or "topic.posts" (or perhaps "community!conference.topic")
      if (commname==null)
      { // we have "conference.topic" or "topic.posts" - have to determine this
	boolean is_topic = false;  // what does "work" currently hold?
	try
	{ // try the "topic.posts" possibility first...
	  setTopicNumber(conf_or_topic);

	} // end try
	catch (ValidationException ve)
	{ // nope, it's gotta be "conference.topic"
	  setConferenceName(conf_or_topic);
	  is_topic = true;

	} // end if

	// complete the parsing here
	if (is_topic)
	  setTopicNumber(work.toString());
	else
	  setPostRange(work);

      } // end if
      else
      { // it's "community!conference.topic"...set 'em up accordingly
	setConferenceName(conf_or_topic);
	setTopicNumber(work.toString());

      } // end else

      return;  // all done

    } // end if
    else if (pos==0)
      throw new ValidationException(". at beginning of string");

    // We definitely have "conference.topic.something" (or "community!conference.topic.something") now,
    // so save off the conference name and topic number.
    setConferenceName(conf_or_topic);
    setTopicNumber(work.substring(0,pos));
    work.delete(0,pos+1);

    if (work.length()==0)
      return;  // we just had "conference.topic." or "community!conference.topic.", which is valid

    setPostRange(work);  // the rest must be the post range!

  } // end constructor

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

  private static final boolean isAllDigits(String str)
  {
    for (int i=0; i<str.length(); i++)
    { // if any character is not a digit, bail out!
      if (!(Character.isDigit(str.charAt(i))))
	return false;

    } // end for

    return true;

  } // end isAllDigits

  private final void setCommunityName(String name) throws ValidationException
  {
    if (name.length()>MAX_COMM_LEN)
      throw new ValidationException("community alias is too long");
    if (!(IDUtils.isValidVeniceID(name)))
      throw new ValidationException("community alias is not a valid Venice identifier");
    commname = name;

  } // end setCommunityName

  private final void setConferenceName(String name) throws ValidationException
  {
    if (name.length()>MAX_CONF_LEN)
      throw new ValidationException("conference alias is too long");
    if (!(IDUtils.isValidVeniceID(name)))
      throw new ValidationException("conference alias is not a valid Venice identifier");
    confname = name;

  } // end setConferenceName

  private final void setTopicNumber(String val) throws ValidationException
  {
    if (!isAllDigits(val))
      throw new ValidationException("invalid topic number reference");
    short temp_val;
    try
    { // parse the temp value
      temp_val = Short.parseShort(val);

    } // end try
    catch (NumberFormatException nfe)
    { // if there's a validation exception, it goes here
      throw new ValidationException("invalid topic number reference");

    } // end catch

    topicnum = temp_val;

  } // end setTopicNumber

  private final void setPostRange(StringBuffer buf) throws ValidationException
  {
    int pos = buf.toString().indexOf('-');
    String temp = null;
    int temp_val = -1;
    if (pos>0)
    { // we've got "temp-something" here...make sure "temp" is a valid post number
      temp = buf.substring(0,pos);
      buf.delete(0,pos+1);
      if (!isAllDigits(temp))
	throw new ValidationException("invalid post number reference");
      try
      { // parse the temp value
	temp_val = Integer.parseInt(temp);

      } // end try
      catch (NumberFormatException nfe)
      { // if there's a validation exception, it goes here
	throw new ValidationException("invalid post number reference");

      } // end catch

      if (buf.length()==0)
      { // we just had "first-"...set it up
	first_post = temp_val;
	last_post = -1;
	return;

      } // end if

    } // end if
    else if (pos==0)
      throw new ValidationException("- at beginning of post range");

    // the remaining string must be all digits at this point
    if (!isAllDigits(buf.toString()))
      throw new ValidationException("invalid post number reference");
    try
    { // parse the first value
      first_post = Integer.parseInt(buf.toString());

    } // end try
    catch (NumberFormatException nfe)
    { // if there's a validation exception, it goes here
      throw new ValidationException("invalid post number reference");

    } // end catch

    if (temp!=null)
    { // we converted a "temp_val" above - include it
      if (temp_val<first_post)
      { // put the "temp_val" up front
	last_post = first_post;
	first_post = temp_val;

      } // end if
      else  // reverse order - arrange them correctly
	last_post = temp_val;

    } // end if
    else  // we have a "range" of one post
      last_post = first_post;

  } // end setPostRange

  /*--------------------------------------------------------------------------------
   * External operations
   *--------------------------------------------------------------------------------
   */

  public final String getCommunity()
  {
    return commname;

  } // end getCommunity

  public final String getConference()
  {
    return confname;

  } // end getConference

  public final short getTopic()
  {
    return topicnum;

  } // end getTopic

  public final int getFirstPost()
  {
    return first_post;

  } // end getFirstPost

  public final int getLastPost()
  {
    return last_post;

  } // end getFirstPost

  public final boolean needDatabaseVerification()
  {
    return ((commname!=null) || (confname!=null));

  } // end needDatabaseVerification

  public void verifyNames(Connection conn) throws ValidationException
  {
    Statement stmt = null;

    try
    { // do the necessary database stuff
      stmt = conn.createStatement();
      StringBuffer sql = new StringBuffer();
      ResultSet rs;

      if (commname!=null)
      { // verify the community name against the database
	sql.append("SELECT sigid FROM sigs WHERE alias = '").append(SQLUtil.encodeString(commname));
	sql.append("';");
	rs = stmt.executeQuery(sql.toString());
	if (!(rs.next()))
	  throw new ValidationException("community alias not found");
	sql.setLength(0);

      } // end if

      if (confname!=null)
      { // verify the conference name against the database
	sql.append("SELECT confid FROM confalias WHERE alias = '").append(SQLUtil.encodeString(confname));
	sql.append("';");
	rs = stmt.executeQuery(sql.toString());
	if (!(rs.next()))
	  throw new ValidationException("conference alias not found");

      } // end if

    } // end try
    catch (SQLException e)
    { // database error verifying stuff!
      throw new ValidationException("unable to verify names in post link");

    } // end catch
    finally
    { // shut down the statement to conserve resources
      SQLUtil.shutdown(stmt);

    } // end finally

  } // end verifyNames

} // end class PostLinkDecoder

