/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.apache.uima.cas.impl;

import org.apache.uima.cas.CAS;
import org.apache.uima.cas.FSIndex;
import org.apache.uima.cas.FSIterator;
import org.apache.uima.cas.Feature;
import org.apache.uima.cas.FeatureStructure;
import org.apache.uima.cas.Type;
import org.apache.uima.cas.admin.FSIndexComparator;
import org.apache.uima.cas.admin.LinearTypeOrder;
import org.apache.uima.internal.util.ComparableIntPointerIterator;
import org.apache.uima.internal.util.IntComparator;
import org.apache.uima.internal.util.IntPointerIterator;

/**
 * Class comment for FSLeafIndexImpl.java goes here.
 * 
 * 
 */
public abstract class FSLeafIndexImpl<T extends FeatureStructure> implements IntComparator, FSIndex<T>, FSIndexImpl {

  private final int indexType;

  // A reference to the low-level CAS.
  protected CASImpl lowLevelCAS;

  private static final int STRING_CODE = 0;

  private static final int FLOAT_CODE = 1;

  private static final int INT_CODE = 2;

  private static final int TYPE_ORDER_CODE = 3;

  private static final int BOOLEAN_CODE = 4;

  private static final int BYTE_CODE = 5;

  private static final int SHORT_CODE = 6;

  private static final int LONG_CODE = 7;

  private static final int DOUBLE_CODE = 8;

  private FSIndexComparatorImpl comparator;

  private boolean isInitialized = false;

  // For each key, the int code of the type of that key.
  private int[] keyType;

  // For each feature key, the feature offset.
  private int[] keyOffset;

  // An array of the type orders used. This array is dense, most values will
  // be null.
  private LinearTypeOrder[] typeOrder;

  // For each key, the comparison to use.
  private int[] keyComp;

  // The number of keys.
  private int numKeys;

  private Type type; // The type of this

  // never called
  // declared private to block external calls
  @SuppressWarnings("unused")
  private FSLeafIndexImpl() {
    super();
    this.indexType = 0; // must do because it's final
  }

  /**
   * Constructor for FSLeafIndexImpl.
   */
  protected FSLeafIndexImpl(CASImpl cas, Type type, int indexType) {
    super();
    this.indexType = indexType;
    this.lowLevelCAS = cas;
    this.type = type;
  }

  abstract boolean insert(int fs);

  abstract void remove(int fs);

  // public abstract IntListIterator iterator();

  // public abstract ComparableIntIterator iterator(IntComparator comp);

  public abstract ComparableIntPointerIterator pointerIterator(IntComparator comp,
          int[] detectIllegalIndexUpdates, int typeCode);

  public FSIndexComparator getComparator() {
    return this.comparator;
  }

  IntComparator getIntComparator() {
    return this;
  }

  public int getIndexingStrategy() {
    return this.indexType;
  }

  boolean init(FSIndexComparator comp) {
    if (this.isInitialized) {
      return false;
    }
    FSIndexComparatorImpl comp1 = (FSIndexComparatorImpl) comp;
    this.comparator = comp1.copy();
    if (!this.comparator.isValid()) {
      return false;
    }
    final int nKeys = this.comparator.getNumberOfKeys();
    // Initialize the comparator info.
    this.keyType = new int[nKeys];
    this.keyOffset = new int[nKeys];
    this.keyComp = new int[nKeys];
    this.typeOrder = new LinearTypeOrder[nKeys];
    Feature keyFeature;
    for (int i = 0; i < nKeys; i++) {
      switch (comp.getKeyType(i)) {
        case FSIndexComparator.FEATURE_KEY: {
          keyFeature = this.comparator.getKeyFeature(i);
          this.keyType[i] = getKeyCode(keyFeature);
          this.keyOffset[i] = getFeatureOffset(keyFeature);
          this.keyComp[i] = this.comparator.getKeyComparator(i);
          break;
        }
        case FSIndexComparator.TYPE_ORDER_KEY: {
          this.keyType[i] = TYPE_ORDER_CODE;
          this.keyComp[i] = this.comparator.getKeyComparator(i);
          this.typeOrder[i] = this.comparator.getKeyTypeOrder(i);
          this.keyOffset[i] = 0;
          break;
        }
        default: {
          // This is an internal error.
          throw new RuntimeException("Assertion failed.");
        }
      }
    }
    this.numKeys = nKeys;
    this.isInitialized = true;
    return true;
  }

  private static final int getKeyCode(Feature feat) {
    String typeName = feat.getRange().getName();
    if (typeName.equals(CAS.TYPE_NAME_STRING)) {
      return STRING_CODE;
    }
    if (typeName.equals(CAS.TYPE_NAME_FLOAT)) {
      return FLOAT_CODE;
    }
    if (typeName.equals(CAS.TYPE_NAME_BOOLEAN)) {
      return BOOLEAN_CODE;
    }
    if (typeName.equals(CAS.TYPE_NAME_BYTE)) {
      return BYTE_CODE;
    }
    if (typeName.equals(CAS.TYPE_NAME_SHORT)) {
      return SHORT_CODE;
    }
    if (typeName.equals(CAS.TYPE_NAME_LONG)) {
      return LONG_CODE;
    }
    if (typeName.equals(CAS.TYPE_NAME_DOUBLE)) {
      return DOUBLE_CODE;
    }
    // This is defaulty and not nice. We rely on the checking in
    // FSIndexComparatorImpl to make sure that only correct types get
    // through.
    return INT_CODE;
  }

  private final int getFeatureOffset(Feature feat) {
    return this.comparator.getLowLevelCAS().getFeatureOffset(((FeatureImpl) feat).getCode());
  }

  public int ll_compare(int ref1, int ref2) {
    return this.compare(ref1, ref2);
  }

  public int compare(int fs1, int fs2) {
    int[] heap = this.lowLevelCAS.getHeap().heap;
    int val1, val2;
    int compVal;
    float float1, float2;
    String string1, string2;
    for (int i = 0; i < this.numKeys; i++) {
      val1 = heap[fs1 + this.keyOffset[i]];
      val2 = heap[fs2 + this.keyOffset[i]];
      switch (this.keyType[i]) {
        case STRING_CODE: {
          // System.out.println("Comparing string codes " + val1 + " and "
          // + val2);
          // System.out.println(
          // "Strings: "
          // + lowLevelCAS.getStringForCode(val1)
          // + ", "
          // + lowLevelCAS.getStringForCode(val2));
          // if (lowLevelCAS.getStringForCode(val1) == null) {
          // System.out.println("Value for " + val1 + " is <null>.");
          // }
          // if (lowLevelCAS.getStringForCode(val2) == null) {
          // System.out.println("Value for " + val2 + " is <null>.");
          // }
          string1 = this.lowLevelCAS.getStringForCode(val1);
          string2 = this.lowLevelCAS.getStringForCode(val2);
          if (string1 == null) {
            if (string2 == null) {
              compVal = 0;
            } else {
              compVal = -1;
            }
          } else {
            if (string2 == null) {
              compVal = 1;
            } else {
              compVal = string1.compareTo(string2);
            }
          }

          if (compVal != 0) {
            if (this.keyComp[i] == FSIndexComparator.STANDARD_COMPARE) {
              return compVal;
            }
            return -compVal;
          }
          break;
        }
        case FLOAT_CODE: {
          float1 = CASImpl.int2float(val1);
          float2 = CASImpl.int2float(val2);
          if (float1 < float2) {
            if (this.keyComp[i] == FSIndexComparator.STANDARD_COMPARE) {
              return -1;
            }
            return 1;
          } else if (float1 > float2) {
            if (this.keyComp[i] == FSIndexComparator.STANDARD_COMPARE) {
              return 1;
            }
            return -1;
          }
          break;
        }
        case TYPE_ORDER_CODE: {
          if (val1 == val2) {
            break;
          }
          if (this.typeOrder[i].lessThan(val1, val2)) {
            if (this.keyComp[i] == FSIndexComparator.STANDARD_COMPARE) {
              return -1;
            }
            return 1;
          }
          if (this.keyComp[i] == FSIndexComparator.STANDARD_COMPARE) {
            return 1;
          }
          return -1;
        }
        case LONG_CODE: {
          long long1 = this.lowLevelCAS.getLongHeap().getHeapValue(val1);
          long long2 = this.lowLevelCAS.getLongHeap().getHeapValue(val2);
          if (long1 < long2) {
            if (this.keyComp[i] == FSIndexComparator.STANDARD_COMPARE) {
              return -1;
            }
            return 1;
          } else if (long1 > long2) {
            if (this.keyComp[i] == FSIndexComparator.STANDARD_COMPARE) {
              return 1;
            }
            return -1;
          }
          break;
        }
        case DOUBLE_CODE: {
          double double1 = Double.longBitsToDouble(this.lowLevelCAS.getLongHeap().getHeapValue(val1));
          double double2 = Double.longBitsToDouble(this.lowLevelCAS.getLongHeap().getHeapValue(val2));
          if (double1 < double2) {
            if (this.keyComp[i] == FSIndexComparator.STANDARD_COMPARE) {
              return -1;
            }
            return 1;
          } else if (double1 > double2) {
            if (this.keyComp[i] == FSIndexComparator.STANDARD_COMPARE) {
              return 1;
            }
            return -1;
          }
          break;
        }

        default: { // Compare the int values directly.
          // boolean compare done here as well.
          // byte compare done here as well.
          // short compare done here as well.
          if (val1 < val2) {
            if (this.keyComp[i] == FSIndexComparator.STANDARD_COMPARE) {
              return -1;
            }
            return 1;
          } else if (val1 > val2) {
            if (this.keyComp[i] == FSIndexComparator.STANDARD_COMPARE) {
              return 1;
            }
            return -1;

          }
          break;
        }
      }
    }
    // FSs are identical as far as this comparator goes.
    return 0;
  }

  // Eclipse says this method is never called by uimaj-core methods 9-2009
  public final boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    return this.comparator.equals(o);
    // if (o instanceof FSIndexComparator) {
    // return this.comparator.equals(o);
    // } else if (o instanceof FSVectorIndex) {
    // return this.comparator.equals(((FSLeafIndexImpl)o).comparator);
    // } else {
    // return false;
    // }
  }

  public int hashCode() {
    throw new UnsupportedOperationException();
  }

  /**
   * @see org.apache.uima.cas.FSIndex#compare(FeatureStructure, FeatureStructure)
   */
  public int compare(FeatureStructure fs1, FeatureStructure fs2) {
    return compare(((FeatureStructureImpl) fs1).getAddress(), ((FeatureStructureImpl) fs2)
            .getAddress());
  }

  /**
   * @see org.apache.uima.cas.FSIndex#getType()
   */
  public Type getType() {
    return this.type;
  }

  protected abstract IntPointerIterator refIterator();

  public IntPointerIterator getIntIterator() {
    return refIterator();
  }

  protected abstract IntPointerIterator refIterator(int fsCode);

  // these next two are never called (maybe)
  // because the object this method is called on is
  // never this object, but instead the FSIndexRepositoryImpl.IndexImpl object
  //
  // It would be good to refactor this so that this confusion is eliminated,
  // perhaps by not having this class implement FSIndex

  /**
   * @see org.apache.uima.cas.FSIndex#iterator()
   */
  public FSIterator<T> iterator() {
    return new FSIteratorWrapper<T>(refIterator(), this.lowLevelCAS);
  }

  /**
   * @see org.apache.uima.cas.FSIndex#iterator(FeatureStructure)
   */
  public FSIterator<T> iterator(FeatureStructure fs) {
    return new FSIteratorWrapper<T>(refIterator(((FeatureStructureImpl) fs).getAddress()),
            this.lowLevelCAS);
  }

  /**
   * Method deleteFS.
   * 
   * @param fs
   */
  public abstract void deleteFS(FeatureStructure fs);

  public LowLevelIterator ll_iterator(boolean ambiguous) {
    if (ambiguous) {
      return this.ll_iterator();
    }

    return null;
  }

  public LowLevelIterator ll_rootIterator() {
      return this.ll_iterator();
  }

}
