/**
 * 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.lucene.analysis;

import java.io.IOException;
import java.io.Reader;
import java.util.LinkedList;

/**
 * Simplistic {@link CharFilter} that applies the mappings
 * contained in a {@link NormalizeCharMap} to the character
 * stream, and correcting the resulting changes to the
 * offsets.
 */
public class MappingCharFilter extends BaseCharFilter {

  private final NormalizeCharMap normMap;
  private LinkedList<Character> buffer;
  private String replacement;
  private int charPointer;
  private int nextCharCounter;

  /** Default constructor that takes a {@link CharStream}. */
  public MappingCharFilter(NormalizeCharMap normMap, CharStream in) {
    super(in);
    this.normMap = normMap;
  }

  /** Easy-use constructor that takes a {@link Reader}. */
  public MappingCharFilter(NormalizeCharMap normMap, Reader in) {
    super(CharReader.get(in));
    this.normMap = normMap;
  }

  @Override
  public int read() throws IOException {
    while(true) {
      if (replacement != null && charPointer < replacement.length()) {
        return replacement.charAt(charPointer++);
      }

      int firstChar = nextChar();
      if (firstChar == -1) return -1;
      NormalizeCharMap nm = normMap.submap != null ?
        normMap.submap.get(Character.valueOf((char) firstChar)) : null;
      if (nm == null) return firstChar;
      NormalizeCharMap result = match(nm);
      if (result == null) return firstChar;
      replacement = result.normStr;
      charPointer = 0;
      if (result.diff != 0) {
        int prevCumulativeDiff = getLastCumulativeDiff();
        if (result.diff < 0) {
          for(int i = 0; i < -result.diff ; i++)
            addOffCorrectMap(nextCharCounter + i - prevCumulativeDiff, prevCumulativeDiff - 1 - i);
        } else {
          addOffCorrectMap(nextCharCounter - result.diff - prevCumulativeDiff, prevCumulativeDiff + result.diff);
        }
      }
    }
  }

  private int nextChar() throws IOException {
    if (buffer != null && !buffer.isEmpty()) {
      nextCharCounter++;
      return buffer.removeFirst().charValue();
    }
    int nextChar = input.read();
    if (nextChar != -1) {
      nextCharCounter++;
    }
    return nextChar;
  }

  private void pushChar(int c) {
    nextCharCounter--;
    if(buffer == null)
      buffer = new LinkedList<Character>();
    buffer.addFirst(Character.valueOf((char) c));
  }

  private void pushLastChar(int c) {
    if (buffer == null) {
      buffer = new LinkedList<Character>();
    }
    buffer.addLast(Character.valueOf((char) c));
  }

  private NormalizeCharMap match(NormalizeCharMap map) throws IOException {
    NormalizeCharMap result = null;
    if (map.submap != null) {
      int chr = nextChar();
      if (chr != -1) {
        NormalizeCharMap subMap = map.submap.get(Character.valueOf((char) chr));
        if (subMap != null) {
          result = match(subMap);
        }
        if (result == null) {
          pushChar(chr);
        }
      }
    }
    if (result == null && map.normStr != null) {
      result = map;
    }
    return result;
  }

  @Override
  public int read(char[] cbuf, int off, int len) throws IOException {
    char[] tmp = new char[len];
    int l = input.read(tmp, 0, len);
    if (l != -1) {
      for(int i = 0; i < l; i++)
        pushLastChar(tmp[i]);
    }
    l = 0;
    for(int i = off; i < off + len; i++) {
      int c = read();
      if (c == -1) break;
      cbuf[i] = (char) c;
      l++;
    }
    return l == 0 ? -1 : l;
  }
}
