package net.minecraft.client;

import java.util.function.Consumer;
import java.util.ListIterator;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.Iterator;
import com.google.common.collect.Lists;
import java.util.List;
import java.util.Optional;
import org.apache.commons.lang3.mutable.MutableInt;
import net.minecraft.network.chat.FormattedText;
import net.minecraft.network.chat.Style;
import org.apache.commons.lang3.mutable.MutableFloat;
import javax.annotation.Nullable;

public class StringSplitter {
    private final WidthProvider widthProvider;
    
    public StringSplitter(final WidthProvider f) {
        this.widthProvider = f;
    }
    
    public float stringWidth(@Nullable final String string) {
        if (string == null) {
            return 0.0f;
        }
        final MutableFloat mutableFloat3 = new MutableFloat();
        StringDecomposer.iterateFormatted(string, Style.EMPTY, (integer2, nb, integer4) -> {
            mutableFloat3.add(this.widthProvider.getWidth(integer4, nb));
            return true;
        });
        return mutableFloat3.floatValue();
    }
    
    public float stringWidth(final FormattedText mu) {
        final MutableFloat mutableFloat3 = new MutableFloat();
        StringDecomposer.iterateFormatted(mu, Style.EMPTY, (integer2, nb, integer4) -> {
            mutableFloat3.add(this.widthProvider.getWidth(integer4, nb));
            return true;
        });
        return mutableFloat3.floatValue();
    }
    
    public int plainIndexAtWidth(final String string, final int integer, final Style nb) {
        final WidthLimitedCharSink e5 = new WidthLimitedCharSink((float)integer);
        StringDecomposer.iterate(string, nb, e5);
        return e5.getPosition();
    }
    
    public String plainHeadByWidth(final String string, final int integer, final Style nb) {
        return string.substring(0, this.plainIndexAtWidth(string, integer, nb));
    }
    
    public String plainTailByWidth(final String string, final int integer, final Style nb) {
        final MutableFloat mutableFloat5 = new MutableFloat();
        final MutableInt mutableInt6 = new MutableInt(string.length());
        final MutableFloat mutableFloat6;
        final float float8;
        final MutableInt mutableInt7;
        StringDecomposer.iterateBackwards(string, nb, (integer4, nb, integer6) -> {
            float8 = mutableFloat6.addAndGet(this.widthProvider.getWidth(integer6, nb));
            if (float8 > integer) {
                return false;
            }
            else {
                mutableInt7.setValue(integer4);
                return true;
            }
        });
        return string.substring(mutableInt6.intValue());
    }
    
    @Nullable
    public Style componentStyleAtWidth(final FormattedText mu, final int integer) {
        final WidthLimitedCharSink e4 = new WidthLimitedCharSink((float)integer);
        return mu.<Style>visit((nb, string) -> StringDecomposer.iterateFormatted(string, nb, e4) ? Optional.<Style>empty() : Optional.<Style>of(nb), Style.EMPTY).orElse(null);
    }
    
    public FormattedText headByWidth(final FormattedText mu, final int integer, final Style nb) {
        final WidthLimitedCharSink e5 = new WidthLimitedCharSink((float)integer);
        return mu.<FormattedText>visit((FormattedText.StyledContentConsumer<FormattedText>)new FormattedText.StyledContentConsumer<FormattedText>() {
            private final ComponentCollector collector = new ComponentCollector();
            
            @Override
            public Optional<FormattedText> accept(final Style nb, final String string) {
                e5.resetPosition();
                if (!StringDecomposer.iterateFormatted(string, nb, e5)) {
                    final String string2 = string.substring(0, e5.getPosition());
                    if (!string2.isEmpty()) {
                        this.collector.append(FormattedText.of(string2, nb));
                    }
                    return Optional.<FormattedText>of(this.collector.getResultOrEmpty());
                }
                if (!string.isEmpty()) {
                    this.collector.append(FormattedText.of(string, nb));
                }
                return Optional.<FormattedText>empty();
            }
        }, nb).orElse(mu);
    }
    
    public static int getWordPosition(final String string, final int integer2, final int integer3, final boolean boolean4) {
        int integer4 = integer3;
        final boolean boolean5 = integer2 < 0;
        for (int integer5 = Math.abs(integer2), integer6 = 0; integer6 < integer5; ++integer6) {
            if (boolean5) {
                while (boolean4 && integer4 > 0 && (string.charAt(integer4 - 1) == ' ' || string.charAt(integer4 - 1) == '\n')) {
                    --integer4;
                }
                while (integer4 > 0 && string.charAt(integer4 - 1) != ' ' && string.charAt(integer4 - 1) != '\n') {
                    --integer4;
                }
            }
            else {
                final int integer7 = string.length();
                final int integer8 = string.indexOf(32, integer4);
                final int integer9 = string.indexOf(10, integer4);
                if (integer8 == -1 && integer9 == -1) {
                    integer4 = -1;
                }
                else if (integer8 != -1 && integer9 != -1) {
                    integer4 = Math.min(integer8, integer9);
                }
                else if (integer8 != -1) {
                    integer4 = integer8;
                }
                else {
                    integer4 = integer9;
                }
                if (integer4 == -1) {
                    integer4 = integer7;
                }
                else {
                    while (boolean4 && integer4 < integer7 && (string.charAt(integer4) == ' ' || string.charAt(integer4) == '\n')) {
                        ++integer4;
                    }
                }
            }
        }
        return integer4;
    }
    
    public void splitLines(final String string, final int integer, final Style nb, final boolean boolean4, final LinePosConsumer d) {
        int integer2 = 0;
        final int integer3 = string.length();
        Style nb2 = nb;
        while (integer2 < integer3) {
            final LineBreakFinder b10 = new LineBreakFinder((float)integer);
            final boolean boolean5 = StringDecomposer.iterateFormatted(string, integer2, nb2, nb, b10);
            if (boolean5) {
                d.accept(nb2, integer2, integer3);
                break;
            }
            final int integer4 = b10.getSplitPosition();
            final char character13 = string.charAt(integer4);
            final int integer5 = (character13 == '\n' || character13 == ' ') ? (integer4 + 1) : integer4;
            d.accept(nb2, integer2, boolean4 ? integer5 : integer4);
            integer2 = integer5;
            nb2 = b10.getSplitStyle();
        }
    }
    
    public List<FormattedText> splitLines(final String string, final int integer, final Style nb) {
        final List<FormattedText> list5 = Lists.newArrayList();
        this.splitLines(string, integer, nb, false, (nb, integer4, integer5) -> list5.add(FormattedText.of(string.substring(integer4, integer5), nb)));
        return list5;
    }
    
    public List<FormattedText> splitLines(final FormattedText mu, final int integer, final Style nb) {
        return this.splitLines(mu, integer, nb, null);
    }
    
    public List<FormattedText> splitLines(final FormattedText mu1, final int integer, final Style nb, @Nullable final FormattedText mu4) {
        final List<FormattedText> list6 = Lists.newArrayList();
        final List<LineComponent> list7 = Lists.newArrayList();
        final List<LineComponent> list8;
        mu1.visit((nb, string) -> {
            if (!string.isEmpty()) {
                list8.add(new LineComponent(string, nb));
            }
            return Optional.empty();
        }, nb);
        final FlatComponents a8 = new FlatComponents(list7);
        boolean boolean9 = true;
        boolean boolean10 = false;
        boolean boolean11 = false;
        while (boolean9) {
            boolean9 = false;
            final LineBreakFinder b12 = new LineBreakFinder((float)integer);
            for (final LineComponent c14 : a8.parts) {
                final boolean boolean12 = StringDecomposer.iterateFormatted(c14.contents, 0, c14.style, nb, b12);
                if (!boolean12) {
                    final int integer2 = b12.getSplitPosition();
                    final Style nb2 = b12.getSplitStyle();
                    final char character18 = a8.charAt(integer2);
                    final boolean boolean13 = character18 == '\n';
                    final boolean boolean14 = boolean13 || character18 == ' ';
                    boolean10 = boolean13;
                    final FormattedText mu5 = a8.splitAt(integer2, boolean14 ? 1 : 0, nb2);
                    list6.add(this.formattedLine(mu5, boolean11, mu4));
                    boolean11 = !boolean13;
                    boolean9 = true;
                    break;
                }
                b12.addToOffset(c14.contents.length());
            }
        }
        final FormattedText mu6 = a8.getRemainder();
        if (mu6 != null) {
            list6.add(this.formattedLine(mu6, boolean11, mu4));
        }
        else if (boolean10) {
            list6.add(FormattedText.EMPTY);
        }
        return list6;
    }
    
    private FormattedText formattedLine(final FormattedText mu1, final boolean boolean2, final FormattedText mu3) {
        if (boolean2 && mu3 != null) {
            return FormattedText.composite(mu3, mu1);
        }
        return mu1;
    }
    
    class WidthLimitedCharSink implements StringDecomposer.Output {
        private float maxWidth;
        private int position;
        
        public WidthLimitedCharSink(final float float2) {
            this.maxWidth = float2;
        }
        
        @Override
        public boolean onChar(final int integer1, final Style nb, final int integer3) {
            this.maxWidth -= StringSplitter.this.widthProvider.getWidth(integer3, nb);
            if (this.maxWidth >= 0.0f) {
                this.position = integer1 + Character.charCount(integer3);
                return true;
            }
            return false;
        }
        
        public int getPosition() {
            return this.position;
        }
        
        public void resetPosition() {
            this.position = 0;
        }
    }
    
    class LineBreakFinder implements StringDecomposer.Output {
        private final float maxWidth;
        private int lineBreak;
        private Style lineBreakStyle;
        private boolean hadNonZeroWidthChar;
        private float width;
        private int lastSpace;
        private Style lastSpaceStyle;
        private int nextChar;
        private int offset;
        
        public LineBreakFinder(final float float2) {
            this.lineBreak = -1;
            this.lineBreakStyle = Style.EMPTY;
            this.lastSpace = -1;
            this.lastSpaceStyle = Style.EMPTY;
            this.maxWidth = Math.max(float2, 1.0f);
        }
        
        @Override
        public boolean onChar(final int integer1, final Style nb, final int integer3) {
            final int integer4 = integer1 + this.offset;
            switch (integer3) {
                case 10: {
                    return this.finishIteration(integer4, nb);
                }
                case 32: {
                    this.lastSpace = integer4;
                    this.lastSpaceStyle = nb;
                    break;
                }
            }
            final float float6 = StringSplitter.this.widthProvider.getWidth(integer3, nb);
            this.width += float6;
            if (!this.hadNonZeroWidthChar || this.width <= this.maxWidth) {
                this.hadNonZeroWidthChar |= (float6 != 0.0f);
                this.nextChar = integer4 + Character.charCount(integer3);
                return true;
            }
            if (this.lastSpace != -1) {
                return this.finishIteration(this.lastSpace, this.lastSpaceStyle);
            }
            return this.finishIteration(integer4, nb);
        }
        
        private boolean finishIteration(final int integer, final Style nb) {
            this.lineBreak = integer;
            this.lineBreakStyle = nb;
            return false;
        }
        
        private boolean lineBreakFound() {
            return this.lineBreak != -1;
        }
        
        public int getSplitPosition() {
            return this.lineBreakFound() ? this.lineBreak : this.nextChar;
        }
        
        public Style getSplitStyle() {
            return this.lineBreakStyle;
        }
        
        public void addToOffset(final int integer) {
            this.offset += integer;
        }
    }
    
    static class LineComponent implements FormattedText {
        private final String contents;
        private final Style style;
        
        public LineComponent(final String string, final Style nb) {
            this.contents = string;
            this.style = nb;
        }
        
        @Override
        public <T> Optional<T> visit(final ContentConsumer<T> a) {
            return a.accept(this.contents);
        }
        
        @Override
        public <T> Optional<T> visit(final StyledContentConsumer<T> b, final Style nb) {
            return b.accept(this.style.applyTo(nb), this.contents);
        }
    }
    
    static class FlatComponents {
        private final List<LineComponent> parts;
        private String flatParts;
        
        public FlatComponents(final List<LineComponent> list) {
            this.parts = list;
            this.flatParts = list.stream().map(c -> c.contents).collect(Collectors.joining());
        }
        
        public char charAt(final int integer) {
            return this.flatParts.charAt(integer);
        }
        
        public FormattedText splitAt(final int integer1, final int integer2, final Style nb) {
            final ComponentCollector dlm5 = new ComponentCollector();
            final ListIterator<LineComponent> listIterator6 = this.parts.listIterator();
            int integer3 = integer1;
            boolean boolean8 = false;
            while (listIterator6.hasNext()) {
                final LineComponent c9 = listIterator6.next();
                final String string10 = c9.contents;
                final int integer4 = string10.length();
                if (!boolean8) {
                    if (integer3 > integer4) {
                        dlm5.append(c9);
                        listIterator6.remove();
                        integer3 -= integer4;
                    }
                    else {
                        final String string11 = string10.substring(0, integer3);
                        if (!string11.isEmpty()) {
                            dlm5.append(FormattedText.of(string11, c9.style));
                        }
                        integer3 += integer2;
                        boolean8 = true;
                    }
                }
                if (boolean8) {
                    if (integer3 > integer4) {
                        listIterator6.remove();
                        integer3 -= integer4;
                    }
                    else {
                        final String string11 = string10.substring(integer3);
                        if (string11.isEmpty()) {
                            listIterator6.remove();
                            break;
                        }
                        listIterator6.set(new LineComponent(string11, nb));
                        break;
                    }
                }
            }
            this.flatParts = this.flatParts.substring(integer1 + integer2);
            return dlm5.getResultOrEmpty();
        }
        
        @Nullable
        public FormattedText getRemainder() {
            final ComponentCollector dlm2 = new ComponentCollector();
            this.parts.forEach(dlm2::append);
            this.parts.clear();
            return dlm2.getResult();
        }
    }
    
    @FunctionalInterface
    public interface LinePosConsumer {
        void accept(final Style nb, final int integer2, final int integer3);
    }
    
    @FunctionalInterface
    public interface WidthProvider {
        float getWidth(final int integer, final Style nb);
    }
}
