/*
* Copyright (c) 2013 Menny Even-Danan
*
* 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 com.anysoftkeyboard.keyboards;
import java.util.LinkedList;
public class KeyEventStateMachine {
public static final int KEYCODE_FIRST_CHAR = -4097;
private static final class KeyEventTransition {
private KeyEventState next;
private int keyCode;
KeyEventTransition(int keyCode, KeyEventState next) {
this.next = next;
this.keyCode = keyCode;
}
}
private static final class KeyEventState {
private LinkedList<KeyEventTransition> transitions;
private int result;
KeyEventState() {
this.result = 0;
}
public KeyEventState getNext(int keyCode) {
if (this.transitions == null)
return null;
for (KeyEventTransition transition : this.transitions) {
if (transition.keyCode == keyCode) {
return transition.next;
}
}
return null;
}
public void addNextState(int keyCode, KeyEventState next) {
if (this.transitions == null)
this.transitions = new LinkedList<>();
this.transitions.add(new KeyEventTransition(keyCode, next));
}
public void setCharacter(int result) {
this.result = result;
}
public boolean hasNext() {
return (this.transitions != null);
}
}
private KeyEventState start;
public static enum State {RESET, REWIND, NO_MATCH, PART_MATCH, FULL_MATCH}
private class NFAPart {
KeyEventState state;
int iVisibleSequenceLength;
int iSequenceLength;
private int resultChar;
private int sequenceLength;
private int visibleSequenceLength;
NFAPart() {
this.reset();
}
void reset() {
this.state = KeyEventStateMachine.this.start;
this.iSequenceLength = 0;
this.iVisibleSequenceLength = 0;
}
void reset(NFAPart part) {
this.state = part.state;
this.iSequenceLength = part.iSequenceLength;
this.iVisibleSequenceLength = part.iVisibleSequenceLength;
}
private void returnToFirst(int keyCode) {
this.state = KeyEventStateMachine.this.start;
if (keyCode > 0)
this.iVisibleSequenceLength--;
this.iSequenceLength--;
}
private State addKeyCode(int keyCode) {
this.state = this.state.getNext(keyCode);
if (this.state == null) {
this.reset();
return State.RESET;
}
if (keyCode > 0)
this.iVisibleSequenceLength++;
this.iSequenceLength++;
if (this.state.result != 0) {
this.resultChar = this.state.result;
this.sequenceLength = this.iSequenceLength;
this.visibleSequenceLength = this.iVisibleSequenceLength;
if (this.resultChar == KEYCODE_FIRST_CHAR) {
return State.REWIND;
}
if (!this.state.hasNext()) {
this.reset();
return State.FULL_MATCH;
}
return State.PART_MATCH;
}
return State.NO_MATCH;
}
}
private static final int MAX_NFA_DIVIDES = 30;
class RingBuffer {
private NFAPart[] buffer;
private int start;
private int end;
private int count;
RingBuffer() {
this.buffer = new NFAPart[MAX_NFA_DIVIDES];
this.start = 0;
this.end = 0;
this.count = 0;
}
boolean hasItem() {
return this.count > 0;
}
NFAPart getItem() {
assert (this.count > 0);
NFAPart result = this.buffer[this.start];
this.buffer[this.start] = null;
this.start = (this.start + 1) % MAX_NFA_DIVIDES;
this.count--;
return result;
}
void putItem(NFAPart item) {
assert (this.count < MAX_NFA_DIVIDES);
this.buffer[this.end] = item;
this.end = (this.end + 1) % MAX_NFA_DIVIDES;
this.count++;
}
int getCount() {
return this.count;
}
}
private RingBuffer walker;
private RingBuffer walkerhelper;
private RingBuffer walkerunused;
private int sequenceLength;
private int resultChar;
public KeyEventStateMachine() {
this.start = new KeyEventState();
this.walker = new RingBuffer();
this.walker.putItem(new NFAPart());
this.walkerunused = new RingBuffer();
for (int i = 1; i < MAX_NFA_DIVIDES; i++)
this.walkerunused.putItem(new NFAPart());
this.walkerhelper = new RingBuffer();
}
private static KeyEventState addNextState(KeyEventState current, int keyCode) {
KeyEventState next = current.getNext(keyCode);
if (next != null)
return next;
next = new KeyEventState();
current.addNextState(keyCode, next);
return next;
}
public void addSequence(int[] sequence, int result) {
addSpecialKeySequence(sequence, 0/*no special key*/, result);
}
public void addSpecialKeySequence(int[] sequence, int specialKey, int result) {
KeyEventState c = this.start;
for (int i = 0; i < sequence.length; i++) {
if (specialKey != 0) {
//special key first
c = addNextState(c, specialKey);
}
//the sequence second
c = addNextState(c, sequence[i]);
}
c.setCharacter(result);
}
public State addKeyCode(int keyCode) {
this.sequenceLength = 0;
this.resultChar = 0;
NFAPart found = null;
State resultstate = State.RESET;
if (!this.walker.hasItem()) {
NFAPart part = this.walkerunused.getItem();
part.reset();
this.walker.putItem(part);
}
while (this.walker.hasItem()) {
NFAPart cWalker = this.walker.getItem();
State result = cWalker.addKeyCode(keyCode);
if (result == State.REWIND) {
if (this.walkerunused.hasItem()) {
NFAPart newwalker = this.walkerunused.getItem();
newwalker.reset(cWalker);
this.walkerhelper.putItem(newwalker);
}
cWalker.returnToFirst(keyCode);
result = cWalker.addKeyCode(keyCode);
}
if (result == State.FULL_MATCH) {
if (found == null) {
this.walkerhelper.putItem(cWalker);
resultstate = result;
found = cWalker;
break;
}
}
if (result == State.PART_MATCH || result == State.NO_MATCH) {
if (resultstate == State.RESET)
resultstate = result;
this.walkerhelper.putItem(cWalker);
} else {
this.walkerunused.putItem(cWalker);
}
if (result == State.PART_MATCH) {
if (this.walkerunused.hasItem()) {
NFAPart newwalker = this.walkerunused.getItem();
newwalker.reset();
this.walkerhelper.putItem(newwalker);
}
}
if (result == State.PART_MATCH) {
if ((found == null) || (found.sequenceLength < cWalker.sequenceLength)) {
found = cWalker;
resultstate = result;
}
}
}
while (this.walker.hasItem())
this.walkerunused.putItem(this.walker.getItem());
final RingBuffer switchWalkerarrays = this.walkerhelper;
this.walkerhelper = this.walker;
this.walker = switchWalkerarrays;
if (found != null) {
this.sequenceLength = found.visibleSequenceLength;
this.resultChar = found.resultChar;
int i = 0;
final int count = this.walker.getCount();
while (i < count) {
NFAPart part = this.walker.getItem();
this.walker.putItem(part);
i++;
if (part == found && resultstate == State.FULL_MATCH)
break;
if (found.visibleSequenceLength > 1) {
part.iVisibleSequenceLength -= found.visibleSequenceLength - 1;
}
if (part == found)
break;
}
while (i++ < count) {
this.walker.putItem(this.walker.getItem());
}
}
return resultstate;
}
public int getCharacter() {
return this.resultChar;
}
public int getSequenceLength() {
return this.sequenceLength;
}
public void reset() {
while (this.walker.hasItem())
this.walkerunused.putItem(this.walker.getItem());
NFAPart first = this.walkerunused.getItem();
first.reset();
this.walker.putItem(first);
}
}