/*
 * Copyright 2015 Julien Viet
 *
 * Licensed 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 io.termd.core.readline;

import java.nio.IntBuffer;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList;

/**
 * @author <a href="mailto:julien@julienviet.com">Julien Viet</a>
 */
public class EventQueue implements Iterator<KeyEvent> {

  private KeyEvent[] bindings;
  private final LinkedList<KeyEvent> events = new LinkedList<>();
  private int[] pending = new int[0];

  public EventQueue(Keymap keymap) {
    this.bindings = keymap.bindings.toArray(new KeyEvent[keymap.bindings.size()]);
  }

  public EventQueue append(int... codePoints) {
    pending = Arrays.copyOf(pending, pending.length + codePoints.length);
    System.arraycopy(codePoints, 0 , pending, pending.length - codePoints.length, codePoints.length);
    return this;
  }

  public EventQueue append(KeyEvent event) {
    events.add(event);
    return this;
  }

  public KeyEvent peek() {
    if (events.isEmpty()) {
      return match(pending);
    } else {
      return events.peekFirst();
    }
  }

  public boolean hasNext() {
    return peek() != null;
  }

  public KeyEvent next() {
    if (events.isEmpty()) {
      KeyEvent next = match(pending);
      if (next != null) {
        events.add(next);
        pending = Arrays.copyOfRange(pending, next.length(), pending.length);
      }
    }
    return events.removeFirst();
  }

  public int[] clear() {
    events.clear();
    int[] buffer = pending;
    pending = new int[0];
    return buffer;
  }

  /**
   * @return the buffer chars as a read-only int buffer
   */
  public IntBuffer getBuffer() {
    return IntBuffer.wrap(pending).asReadOnlyBuffer();
  }

  private KeyEvent match(int[] buffer) {
    if (buffer.length > 0) {
      KeyEvent candidate = null;
      int prefixes = 0;
      next:
      for (KeyEvent action : bindings) {
        if (action.length() > 0) {
          if (action.length() <= buffer.length) {
            for (int i = 0;i < action.length();i++) {
              if (action.getCodePointAt(i) != buffer[i]) {
                continue next;
              }
            }
            if (candidate != null && candidate.length() > action.length()) {
              continue next;
            }
            candidate = action;
          } else {
            for (int i = 0;i < buffer.length;i++) {
              if (action.getCodePointAt(i) != buffer[i]) {
                continue next;
              }
            }
            prefixes++;
          }
        }
      }
      if (candidate == null) {
        if (prefixes == 0) {
          final int c = buffer[0];
          return new KeyEvent() {
            @Override
            public int getCodePointAt(int index) throws IndexOutOfBoundsException {
              if (index != 0) {
                throw new IndexOutOfBoundsException("Wrong index " + index);
              }
              return c;
            }
            @Override
            public int length() {
              return 1;
            }
            @Override
            public String toString() {
              return "key:" + c;
            }
          };
        }
      } else {
        return candidate;
      }
    }
    return null;
  }
}
