package org.dbxml.core.indexer;

/*
 * dbXML License, Version 1.0
 *
 *
 * Copyright (c) 1999-2001 The dbXML Group, L.L.C.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by
 *        The dbXML Group (http://www.dbxml.com/)."
 *    Alternately, this acknowledgment may appear in the software
 *    itself, if and wherever such third-party acknowledgments normally
 *    appear.
 *
 * 4. The names "dbXML" and "The dbXML Group" must not be used to
 *    endorse or promote products derived from this software without
 *    prior written permission. For written permission, please contact
 *    info@dbxml.com.
 *
 * 5. Products derived from this software may not be called "dbXML",
 *    nor may "dbXML" appear in their name, without prior written
 *    permission of The dbXML Group.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE DBXML GROUP OR ITS CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * $Id: ValueIndexer.java,v 1.14 2001/08/06 03:10:02 tom Exp $
 */

import org.dbxml.core.*;
import org.dbxml.core.Collection;
import org.dbxml.core.data.*;
import org.dbxml.core.filer.*;
import org.dbxml.core.query.*;
import org.dbxml.server.*;
import org.dbxml.xml.*;
import org.dbxml.xml.dom.*;

import java.io.*;
import java.util.*;

import org.w3c.dom.*;

/**
 * ValueIndexer is a basic implementation of the Indexer interface.
 * It is used for maintaining element and element@attribute value
 * indexes.
 */

public final class ValueIndexer extends BTree implements Indexer {
   private static final IndexMatch[] EmptyMatches = new IndexMatch[0];
   private static final Value EmptyValue = new Value(new byte[0]);

   private static final byte MATCHES = 20;
   private static final long MATCH_INFO = -1000;

   private static final String NAME = "name";
   private static final String PATTERN = "pattern";
   private static final String TYPE = "type";
   private static final String PAGESIZE = "pagesize";
   private static final String MAXKEYSIZE = "maxkeysize";

   private static final int STRING = 0;
   private static final int SHORT = 1;
   private static final int INT = 2;
   private static final int LONG = 3;
   private static final int FLOAT = 4;
   private static final int DOUBLE = 5;
   private static final int BYTE = 6;
   private static final int CHAR = 7;
   private static final int BOOLEAN = 8;
   private static final int TRIMMED = 9;

   private static final int[] sizes = { -1, 2, 4, 8, 4, 8, 1, 2, 1, -1 };

   private static final String STRING_VAL = "string";
   private static final String SHORT_VAL = "short";
   private static final String INT_VAL = "int";
   private static final String LONG_VAL = "long";
   private static final String FLOAT_VAL = "float";
   private static final String DOUBLE_VAL = "double";
   private static final String BYTE_VAL = "byte";
   private static final String CHAR_VAL = "char";
   private static final String BOOLEAN_VAL = "boolean";
   private static final String TRIMMED_VAL = "trimmed";

   private Configuration config;
   private Collection collection;
   private SymbolTable symbols;

   private String name;
   private String pattern;
   private int type;
   private int typeSize = 32;
   private boolean wildcard = false;

   private FileHeader fileHeader;

   public ValueIndexer() {
      super();
      fileHeader = getFileHeader();
      fileHeader.setPageSize(1024);
   }

   public void setConfig(Configuration config) {
      this.config = config;
      try {
         name = config.getAttribute(NAME);

         pattern = config.getAttribute(PATTERN);
         wildcard = pattern.indexOf('*') != -1;

         // Determine the Index Type
         String tv = config.getAttribute(TYPE, STRING_VAL).toLowerCase();
         if ( tv.equals(STRING_VAL) )       type = STRING;
         else if ( tv.equals(TRIMMED_VAL) ) type = TRIMMED;
         else if ( tv.equals(SHORT_VAL) )   type = SHORT;
         else if ( tv.equals(INT_VAL) )     type = INT;
         else if ( tv.equals(LONG_VAL) )    type = LONG;
         else if ( tv.equals(FLOAT_VAL) )   type = FLOAT;
         else if ( tv.equals(DOUBLE_VAL) )  type = DOUBLE;
         else if ( tv.equals(BYTE_VAL) )    type = BYTE;
         else if ( tv.equals(CHAR_VAL) )    type = CHAR;
         else if ( tv.equals(BOOLEAN_VAL) ) type = BOOLEAN;
         else {
            if ( pattern.indexOf('@') != -1 )
               type = STRING;
            else
               type = TRIMMED;
         }

         typeSize = sizes[type];

         fileHeader.setPageSize(config.getIntAttribute(PAGESIZE, fileHeader.getPageSize()));
         fileHeader.setMaxKeySize(config.getShortAttribute(MAXKEYSIZE, fileHeader.getMaxKeySize()));

         setLocation(name);
      }
      catch ( Exception e ) {
         org.dbxml.Debug.printStackTrace(e);
      }
   }

   public Configuration getConfig() {
      return config;
   }

   public String getName() {
      return name;
   }

   public void setLocation(String location) {
      setFile(new File(collection.getCollectionRoot(), location+".idx"));
   }

   public void setCollection(Collection collection) {
      try {
         this.collection = collection;
         symbols = collection.getSymbols();
      }
      catch ( Exception e ) {
         org.dbxml.Debug.printStackTrace(e);
      }
   }

   public String getIndexStyle() {
      return STYLE_NODEVALUE;
   }

   public String getPattern() {
      return pattern;
   }

   public Value getTypedValue(String value) {
      if ( type != STRING && type != TRIMMED ) {
         value = value.trim();

         if ( value.length() == 0 )
            return EmptyValue;

         ByteArrayOutputStream bos = new ByteArrayOutputStream(typeSize);
         DataOutputStream dos = new DataOutputStream(bos);
         try {
            switch ( type ) {
               case SHORT:
                  dos.writeShort(Short.parseShort(value));
                  break;
               case INT:
                  dos.writeInt(Integer.parseInt(value));
                  break;
               case LONG:
                  dos.writeLong(Long.parseLong(value));
                  break;
               case FLOAT:
                  dos.writeFloat(Float.parseFloat(value));
                  break;
               case DOUBLE:
                  dos.writeDouble(Double.parseDouble(value));
                  break;
               case BYTE:
                  dos.writeByte(Byte.parseByte(value));
                  break;
               case CHAR:
                  dos.writeChar(value.charAt(0));
                  break;
               case BOOLEAN:
                  if ( "[true][yes][1][y][on]".indexOf("["+value.toString().toLowerCase()+"]") != -1 )
                     dos.writeBoolean(true);
                  else if ( "[false][no][0][n][off]".indexOf("["+value.toString().toLowerCase()+"]") != -1 )
                     dos.writeBoolean(false);
                  else
                     return EmptyValue;
                  break;
            }
            return new Value(bos.toByteArray());
         }
         catch ( Exception e ) {
            return EmptyValue;
         }
      }

      if ( type == TRIMMED )
         value = QueryEngine.normalizeString(value);

      return new Value(value);
   }

   private Value getCombinedValue(Key key, int pos, int len, short elemID, short attrID) {
      Value result;
      try {
         // TODO: Friggin' optimize this!
         ByteArrayOutputStream bos = new ByteArrayOutputStream(key.getLength()+13);
         DataOutputStream dos = new DataOutputStream(bos);
         dos.write(key.getData());
         dos.writeByte(0); // Add a null for proper sorting
         dos.writeInt(pos);
         dos.writeInt(len);
         dos.writeShort(elemID);
         dos.writeShort(attrID);
         result = new Value(bos.toByteArray());
      }
      catch ( Exception e ) {
         result = null; // This will never happen
      }
      return result;
   }
   
   private IndexMatch getIndexMatch(Value v) {
      IndexMatch match;
      try {
         // TODO: Friggin' optimize this!
         byte[] b = v.getData();
         ByteArrayInputStream bis = new ByteArrayInputStream(b);
         DataInputStream dis = new DataInputStream(bis);
         
         Key key = new Key(b, 0, b.length-13);
         dis.skip(b.length-12); // Skip the null as well
         int pos = dis.readInt();
         int len = dis.readInt();
         short elemID = dis.readShort();
         short attrID = dis.readShort();
         match = new IndexMatch(key, pos, len, elemID, attrID);
      }
      catch ( Exception e ) {
         match = null; // This will never happen
      }
      return match;
   }
   
   public void remove(String value, Key key, int pos, int len, short elemID, short attrID) throws DBException {
      Value v = getTypedValue(value);
      if ( type != STRING && type != TRIMMED && v.getLength() == 0 )
         return;

      try {
         BTreeRootInfo root = findBTreeRoot(v);
         Value cv = getCombinedValue(key, pos, len, elemID, attrID);
         removeValue(root, cv);
      }
      catch ( DBException d ) {
         throw d;
      }
      catch ( Exception e ) {
         org.dbxml.Debug.printStackTrace(e);
      }
   }

   public void add(String value, Key key, int pos, int len, short elemID, short attrID) throws DBException {
      Value v = getTypedValue(value);
      if ( type != STRING && type != TRIMMED && v.getLength() == 0 )
         return;
  
      try {
         BTreeRootInfo root;
         
         try {
            root = findBTreeRoot(v);
         }
         catch ( BTreeNotFoundException e ) {
            root = createBTreeRoot(v);
         }

         Value cv = getCombinedValue(key, pos, len, elemID, attrID);
         addValue(root, cv, MATCH_INFO);
      }
      catch ( DBException d ) {
         throw d;
      }
      catch ( IOException e ) {
         throw new BTreeCorruptException("Corruption detected on add");
      }
      catch ( Exception e ) {
         org.dbxml.Debug.printStackTrace(e);
      }
   }

   public IndexMatch[] queryMatches(final IndexQuery query) throws DBException {
      // Pre-process the value-set for typing and trimming
      if ( type != STRING ) {
         Value[] vals = query.getValues();
         for ( int i = 0; i < vals.length; i++ )
            vals[i] = getTypedValue(vals[i].toString());
      }

      // Now issue the query
      final List results = new ArrayList();

      try {
         query(query, new BTreeCallback() {
            public boolean indexInfo(Value value, long pos) {
               try {
                  if ( pos == MATCH_INFO ) {
                     IndexMatch match = getIndexMatch(value);
                     if ( !wildcard )
                        results.add(match);
                     else {
                        IndexPattern pt = new IndexPattern(symbols, match.getElement(), match.getAttribute());
                        if ( pt.getMatchLevel(query.getPattern()) > 0 )
                           results.add(match);
                     }
                  }
                  else {
                     BTreeRootInfo root = new BTreeRootInfo(value, pos);
                     query(root, null, this);
                  }
                  return true;
               }
               catch ( Exception e ) {
                  org.dbxml.Debug.printStackTrace(e);
               }
               return true;
            }
         });
      }
      catch ( IOException e ) {
         throw new BTreeCorruptException("Corruption detected on query");
      }
      catch ( Exception e ) {
         org.dbxml.Debug.printStackTrace(e);
      }

      return (IndexMatch[])results.toArray(EmptyMatches);
   }
}


