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

package org.jline.utils;

import java.util.Iterator;
import java.util.ArrayList;
import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.Arrays;

public class AttributedStringBuilder extends AttributedCharSequence implements Appendable
{
    private char[] buffer;
    private long[] style;
    private int length;
    private TabStops tabs;
    private char[] altIn;
    private char[] altOut;
    private boolean inAltCharset;
    private int lastLineLength;
    private AttributedStyle current;
    
    public static AttributedString append(final CharSequence... strings) {
        final AttributedStringBuilder sb = new AttributedStringBuilder();
        for (final CharSequence s : strings) {
            sb.append(s);
        }
        return sb.toAttributedString();
    }
    
    public AttributedStringBuilder() {
        this(64);
    }
    
    public AttributedStringBuilder(final int capacity) {
        this.tabs = new TabStops(0);
        this.lastLineLength = 0;
        this.current = AttributedStyle.DEFAULT;
        this.buffer = new char[capacity];
        this.style = new long[capacity];
        this.length = 0;
    }
    
    @Override
    public int length() {
        return this.length;
    }
    
    @Override
    public char charAt(final int index) {
        return this.buffer[index];
    }
    
    @Override
    public AttributedStyle styleAt(final int index) {
        return new AttributedStyle(this.style[index], this.style[index]);
    }
    
    @Override
    long styleCodeAt(final int index) {
        return this.style[index];
    }
    
    @Override
    protected char[] buffer() {
        return this.buffer;
    }
    
    @Override
    protected int offset() {
        return 0;
    }
    
    @Override
    public AttributedString subSequence(final int start, final int end) {
        return new AttributedString(Arrays.copyOfRange(this.buffer, start, end), Arrays.copyOfRange(this.style, start, end), 0, end - start);
    }
    
    @Override
    public AttributedStringBuilder append(CharSequence csq) {
        if (csq == null) {
            csq = "null";
        }
        return this.append(new AttributedString(csq, this.current));
    }
    
    @Override
    public AttributedStringBuilder append(CharSequence csq, final int start, final int end) {
        if (csq == null) {
            csq = "null";
        }
        return this.append(csq.subSequence(start, end));
    }
    
    @Override
    public AttributedStringBuilder append(final char c) {
        return this.append(Character.toString(c));
    }
    
    public AttributedStringBuilder append(final char c, int repeat) {
        final AttributedString s = new AttributedString(Character.toString(c), this.current);
        while (repeat-- > 0) {
            this.append(s);
        }
        return this;
    }
    
    public AttributedStringBuilder append(final CharSequence csq, final AttributedStyle style) {
        return this.append(new AttributedString(csq, style));
    }
    
    public AttributedStringBuilder style(final AttributedStyle style) {
        this.current = style;
        return this;
    }
    
    public AttributedStringBuilder style(final Function<AttributedStyle, AttributedStyle> style) {
        this.current = style.apply(this.current);
        return this;
    }
    
    public AttributedStringBuilder styled(final Function<AttributedStyle, AttributedStyle> style, final CharSequence cs) {
        return this.styled(style, sb -> sb.append(cs));
    }
    
    public AttributedStringBuilder styled(final AttributedStyle style, final CharSequence cs) {
        return this.styled(s -> style, sb -> sb.append(cs));
    }
    
    public AttributedStringBuilder styled(final Function<AttributedStyle, AttributedStyle> style, final Consumer<AttributedStringBuilder> consumer) {
        final AttributedStyle prev = this.current;
        this.current = style.apply(prev);
        consumer.accept(this);
        this.current = prev;
        return this;
    }
    
    public AttributedStyle style() {
        return this.current;
    }
    
    public AttributedStringBuilder append(final AttributedString str) {
        return this.append((AttributedCharSequence)str, 0, str.length());
    }
    
    public AttributedStringBuilder append(final AttributedString str, final int start, final int end) {
        return this.append((AttributedCharSequence)str, start, end);
    }
    
    public AttributedStringBuilder append(final AttributedCharSequence str) {
        return this.append(str, 0, str.length());
    }
    
    public AttributedStringBuilder append(final AttributedCharSequence str, final int start, final int end) {
        this.ensureCapacity(this.length + end - start);
        for (int i = start; i < end; ++i) {
            final char c = str.charAt(i);
            final long s = (str.styleCodeAt(i) & ~this.current.getMask()) | this.current.getStyle();
            if (this.tabs.defined() && c == '\t') {
                this.insertTab(new AttributedStyle(s, 0L));
            }
            else {
                this.ensureCapacity(this.length + 1);
                this.buffer[this.length] = c;
                this.style[this.length] = s;
                if (c == '\n') {
                    this.lastLineLength = 0;
                }
                else {
                    ++this.lastLineLength;
                }
                ++this.length;
            }
        }
        return this;
    }
    
    protected void ensureCapacity(final int nl) {
        if (nl > this.buffer.length) {
            int s;
            for (s = Math.max(this.buffer.length, 1); s <= nl; s *= 2) {}
            this.buffer = Arrays.copyOf(this.buffer, s);
            this.style = Arrays.copyOf(this.style, s);
        }
    }
    
    public void appendAnsi(final String ansi) {
        this.ansiAppend(ansi);
    }
    
    public AttributedStringBuilder ansiAppend(final String ansi) {
        int ansiStart = 0;
        int ansiState = 0;
        this.ensureCapacity(this.length + ansi.length());
        for (int i = 0; i < ansi.length(); ++i) {
            char c = ansi.charAt(i);
            if (ansiState == 0 && c == '\u001b') {
                ++ansiState;
            }
            else if (ansiState == 1 && c == '[') {
                ++ansiState;
                ansiStart = i + 1;
            }
            else if (ansiState == 2) {
                if (c == 'm') {
                    final String[] params = ansi.substring(ansiStart, i).split(";");
                    for (int j = 0; j < params.length; ++j) {
                        final int ansiParam = params[j].isEmpty() ? 0 : Integer.parseInt(params[j]);
                        switch (ansiParam) {
                            case 0: {
                                this.current = AttributedStyle.DEFAULT;
                                break;
                            }
                            case 1: {
                                this.current = this.current.bold();
                                break;
                            }
                            case 2: {
                                this.current = this.current.faint();
                                break;
                            }
                            case 3: {
                                this.current = this.current.italic();
                                break;
                            }
                            case 4: {
                                this.current = this.current.underline();
                                break;
                            }
                            case 5: {
                                this.current = this.current.blink();
                                break;
                            }
                            case 7: {
                                this.current = this.current.inverse();
                                break;
                            }
                            case 8: {
                                this.current = this.current.conceal();
                                break;
                            }
                            case 9: {
                                this.current = this.current.crossedOut();
                                break;
                            }
                            case 22: {
                                this.current = this.current.boldOff().faintOff();
                                break;
                            }
                            case 23: {
                                this.current = this.current.italicOff();
                                break;
                            }
                            case 24: {
                                this.current = this.current.underlineOff();
                                break;
                            }
                            case 25: {
                                this.current = this.current.blinkOff();
                                break;
                            }
                            case 27: {
                                this.current = this.current.inverseOff();
                                break;
                            }
                            case 28: {
                                this.current = this.current.concealOff();
                                break;
                            }
                            case 29: {
                                this.current = this.current.crossedOutOff();
                                break;
                            }
                            case 30:
                            case 31:
                            case 32:
                            case 33:
                            case 34:
                            case 35:
                            case 36:
                            case 37: {
                                this.current = this.current.foreground(ansiParam - 30);
                                break;
                            }
                            case 39: {
                                this.current = this.current.foregroundOff();
                                break;
                            }
                            case 40:
                            case 41:
                            case 42:
                            case 43:
                            case 44:
                            case 45:
                            case 46:
                            case 47: {
                                this.current = this.current.background(ansiParam - 40);
                                break;
                            }
                            case 49: {
                                this.current = this.current.backgroundOff();
                                break;
                            }
                            case 38:
                            case 48: {
                                if (j + 1 < params.length) {
                                    final int ansiParam2 = Integer.parseInt(params[++j]);
                                    if (ansiParam2 == 2) {
                                        if (j + 3 < params.length) {
                                            final int r = Integer.parseInt(params[++j]);
                                            final int g = Integer.parseInt(params[++j]);
                                            final int b = Integer.parseInt(params[++j]);
                                            if (ansiParam == 38) {
                                                this.current = this.current.foreground(r, g, b);
                                            }
                                            else {
                                                this.current = this.current.background(r, g, b);
                                            }
                                        }
                                    }
                                    else if (ansiParam2 == 5 && j + 1 < params.length) {
                                        final int col = Integer.parseInt(params[++j]);
                                        if (ansiParam == 38) {
                                            this.current = this.current.foreground(col);
                                        }
                                        else {
                                            this.current = this.current.background(col);
                                        }
                                    }
                                    break;
                                }
                                break;
                            }
                            case 90:
                            case 91:
                            case 92:
                            case 93:
                            case 94:
                            case 95:
                            case 96:
                            case 97: {
                                this.current = this.current.foreground(ansiParam - 90 + 8);
                                break;
                            }
                            case 100:
                            case 101:
                            case 102:
                            case 103:
                            case 104:
                            case 105:
                            case 106:
                            case 107: {
                                this.current = this.current.background(ansiParam - 100 + 8);
                                break;
                            }
                        }
                    }
                    ansiState = 0;
                }
                else if ((c < '0' || c > '9') && c != ';') {
                    ansiState = 0;
                }
            }
            else {
                if (ansiState >= 1) {
                    this.ensureCapacity(this.length + 1);
                    this.buffer[this.length++] = '\u001b';
                    if (ansiState >= 2) {
                        this.ensureCapacity(this.length + 1);
                        this.buffer[this.length++] = '[';
                    }
                    ansiState = 0;
                }
                if (c == '\t' && this.tabs.defined()) {
                    this.insertTab(this.current);
                }
                else {
                    this.ensureCapacity(this.length + 1);
                    if (this.inAltCharset) {
                        switch (c) {
                            case 'j': {
                                c = '\u2518';
                                break;
                            }
                            case 'k': {
                                c = '\u2510';
                                break;
                            }
                            case 'l': {
                                c = '\u250c';
                                break;
                            }
                            case 'm': {
                                c = '\u2514';
                                break;
                            }
                            case 'n': {
                                c = '\u253c';
                                break;
                            }
                            case 'q': {
                                c = '\u2500';
                                break;
                            }
                            case 't': {
                                c = '\u251c';
                                break;
                            }
                            case 'u': {
                                c = '\u2524';
                                break;
                            }
                            case 'v': {
                                c = '\u2534';
                                break;
                            }
                            case 'w': {
                                c = '\u252c';
                                break;
                            }
                            case 'x': {
                                c = '\u2502';
                                break;
                            }
                        }
                    }
                    this.buffer[this.length] = c;
                    this.style[this.length] = this.current.getStyle();
                    if (c == '\n') {
                        this.lastLineLength = 0;
                    }
                    else {
                        ++this.lastLineLength;
                    }
                    ++this.length;
                    if (this.altIn != null && this.altOut != null) {
                        final char[] alt = this.inAltCharset ? this.altOut : this.altIn;
                        if (equals(this.buffer, this.length - alt.length, alt, 0, alt.length)) {
                            this.inAltCharset = !this.inAltCharset;
                            this.length -= alt.length;
                        }
                    }
                }
            }
        }
        return this;
    }
    
    private static boolean equals(final char[] a, final int aFromIndex, final char[] b, final int bFromIndex, final int length) {
        if (aFromIndex < 0 || bFromIndex < 0 || aFromIndex + length > a.length || bFromIndex + length > b.length) {
            return false;
        }
        for (int i = 0; i < length; ++i) {
            if (a[aFromIndex + i] != b[bFromIndex + i]) {
                return false;
            }
        }
        return true;
    }
    
    protected void insertTab(final AttributedStyle s) {
        final int nb = this.tabs.spaces(this.lastLineLength);
        this.ensureCapacity(this.length + nb);
        for (int i = 0; i < nb; ++i) {
            this.buffer[this.length] = ' ';
            this.style[this.length] = s.getStyle();
            ++this.length;
        }
        this.lastLineLength += nb;
    }
    
    public void setLength(final int l) {
        this.length = l;
    }
    
    public AttributedStringBuilder tabs(final int tabsize) {
        if (tabsize < 0) {
            throw new IllegalArgumentException("Tab size must be non negative");
        }
        return this.tabs(Arrays.asList(tabsize));
    }
    
    public AttributedStringBuilder tabs(final List<Integer> tabs) {
        if (this.length > 0) {
            throw new IllegalStateException("Cannot change tab size after appending text");
        }
        this.tabs = new TabStops(tabs);
        return this;
    }
    
    public AttributedStringBuilder altCharset(final String altIn, final String altOut) {
        if (this.length > 0) {
            throw new IllegalStateException("Cannot change alternative charset after appending text");
        }
        this.altIn = (char[])((altIn != null) ? altIn.toCharArray() : null);
        this.altOut = (char[])((altOut != null) ? altOut.toCharArray() : null);
        return this;
    }
    
    public AttributedStringBuilder styleMatches(final Pattern pattern, final AttributedStyle s) {
        final Matcher matcher = pattern.matcher(this);
        while (matcher.find()) {
            for (int i = matcher.start(); i < matcher.end(); ++i) {
                this.style[i] = ((this.style[i] & ~s.getMask()) | s.getStyle());
            }
        }
        return this;
    }
    
    public AttributedStringBuilder styleMatches(final Pattern pattern, final List<AttributedStyle> styles) {
        final Matcher matcher = pattern.matcher(this);
        while (matcher.find()) {
            for (int group = 0; group < matcher.groupCount(); ++group) {
                final AttributedStyle s = styles.get(group);
                for (int i = matcher.start(group + 1); i < matcher.end(group + 1); ++i) {
                    this.style[i] = ((this.style[i] & ~s.getMask()) | s.getStyle());
                }
            }
        }
        return this;
    }
    
    private static class TabStops
    {
        private List<Integer> tabs;
        private int lastStop;
        private int lastSize;
        
        public TabStops(final int tabs) {
            this.tabs = new ArrayList<Integer>();
            this.lastStop = 0;
            this.lastSize = 0;
            this.lastSize = tabs;
        }
        
        public TabStops(final List<Integer> tabs) {
            this.tabs = new ArrayList<Integer>();
            this.lastStop = 0;
            this.lastSize = 0;
            this.tabs = tabs;
            int p = 0;
            for (final int s : tabs) {
                if (s <= p) {
                    continue;
                }
                this.lastStop = s;
                this.lastSize = s - p;
                p = s;
            }
        }
        
        boolean defined() {
            return this.lastSize > 0;
        }
        
        int spaces(final int lastLineLength) {
            int out = 0;
            if (lastLineLength >= this.lastStop) {
                out = this.lastSize - (lastLineLength - this.lastStop) % this.lastSize;
            }
            else {
                for (final int s : this.tabs) {
                    if (s > lastLineLength) {
                        out = s - lastLineLength;
                        break;
                    }
                }
            }
            return out;
        }
    }
}
