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

import java.io.*;
import java.util.*;
import javax.servlet.*;
import org.apache.log4j.*;
import org.apache.regexp.*;
import com.silverwrist.util.*;

class BrowserDatabase
{
  /*--------------------------------------------------------------------------------
   * Static data members
   *--------------------------------------------------------------------------------
   */

  private static Logger logger = Logger.getLogger(BrowserDatabase.class);

  private static final String SERVLET_ATTR = "com.silverwrist.venice.ui.BrowserDatabase";

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

  private boolean m_broken = false;                    // true if we had an error in loading
  private ArrayList m_browser_list = new ArrayList();  // the actual list
  private HashMap m_name_map = new HashMap();          // map names to elements

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

  private BrowserDatabase()
  {
    InputStream stm = null;
    BufferedReader rdr = null;
    try
    { // load the contents of the browscap.ini file
      stm = this.getClass().getResourceAsStream("browscap.ini");
      rdr = new BufferedReader(new InputStreamReader(stm));
      String line;
      BrowserEntry current = null;
      while ((line = rdr.readLine())!=null)
      { // read in lines from the resource
	line = line.trim();
	if ((line.length()==0) || line.startsWith(";"))
	  continue;  // blank line or comment

	if (line.startsWith("[") && line.endsWith("]"))
	{ // start of new browser entry
	  current = new BrowserEntry(line.substring(1,line.length() - 1));
	  m_name_map.put(current.getName(),current);
	  m_browser_list.add(current);
	  
	} // end if

	if (current!=null)
	{ // break the line at the equals sign
	  int p = line.indexOf('=');
	  if (p!=-1)
	  { // separate the name and value, and stow them
	    String name = line.substring(0,p).trim();
	    String value = line.substring(p + 1).trim();
	    if (value.startsWith("#"))
	      value = value.substring(1);
	    current.addValue(name,value);

	  } // end if

	} // end if

      } // end while

    } // end try
    catch (IOException e)
    { // WTF?  this is bad!
      logger.fatal("Error loading BROWSCAP.INI database",e);
      m_broken = true;

    } // end catch
    catch (RESyntaxException e)
    { // WTF?  this is bad!
      logger.fatal("Error loading BROWSCAP.INI database",e);
      m_broken = true;

    } // end catch
    finally
    { // shut down the stream
      IOUtil.shutdown(rdr);
      IOUtil.shutdown(stm);

    } // end finally

    if (m_broken)
      logger.info("BrowserDatabase: load broken");
    else
      logger.info("BrowserDatabase: loaded " + m_browser_list.size() + " entries");

  } // end constructor

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

  BrowserEntry lookup(String agent)
  {
    if (m_broken)
      return null;  // database wasn't loaded

    for (Iterator it=m_browser_list.iterator(); it.hasNext(); )
    { // look for a matching agent
      BrowserEntry be = (BrowserEntry)(it.next());
      if (be.matches(agent))
	return be;

    } // end for

    return null;

  } // end lookup

  BrowserEntry getByName(String name)
  {
    return (BrowserEntry)(m_name_map.get(name));

  } // end getByName

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

  static synchronized BrowserDatabase get(ServletContext ctxt)
  {
    BrowserDatabase rc = (BrowserDatabase)(ctxt.getAttribute(SERVLET_ATTR));
    if (rc==null)
    { // create database and add it to attributes
      rc = new BrowserDatabase();
      ctxt.setAttribute(SERVLET_ATTR,rc);

    } // end if

    return rc;

  } // end get

} // end class BrowserDatabase

class BrowserEntry
{
  /*--------------------------------------------------------------------------------
   * Static data members
   *--------------------------------------------------------------------------------
   */

  private static final String SPECIAL_CHARS = "*.?[](){}+|^$\\";

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

  private String m_id;                            // ID of this entry
  private REProgram m_pattern;                    // pattern the entry matches
  private HashMap m_values = new HashMap();       // values table for this entry

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

  BrowserEntry(String header) throws RESyntaxException
  {
    m_id = header.intern();
    RECompiler compiler = new RECompiler();
    m_pattern = compiler.compile(preprocess(header));
    
  } // end constructor

  /*--------------------------------------------------------------------------------
   * Internal operations
   *--------------------------------------------------------------------------------
   */

  private static final String preprocess(String header)
  {
    StringBuffer buf = new StringBuffer();
    char[] src = header.toCharArray();
    for (int i=0; i<src.length; i++)
    { // turn DOS-style glob characters into a proper regular expression
      if (src[i]=='*')
	buf.append(".*");
      else if (src[i]=='?')
	buf.append('.');
      else
      { // append the standard character
	if (SPECIAL_CHARS.indexOf(src[i])>=0)
	  buf.append('\\');
	buf.append(src[i]);

      } // end else

    } // end for

    return buf.toString();

  } // end preprocess

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

  String getName()
  {
    return m_id;

  } // end getName

  void addValue(String name, String value)
  {
    m_values.put(name,value);

  } // end addValue

  boolean matches(String agent)
  {
    RE r = new RE(m_pattern);
    return r.match(agent);

  } // end matches

  String getValue(String name)
  {
    return (String)(m_values.get(name));

  } // end getValue

} // end class BrowserEntry
