/* UtilLib - Copyright (c) 2007-2025 Vincent Calame - Exemole
 * Logiciel libre donné sous triple licence :
 * 1) selon les termes de la CeCILL V2
 * 2) selon les termes de l’EUPL V.1.1
 * 3) selon les termes de la GNU GPLv3
 * Voir le fichier licences.txt
 */


package net.mapeadores.util.text;

import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;


/**
 *
 * @author Vincent Calame
 */
public class AccoladePattern {

    public final static ValueResolver DEFAULT_VALUERESOLVER = new NoneValueResolver();
    public final static AccoladeArgumentFactory DEFAULT_ACCOLADEARGUMENTFACTORY = new DefaultAccoladeArgumentFactory();
    private String patternString;
    PatternPart[] patternPartArray;
    int bufferSize = 0;
    int argumentCount = 0;

    private AccoladePattern() {
    }

    public AccoladePattern(String patternString) throws ParseException {
        this(patternString, null);
    }

    public AccoladePattern(String patternString, AccoladeArgumentFactory accoladeArgumentFactory) throws ParseException {
        this.patternString = patternString;
        if (accoladeArgumentFactory == null) {
            accoladeArgumentFactory = DEFAULT_ACCOLADEARGUMENTFACTORY;
        }
        List<PatternPart> list = buildPattern(patternString, accoladeArgumentFactory);
        int size = list.size();
        patternPartArray = new PatternPart[size];
        for (int i = 0; i < size; i++) {
            patternPartArray[i] = list.get(i);
        }
        if (bufferSize < 8) {
            bufferSize = 16;
        } else {
            bufferSize = bufferSize + (10 * argumentCount);
        }
    }

    public static AccoladePattern getNoArgumentPattern(String prefix, String rawString) {
        String text = prefix + rawString;
        AccoladePattern pattern = new AccoladePattern();
        PatternPart[] pp = new PatternPart[1];
        pp[0] = new PatternPart(text);
        pattern.patternPartArray = pp;
        pattern.patternString = rawString;
        pattern.bufferSize = text.length();
        return pattern;
    }

    public static List<Object> parseToList(String text) throws ParseException {
        AccoladePattern accoladePattern = new AccoladePattern(text);
        return accoladePattern.toList();

    }

    public int getArgumentCount() {
        return argumentCount;
    }

    public String format(ValueResolver valueResolver) {
        StringBuilder buf = new StringBuilder(bufferSize);
        for (int i = 0; i < patternPartArray.length; i++) {
            PatternPart patternPart = patternPartArray[i];
            if (patternPart.isArgumentPart()) {
                buf.append(valueResolver.getValue(patternPart.getPatternArgument()));
            } else {
                buf.append(patternPart.getText());
            }
        }
        return buf.toString();
    }

    public List<Object> toList() {
        List<Object> list = new ArrayList<Object>();
        for (PatternPart patternPart : patternPartArray) {
            if (patternPart.isArgumentPart()) {
                list.add(patternPart.patternArgument);
            } else {
                list.add(patternPart.text);
            }
        }
        return list;
    }

    private List<PatternPart> buildPattern(String strg_pattern, AccoladeArgumentFactory patternArgumentFactory) throws ParseException {
        boolean intoBrace = false;
        List<PatternPart> partsList = new ArrayList<PatternPart>();
        StringBuilder buf = new StringBuilder();
        boolean inQuote = false;
        for (int i = 0; i < strg_pattern.length(); ++i) {
            char ch = strg_pattern.charAt(i);
            if (inQuote) {
                if (ch == '\'') {
                    if (i + 1 < strg_pattern.length() && strg_pattern.charAt(i + 1) == '\'') {
                        buf.append(ch);
                        ++i;
                    } else {
                        inQuote = false;
                    }
                } else {
                    buf.append(ch);
                }
            } else if (!intoBrace) {
                if (ch == '\'') {
                    if (i + 1 < strg_pattern.length() && strg_pattern.charAt(i + 1) == '\'') {
                        buf.append(ch);
                        ++i;
                    } else {
                        inQuote = true;
                    }
                } else if (ch == '{') {
                    intoBrace = true;
                    buf = flushTextPatternPartBuffer(partsList, buf);
                } else if (ch == '}') {
                    throw new ParseException("Unmatched braces in the pattern at position", i);
                } else {
                    buf.append(ch);
                }
            } else {
                switch (ch) {
                    case '{':
                        throw new ParseException("Unmatched braces in the pattern at position", i);
                    case '}':
                        intoBrace = false;
                        buf = flushArgumentPatternPartBuffer(partsList, buf, patternArgumentFactory);
                        break;
                    default:
                        buf.append(ch);
                        break;
                }
            }
        }
        if (intoBrace) {
            throw new ParseException("Unmatched braces in the pattern (brace not closed).", strg_pattern.length() - 1);
        }
        flushTextPatternPartBuffer(partsList, buf);
        return partsList;
    }

    private StringBuilder flushArgumentPatternPartBuffer(List<PatternPart> list, StringBuilder buf, AccoladeArgumentFactory patternArgumentFactory) {
        String name = buf.toString().trim();
        String mode = null;
        int idx = name.indexOf('!');
        if (idx != -1) {
            mode = name.substring(idx + 1).trim();
            if (mode.length() == 0) {
                mode = null;
            }
            name = name.substring(0, idx).trim();
        }
        if (name.length() > 0) {
            AccoladeArgument patternArgument = patternArgumentFactory.getAccoladeArgument(name, mode);
            if (patternArgument != null) {
                list.add(newArgumentPatternPart(patternArgument));
            } else {
                list.add(new PatternPart(toString(name, mode)));
            }
        } else {
            list.add(new PatternPart(toString(name, mode)));
        }
        return new StringBuilder();
    }

    private StringBuilder flushTextPatternPartBuffer(List<PatternPart> list, StringBuilder buf) {
        String strg = buf.toString();
        bufferSize = bufferSize + strg.length();
        if (strg.length() > 0) {
            list.add(new PatternPart(strg));
        }
        return new StringBuilder();
    }

    /**
     * Retourne la chaîne qui a servit à construire l'instance de Pattern.
     */
    @Override
    public String toString() {
        return patternString;
    }

    public static String toString(String name, String mode) {
        StringBuilder buf = new StringBuilder();
        buf.append('{');
        buf.append(name);
        if (mode != null) {
            buf.append('!');
            buf.append(mode);
        }
        buf.append('}');
        return buf.toString();
    }

    public static String toString(AccoladeArgument patternArgument) {
        return toString(patternArgument.getName(), patternArgument.getMode());
    }

    private PatternPart newArgumentPatternPart(AccoladeArgument patternArgument) {
        argumentCount++;
        return new PatternPart(patternArgument);
    }


    static class PatternPart {

        AccoladeArgument patternArgument;
        String text;

        PatternPart(String text) {
            this.text = text;
        }

        PatternPart(AccoladeArgument patternArgument) {
            this.patternArgument = patternArgument;
        }

        public boolean isArgumentPart() {
            return (patternArgument != null);
        }

        public AccoladeArgument getPatternArgument() {
            return patternArgument;
        }

        public String getText() {
            return text;
        }

    }


    private static class NoneValueResolver implements ValueResolver {

        private NoneValueResolver() {
        }

        @Override
        public String getValue(AccoladeArgument accoladeArgument) {
            return AccoladePattern.toString(accoladeArgument);
        }

    }


    private static class DefaultAccoladeArgumentFactory implements AccoladeArgumentFactory {

        private DefaultAccoladeArgumentFactory() {
        }

        @Override
        public AccoladeArgument getAccoladeArgument(String name, String mode) {
            return new DefaultAccoladeArgument(name, mode);
        }

    }


    private static class DefaultAccoladeArgument implements AccoladeArgument {

        private final String name;
        private final String mode;

        private DefaultAccoladeArgument(String name, String mode) {
            this.name = name;
            this.mode = mode;
        }

        @Override
        public String getName() {
            return name;
        }

        @Override
        public String getMode() {
            return mode;
        }

    }

}
