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

package org.jline.keymap;

import java.util.stream.IntStream;
import java.io.IOException;
import java.io.IOError;
import org.jline.reader.EndOfFileException;
import org.jline.utils.ClosedException;
import java.util.function.Consumer;
import java.util.Objects;
import java.util.ArrayDeque;
import java.util.Deque;
import org.jline.utils.NonBlockingReader;

public class BindingReader
{
    protected final NonBlockingReader reader;
    protected final StringBuilder opBuffer;
    protected final Deque<Integer> pushBackChar;
    protected String lastBinding;
    
    public BindingReader(final NonBlockingReader reader) {
        this.opBuffer = new StringBuilder();
        this.pushBackChar = new ArrayDeque<Integer>();
        this.reader = reader;
    }
    
    public <T> T readBinding(final KeyMap<T> keys) {
        return this.readBinding(keys, null, true);
    }
    
    public <T> T readBinding(final KeyMap<T> keys, final KeyMap<T> local) {
        return this.readBinding(keys, local, true);
    }
    
    public <T> T readBinding(final KeyMap<T> keys, final KeyMap<T> local, final boolean block) {
        this.lastBinding = null;
        T o = null;
        final int[] remaining = { 0 };
        boolean hasRead = false;
        while (true) {
            if (local != null) {
                o = local.getBound(this.opBuffer, remaining);
            }
            if (o == null && (local == null || remaining[0] >= 0)) {
                o = keys.getBound(this.opBuffer, remaining);
            }
            if (o != null) {
                if (remaining[0] >= 0) {
                    this.runMacro(this.opBuffer.substring());
                    this.opBuffer.setLength();
                }
                else {
                    final long ambiguousTimeout = keys.getAmbiguousTimeout();
                    if (ambiguousTimeout > 0L && this.peekCharacter(ambiguousTimeout) != -2) {
                        o = null;
                    }
                }
                if (o != null) {
                    this.lastBinding = this.opBuffer.toString();
                    this.opBuffer.setLength();
                    return o;
                }
            }
            else if (remaining[0] > 0) {
                final int cp = this.opBuffer.codePointAt();
                final String rem = this.opBuffer.substring();
                this.lastBinding = this.opBuffer.substring();
                o = ((cp >= 128) ? keys.getUnicode() : keys.getNomatch());
                this.opBuffer.setLength();
                this.opBuffer.append(rem);
                if (o != null) {
                    return o;
                }
            }
            if (!block && hasRead) {
                return null;
            }
            final int c = this.readCharacter();
            if (c == -1) {
                return null;
            }
            this.opBuffer.appendCodePoint(c);
            hasRead = true;
        }
    }
    
    public String readStringUntil(final String sequence) {
        final StringBuilder sb = new StringBuilder();
        if (!this.pushBackChar.isEmpty()) {
            final Deque<Integer> pushBackChar = this.pushBackChar;
            final StringBuilder obj = sb;
            Objects.requireNonNull(obj);
            pushBackChar.forEach(obj::appendCodePoint);
        }
        try {
            final char[] buf = new char[64];
            while (true) {
                final int idx = sb.indexOf(sequence, Math.max(0, sb.length() - buf.length - sequence.length()));
                if (idx >= 0) {
                    final String rem = sb.substring();
                    this.runMacro(rem);
                    return sb.substring();
                }
                final int l = this.reader.readBuffered(buf);
                if (l < 0) {
                    throw new ClosedException();
                }
                sb.append(buf, 0, l);
            }
        }
        catch (final ClosedException e) {
            throw new EndOfFileException(e);
        }
        catch (final IOException e2) {
            throw new IOError(e2);
        }
    }
    
    public int readCharacter() {
        if (!this.pushBackChar.isEmpty()) {
            return this.pushBackChar.pop();
        }
        try {
            int c = -2;
            int s = 0;
            while (c == -2) {
                c = this.reader.read(100L);
                if (c >= 0 && Character.isHighSurrogate((char)c)) {
                    s = c;
                    c = -2;
                }
            }
            return (s != 0) ? Character.toCodePoint((char)s, (char)c) : c;
        }
        catch (final ClosedException e) {
            throw new EndOfFileException(e);
        }
        catch (final IOException e2) {
            throw new IOError(e2);
        }
    }
    
    public int readCharacterBuffered() {
        try {
            if (this.pushBackChar.isEmpty()) {
                final char[] buf = new char[32];
                final int l = this.reader.readBuffered(buf);
                if (l <= 0) {
                    return -1;
                }
                int s = 0;
                int i = 0;
                while (i < l) {
                    int c = buf[i++];
                    if (Character.isHighSurrogate((char)c)) {
                        s = c;
                        if (i >= l) {
                            break;
                        }
                        c = buf[i++];
                        this.pushBackChar.addLast(Character.toCodePoint((char)s, (char)c));
                    }
                    else {
                        s = 0;
                        this.pushBackChar.addLast(c);
                    }
                }
                if (s != 0) {
                    final int c2 = this.reader.read();
                    if (c2 < 0) {
                        return -1;
                    }
                    this.pushBackChar.addLast(Character.toCodePoint((char)s, (char)c2));
                }
            }
            return this.pushBackChar.pop();
        }
        catch (final ClosedException e) {
            throw new EndOfFileException(e);
        }
        catch (final IOException e2) {
            throw new IOError(e2);
        }
    }
    
    public int peekCharacter(final long timeout) {
        if (!this.pushBackChar.isEmpty()) {
            return this.pushBackChar.peek();
        }
        try {
            return this.reader.peek(timeout);
        }
        catch (final IOException e) {
            throw new IOError(e);
        }
    }
    
    public void runMacro(final String macro) {
        final IntStream codePoints = macro.codePoints();
        final Deque<Integer> pushBackChar = this.pushBackChar;
        Objects.requireNonNull(pushBackChar);
        codePoints.forEachOrdered(pushBackChar::addLast);
    }
    
    public String getCurrentBuffer() {
        return this.opBuffer.toString();
    }
    
    public String getLastBinding() {
        return this.lastBinding;
    }
}
