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

package org.jline.widget;

import java.util.List;
import java.util.ArrayList;
import org.jline.reader.Buffer;
import org.jline.reader.Reference;
import java.util.Iterator;
import org.jline.keymap.KeyMap;
import java.util.HashMap;
import org.jline.reader.LineReader;
import org.jline.reader.Binding;
import java.util.Map;

public class AutopairWidgets extends Widgets
{
    private static final Map<String, String> LBOUNDS;
    private static final Map<String, String> RBOUNDS;
    private final Map<String, String> pairs;
    private final Map<String, Binding> defaultBindings;
    private boolean enabled;
    
    public AutopairWidgets(final LineReader reader) {
        this(reader, false);
    }
    
    public AutopairWidgets(final LineReader reader, final boolean addCurlyBrackets) {
        super(reader);
        this.defaultBindings = new HashMap<String, Binding>();
        (this.pairs = new HashMap<String, String>()).put("`", "`");
        this.pairs.put("'", "'");
        this.pairs.put("\"", "\"");
        this.pairs.put("[", "]");
        this.pairs.put("(", ")");
        this.pairs.put(" ", " ");
        if (this.existsWidget("_autopair-insert")) {
            throw new IllegalStateException("AutopairWidgets already created!");
        }
        if (addCurlyBrackets) {
            this.pairs.put("{", "}");
        }
        this.addWidget("_autopair-insert", this::autopairInsert);
        this.addWidget("_autopair-close", this::autopairClose);
        this.addWidget("_autopair-backward-delete-char", this::autopairDelete);
        this.addWidget("autopair-toggle", this::toggleKeyBindings);
        final KeyMap<Binding> map = this.getKeyMap();
        for (final Map.Entry<String, String> p : this.pairs.entrySet()) {
            this.defaultBindings.put(p.getKey(), map.getBound(p.getKey()));
            if (!p.getKey().equals(p.getValue())) {
                this.defaultBindings.put(p.getValue(), map.getBound(p.getValue()));
            }
        }
    }
    
    public void enable() {
        if (!this.enabled) {
            this.toggle();
        }
    }
    
    public void disable() {
        if (this.enabled) {
            this.toggle();
        }
    }
    
    public boolean toggle() {
        final boolean before = this.enabled;
        this.toggleKeyBindings();
        return !before;
    }
    
    public boolean autopairInsert() {
        if (this.pairs.containsKey(this.lastBinding())) {
            if (this.canSkip(this.lastBinding())) {
                this.callWidget("forward-char");
            }
            else if (this.canPair(this.lastBinding())) {
                this.callWidget("self-insert");
                this.putString(this.pairs.get(this.lastBinding()));
                this.callWidget("backward-char");
            }
            else {
                this.callWidget("self-insert");
            }
        }
        else {
            this.callWidget("self-insert");
        }
        return true;
    }
    
    public boolean autopairClose() {
        if (this.pairs.containsValue(this.lastBinding()) && this.currChar().equals(this.lastBinding())) {
            this.callWidget("forward-char");
        }
        else {
            this.callWidget("self-insert");
        }
        return true;
    }
    
    public boolean autopairDelete() {
        if (this.pairs.containsKey(this.prevChar()) && this.pairs.get(this.prevChar()).equals(this.currChar()) && this.canDelete(this.prevChar())) {
            this.callWidget("delete-char");
        }
        this.callWidget("backward-delete-char");
        return true;
    }
    
    public boolean toggleKeyBindings() {
        if (this.enabled) {
            this.defaultBindings();
        }
        else {
            this.customBindings();
        }
        return this.enabled;
    }
    
    private void customBindings() {
        final boolean ttActive = this.tailtipEnabled();
        if (ttActive) {
            this.callWidget("tailtip-toggle");
        }
        final KeyMap<Binding> map = this.getKeyMap();
        for (final Map.Entry<String, String> p : this.pairs.entrySet()) {
            map.bind(new Reference("_autopair-insert"), p.getKey());
            if (!p.getKey().equals(p.getValue())) {
                map.bind(new Reference("_autopair-close"), p.getValue());
            }
        }
        this.aliasWidget("_autopair-backward-delete-char", "backward-delete-char");
        if (ttActive) {
            this.callWidget("tailtip-toggle");
        }
        this.enabled = true;
    }
    
    private void defaultBindings() {
        final KeyMap<Binding> map = this.getKeyMap();
        for (final Map.Entry<String, String> p : this.pairs.entrySet()) {
            map.bind(this.defaultBindings.get(p.getKey()), p.getKey());
            if (!p.getKey().equals(p.getValue())) {
                map.bind(this.defaultBindings.get(p.getValue()), p.getValue());
            }
        }
        this.aliasWidget(".backward-delete-char", "backward-delete-char");
        if (this.tailtipEnabled()) {
            this.callWidget("tailtip-toggle");
            this.callWidget("tailtip-toggle");
        }
        this.enabled = false;
    }
    
    private boolean tailtipEnabled() {
        return this.getWidget("accept-line").equals("_tailtip-accept-line");
    }
    
    private boolean canPair(final String d) {
        return this.balanced(d) && !this.nexToBoundary(d) && (!d.equals(" ") || (!this.prevChar().equals(" ") && !this.currChar().equals(" ")));
    }
    
    private boolean canSkip(final String d) {
        return this.pairs.get(d).equals(d) && d.charAt(0) != ' ' && this.currChar().equals(d) && this.balanced(d);
    }
    
    private boolean canDelete(final String d) {
        return this.balanced(d);
    }
    
    private boolean balanced(final String d) {
        boolean out = false;
        final Buffer buf = this.buffer();
        final String lbuf = buf.upToCursor();
        final String rbuf = buf.substring(lbuf.length());
        final String regx1 = this.pairs.get(d).equals(d) ? d : ("\\" + d);
        final String regx2 = this.pairs.get(d).equals(d) ? this.pairs.get(d) : ("\\" + this.pairs.get(d));
        final int llen = lbuf.length() - lbuf.replaceAll(regx1, "").length();
        final int rlen = rbuf.length() - rbuf.replaceAll(regx2, "").length();
        if (llen == 0 && rlen == 0) {
            out = true;
        }
        else if (d.charAt(0) == ' ') {
            out = true;
        }
        else if (this.pairs.get(d).equals(d)) {
            if (llen == rlen || (llen + rlen) % 2 == 0) {
                out = true;
            }
        }
        else {
            final int l2len = lbuf.length() - lbuf.replaceAll(regx2, "").length();
            final int r2len = rbuf.length() - rbuf.replaceAll(regx1, "").length();
            int ltotal = llen - l2len;
            final int rtotal = rlen - r2len;
            if (ltotal < 0) {
                ltotal = 0;
            }
            if (ltotal >= rtotal) {
                out = true;
            }
        }
        return out;
    }
    
    private boolean boundary(final String lb, final String rb) {
        return (lb.length() > 0 && this.prevChar().matches(lb)) || (rb.length() > 0 && this.currChar().matches(rb));
    }
    
    private boolean nexToBoundary(final String d) {
        final List<String> bk = new ArrayList<String>();
        bk.add("all");
        if (d.matches("['\"`]")) {
            bk.add("quotes");
        }
        else if (d.matches("[{\\[(<]")) {
            bk.add("braces");
        }
        else if (d.charAt(0) == ' ') {
            bk.add("spaces");
        }
        if (AutopairWidgets.LBOUNDS.containsKey(d) && AutopairWidgets.RBOUNDS.containsKey(d)) {
            bk.add(d);
        }
        for (final String k : bk) {
            if (this.boundary(AutopairWidgets.LBOUNDS.get(k), AutopairWidgets.RBOUNDS.get(k))) {
                return true;
            }
        }
        return false;
    }
    
    static {
        (LBOUNDS = new HashMap<String, String>()).put("all", "[.:/\\!]");
        AutopairWidgets.LBOUNDS.put("quotes", "[\\]})a-zA-Z0-9]");
        AutopairWidgets.LBOUNDS.put("spaces", "[^{(\\[]");
        AutopairWidgets.LBOUNDS.put("braces", "");
        AutopairWidgets.LBOUNDS.put("`", "`");
        AutopairWidgets.LBOUNDS.put("\"", "\"");
        AutopairWidgets.LBOUNDS.put("'", "'");
        (RBOUNDS = new HashMap<String, String>()).put("all", "[\\[{(<,.:?/%$!a-zA-Z0-9]");
        AutopairWidgets.RBOUNDS.put("quotes", "[a-zA-Z0-9]");
        AutopairWidgets.RBOUNDS.put("spaces", "[^\\]})]");
        AutopairWidgets.RBOUNDS.put("braces", "");
        AutopairWidgets.RBOUNDS.put("`", "");
        AutopairWidgets.RBOUNDS.put("\"", "");
        AutopairWidgets.RBOUNDS.put("'", "");
    }
}
