// 
// Decompiled by Procyon v0.6.0
// 

package org.jline.keymap;

import java.util.Iterator;
import java.util.TreeMap;
import java.util.Map;
import org.jline.utils.Curses;
import org.jline.utils.InfoCmp;
import org.jline.terminal.Terminal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;

public class KeyMap<T>
{
    public static final int KEYMAP_LENGTH = 128;
    public static final long DEFAULT_AMBIGUOUS_TIMEOUT = 1000L;
    private Object[] mapping;
    private T anotherKey;
    private T unicode;
    private T nomatch;
    private long ambiguousTimeout;
    public static final Comparator<String> KEYSEQ_COMPARATOR;
    
    public KeyMap() {
        this.mapping = new Object[128];
        this.anotherKey = null;
        this.ambiguousTimeout = 1000L;
    }
    
    public static String display(final String key) {
        final StringBuilder sb = new StringBuilder();
        sb.append("\"");
        for (int i = 0; i < key.length(); ++i) {
            final char c = key.charAt(i);
            if (c < ' ') {
                sb.append('^');
                sb.append((char)(c + 'A' - 1));
            }
            else if (c == '\u007f') {
                sb.append("^?");
            }
            else if (c == '^' || c == '\\') {
                sb.append('\\').append(c);
            }
            else if (c >= '\u0080') {
                sb.append(String.format("\\u%04x", (int)c));
            }
            else {
                sb.append(c);
            }
        }
        sb.append("\"");
        return sb.toString();
    }
    
    public static String translate(String str) {
        if (!str.isEmpty()) {
            final char c = str.charAt(0);
            if ((c == '\'' || c == '\"') && str.charAt(str.length() - 1) == c) {
                str = str.substring(1, str.length() - 1);
            }
        }
        final StringBuilder keySeq = new StringBuilder();
        for (int i = 0; i < str.length(); ++i) {
            char c2 = str.charAt(i);
            if (c2 == '\\') {
                if (++i >= str.length()) {
                    break;
                }
                c2 = str.charAt(i);
                switch (c2) {
                    case 'a': {
                        c2 = '\u0007';
                        break;
                    }
                    case 'b': {
                        c2 = '\b';
                        break;
                    }
                    case 'd': {
                        c2 = '\u007f';
                        break;
                    }
                    case 'E':
                    case 'e': {
                        c2 = '\u001b';
                        break;
                    }
                    case 'f': {
                        c2 = '\f';
                        break;
                    }
                    case 'n': {
                        c2 = '\n';
                        break;
                    }
                    case 'r': {
                        c2 = '\r';
                        break;
                    }
                    case 't': {
                        c2 = '\t';
                        break;
                    }
                    case 'v': {
                        c2 = '\u000b';
                        break;
                    }
                    case '\\': {
                        c2 = '\\';
                        break;
                    }
                    case '0':
                    case '1':
                    case '2':
                    case '3':
                    case '4':
                    case '5':
                    case '6':
                    case '7': {
                        c2 = '\0';
                        for (int j = 0; j < 3; ++j, ++i) {
                            if (i >= str.length()) {
                                break;
                            }
                            final int k = Character.digit(str.charAt(i), 8);
                            if (k < 0) {
                                break;
                            }
                            c2 = (char)(c2 * '\b' + k);
                        }
                        --i;
                        c2 &= '\u00ff';
                        break;
                    }
                    case 'x': {
                        ++i;
                        c2 = '\0';
                        for (int j = 0; j < 2; ++j, ++i) {
                            if (i >= str.length()) {
                                break;
                            }
                            final int k = Character.digit(str.charAt(i), 16);
                            if (k < 0) {
                                break;
                            }
                            c2 = (char)(c2 * '\u0010' + k);
                        }
                        --i;
                        c2 &= '\u00ff';
                        break;
                    }
                    case 'u': {
                        ++i;
                        c2 = '\0';
                        for (int j = 0; j < 4; ++j, ++i) {
                            if (i >= str.length()) {
                                break;
                            }
                            final int k = Character.digit(str.charAt(i), 16);
                            if (k < 0) {
                                break;
                            }
                            c2 = (char)(c2 * '\u0010' + k);
                        }
                        break;
                    }
                    case 'C': {
                        if (++i >= str.length()) {
                            break;
                        }
                        c2 = str.charAt(i);
                        if (c2 == '-') {
                            if (++i >= str.length()) {
                                break;
                            }
                            c2 = str.charAt(i);
                        }
                        c2 = ((c2 == '?') ? '\u007f' : ((char)(Character.toUpperCase(c2) & '\u001f')));
                        break;
                    }
                }
            }
            else if (c2 == '^') {
                if (++i >= str.length()) {
                    break;
                }
                c2 = str.charAt(i);
                if (c2 != '^') {
                    c2 = ((c2 == '?') ? '\u007f' : ((char)(Character.toUpperCase(c2) & '\u001f')));
                }
            }
            keySeq.append(c2);
        }
        return keySeq.toString();
    }
    
    public static Collection<String> range(final String range) {
        final String[] keys = range.split("-");
        if (keys.length != 2) {
            return null;
        }
        keys[0] = translate(keys[0]);
        keys[1] = translate(keys[1]);
        if (keys[0].length() != keys[1].length()) {
            return null;
        }
        String pfx;
        if (keys[0].length() > 1) {
            pfx = keys[0].substring(0, keys[0].length() - 1);
            if (!keys[1].startsWith(pfx)) {
                return null;
            }
        }
        else {
            pfx = "";
        }
        final char c0 = keys[0].charAt(keys[0].length() - 1);
        final char c2 = keys[1].charAt(keys[1].length() - 1);
        if (c0 > c2) {
            return null;
        }
        final Collection<String> seqs = new ArrayList<String>();
        for (char c3 = c0; c3 <= c2; ++c3) {
            seqs.add(pfx + c3);
        }
        return seqs;
    }
    
    public static String esc() {
        return "\u001b";
    }
    
    public static String alt(final char c) {
        return "\u001b" + c;
    }
    
    public static String alt(final String c) {
        return "\u001b" + c;
    }
    
    public static String del() {
        return "\u007f";
    }
    
    public static String ctrl(final char key) {
        return (key == '?') ? del() : Character.toString((char)(Character.toUpperCase(key) & '\u001f'));
    }
    
    public static String key(final Terminal terminal, final InfoCmp.Capability capability) {
        return Curses.tputs(terminal.getStringCapability(capability), new Object[0]);
    }
    
    public T getUnicode() {
        return this.unicode;
    }
    
    public void setUnicode(final T unicode) {
        this.unicode = unicode;
    }
    
    public T getNomatch() {
        return this.nomatch;
    }
    
    public void setNomatch(final T nomatch) {
        this.nomatch = nomatch;
    }
    
    public long getAmbiguousTimeout() {
        return this.ambiguousTimeout;
    }
    
    public void setAmbiguousTimeout(final long ambiguousTimeout) {
        this.ambiguousTimeout = ambiguousTimeout;
    }
    
    public T getAnotherKey() {
        return this.anotherKey;
    }
    
    public Map<String, T> getBoundKeys() {
        final Map<String, T> bound = new TreeMap<String, T>(KeyMap.KEYSEQ_COMPARATOR);
        doGetBoundKeys(this, "", bound);
        return bound;
    }
    
    private static <T> void doGetBoundKeys(final KeyMap<T> keyMap, final String prefix, final Map<String, T> bound) {
        if (keyMap.anotherKey != null) {
            bound.put(prefix, keyMap.anotherKey);
        }
        for (int c = 0; c < keyMap.mapping.length; ++c) {
            if (keyMap.mapping[c] instanceof KeyMap) {
                doGetBoundKeys((KeyMap<Object>)keyMap.mapping[c], prefix + (char)c, (Map<String, Object>)bound);
            }
            else if (keyMap.mapping[c] != null) {
                bound.put(prefix + (char)c, (T)keyMap.mapping[c]);
            }
        }
    }
    
    public T getBound(final CharSequence keySeq, final int[] remaining) {
        remaining[0] = -1;
        if (keySeq == null || keySeq.length() <= 0) {
            return this.anotherKey;
        }
        final char c = keySeq.charAt(0);
        if (c >= this.mapping.length) {
            remaining[0] = Character.codePointCount(keySeq, 0, keySeq.length());
            return null;
        }
        if (this.mapping[c] instanceof KeyMap) {
            final CharSequence sub = keySeq.subSequence(1, keySeq.length());
            return (T)((KeyMap)this.mapping[c]).getBound(sub, remaining);
        }
        if (this.mapping[c] != null) {
            remaining[0] = keySeq.length() - 1;
            return (T)this.mapping[c];
        }
        remaining[0] = keySeq.length();
        return this.anotherKey;
    }
    
    public T getBound(final CharSequence keySeq) {
        final int[] remaining = { 0 };
        final T res = this.getBound(keySeq, remaining);
        return (remaining[0] <= 0) ? res : null;
    }
    
    public void bindIfNotBound(final T function, final CharSequence keySeq) {
        if (function != null && keySeq != null) {
            bind(this, keySeq, function, true);
        }
    }
    
    public void bind(final T function, final CharSequence... keySeqs) {
        for (final CharSequence keySeq : keySeqs) {
            this.bind(function, keySeq);
        }
    }
    
    public void bind(final T function, final Iterable<? extends CharSequence> keySeqs) {
        for (final CharSequence keySeq : keySeqs) {
            this.bind(function, keySeq);
        }
    }
    
    public void bind(final T function, final CharSequence keySeq) {
        if (keySeq != null) {
            if (function == null) {
                this.unbind(keySeq);
            }
            else {
                bind(this, keySeq, function, false);
            }
        }
    }
    
    public void unbind(final CharSequence... keySeqs) {
        for (final CharSequence keySeq : keySeqs) {
            this.unbind(keySeq);
        }
    }
    
    public void unbind(final CharSequence keySeq) {
        if (keySeq != null) {
            unbind((KeyMap<Object>)this, keySeq);
        }
    }
    
    private static <T> T unbind(KeyMap<T> map, final CharSequence keySeq) {
        KeyMap<T> prev = null;
        if (keySeq == null || keySeq.length() <= 0) {
            return null;
        }
        for (int i = 0; i < keySeq.length() - 1; ++i) {
            final char c = keySeq.charAt(i);
            if (c > map.mapping.length) {
                return null;
            }
            if (!(map.mapping[c] instanceof KeyMap)) {
                return null;
            }
            prev = map;
            map = (KeyMap)map.mapping[c];
        }
        final char c2 = keySeq.charAt(keySeq.length() - 1);
        if (c2 > map.mapping.length) {
            return null;
        }
        if (map.mapping[c2] instanceof KeyMap) {
            final KeyMap<?> sub = (KeyMap<?>)map.mapping[c2];
            final Object res = sub.anotherKey;
            sub.anotherKey = null;
            return (T)res;
        }
        final Object res2 = map.mapping[c2];
        map.mapping[c2] = null;
        int nb = 0;
        for (int j = 0; j < map.mapping.length; ++j) {
            if (map.mapping[j] != null) {
                ++nb;
            }
        }
        if (nb == 0 && prev != null) {
            prev.mapping[keySeq.charAt(keySeq.length() - 2)] = map.anotherKey;
        }
        return (T)res2;
    }
    
    private static <T> void bind(KeyMap<T> map, final CharSequence keySeq, final T function, final boolean onlyIfNotBound) {
        if (keySeq != null && keySeq.length() > 0) {
            for (int i = 0; i < keySeq.length(); ++i) {
                final char c = keySeq.charAt(i);
                if (c >= map.mapping.length) {
                    return;
                }
                if (i < keySeq.length() - 1) {
                    if (!(map.mapping[c] instanceof KeyMap)) {
                        final KeyMap<T> m = new KeyMap<T>();
                        m.anotherKey = (T)map.mapping[c];
                        map.mapping[c] = m;
                    }
                    map = (KeyMap)map.mapping[c];
                }
                else if (map.mapping[c] instanceof KeyMap) {
                    ((KeyMap)map.mapping[c]).anotherKey = (T)function;
                }
                else {
                    final Object op = map.mapping[c];
                    if (!onlyIfNotBound || op == null) {
                        map.mapping[c] = function;
                    }
                }
            }
        }
    }
    
    static {
        KEYSEQ_COMPARATOR = ((s1, s2) -> {
            final int len1 = s1.length();
            final int len2 = s2.length();
            final int lim = Math.min(len1, len2);
            int k = 0;
            while (k < lim) {
                final char c1 = s1.charAt(k);
                final char c2 = s2.charAt(k);
                if (c1 != c2) {
                    final int l = len1 - len2;
                    return (l != 0) ? l : (c1 - c2);
                }
                else {
                    ++k;
                }
            }
            return len1 - len2;
        });
    }
}
