/* FichothequeLib_Tools - Copyright (c) 2006-2019 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.fichotheque.tools.parsers;

import net.mapeadores.util.text.QuoteOptions;
import net.mapeadores.util.text.TypoOptions;


/**
 *
 * @author Vincent Calame
 */
public final class TypoParser {

    private TypoParser() {
    }

    public static String parseTypo(String s, TypoOptions typoOptions) {
        if (s == null) {
            return "";
        }
        int length = s.length();
        if (length == 0) {
            return "";
        }
        return parseTypo(s, 0, length, typoOptions);
    }

    public static String parseTypo(CharSequence charSequence, int start, int len, TypoOptions typoOptions) {
        QuoteOptions quoteOptions = typoOptions.getQuoteOptions();
        int stopIndex = start + len;
        StringBuilder buf = new StringBuilder();
        for (int i = start; i < stopIndex; i++) {
            int precedingIndices = i - start;
            int remainingIndices = stopIndex - i - 1;
            char carac = charSequence.charAt(i);
            switch (carac) {
                case ' ':
                    i = testSpace(charSequence, i, remainingIndices, buf);
                    break;
                case '~':
                    i = testNonBreakingSpace(charSequence, i, remainingIndices, buf);
                    break;
                case '.':
                    i = testDot(charSequence, i, remainingIndices, buf);
                    break;
                case '^':
                    i = testSuperscript(charSequence, i, remainingIndices, buf);
                    break;
                case '\\':
                    i = testAntiSlash(charSequence, i, remainingIndices, buf);
                    break;
                case '\u00AB': //«
                    i = testFollowingSpace(charSequence, i, remainingIndices, buf);
                    break;
                case '\'':
                    i = parseSimpleQuote(charSequence, i, remainingIndices, precedingIndices, buf);
                    break;
                case '"':
                    i = parseDoubleQuote(charSequence, i, remainingIndices, precedingIndices, buf, quoteOptions);
                    break;
                default:
                    buf.append(carac);
            }
        }
        return buf.toString();
    }

    private static int testNonBreakingSpace(CharSequence charSequence, int currentIndex, int remainingIndices, StringBuilder buf) {
        if (remainingIndices == 0) {
            buf.append('\u00A0'); // &nbsp;
            return currentIndex;
        }
        char nextChar = charSequence.charAt(currentIndex + 1);
        switch (nextChar) {
            case '?':
            case '!':
            case ';':
            case '\u00BB': // »
                buf.append('\u202F'); // &nnbsp;
                break;
            default:
                buf.append('\u00A0'); // &nbsp;
                break;
        }
        return currentIndex;
    }

    private static int testSpace(CharSequence charSequence, int currentIndex, int remainingIndices, StringBuilder buf) {
        if (remainingIndices == 0) {
            buf.append(' ');
            return currentIndex;
        }
        char nextChar = charSequence.charAt(currentIndex + 1);
        switch (nextChar) {
            case '?':
            case '!':
            case ';':
            case '\u00BB': // »
                buf.append('\u202F'); // &nnbsp;
                break;
            case ':':
                buf.append('\u00A0'); // &nbsp;
                break;
            default:
                buf.append(' ');
        }
        return currentIndex;
    }

    private static int testDot(CharSequence charSequence, int currentIndex, int remainingIndices, StringBuilder buf) {
        if (remainingIndices < 2) {
            buf.append('.');
            return currentIndex;
        }
        char nextChar = charSequence.charAt(currentIndex + 1);
        if (nextChar != '.') {
            buf.append('.');
            return currentIndex;
        }
        nextChar = charSequence.charAt(currentIndex + 2);
        if (nextChar != '.') {
            buf.append('.');
            buf.append('.');
            return currentIndex + 1;
        } else {
            buf.append('\u2026');
            return currentIndex + 2;
        }
    }

    private static int testSuperscript(CharSequence charSequence, int currentIndex, int remainingIndices, StringBuilder buf) {
        if (remainingIndices == 0) {
            buf.append('^');
            return currentIndex;
        }
        int p = 0;
        int stopIndex = currentIndex + remainingIndices + 1;
        for (int i = currentIndex + 1; i < stopIndex; i++) {
            char carac = charSequence.charAt(i);
            char superchar = getsSuperscriptForm(carac);
            if (superchar == '0') {
                break;
            } else {
                buf.append(superchar);
                p++;
            }
        }
        if (p == 0) {
            buf.append('^');
        }
        return currentIndex + p;
    }

    private static int testAntiSlash(CharSequence charSequence, int currentIndex, int remainingIndices, StringBuilder buf) {
        if (remainingIndices == 0) {
            buf.append('\\');
            return currentIndex;
        }
        char nextChar = charSequence.charAt(currentIndex + 1);
        if (nextChar == ']') {
            buf.append(']');
            return currentIndex + 1;
        } else if (nextChar == '}') {
            buf.append('}');
            return currentIndex + 1;
        } else if (nextChar == '\\') {
            buf.append('\\');
            return currentIndex + 1;
        } else if (nextChar == 'u') { //Unicode
            int min = currentIndex + 2 + Math.min(6, remainingIndices - 1);
            int p = 0;
            int result = 0;
            for (int i = currentIndex + 2; i < min; i++) {
                char carac = charSequence.charAt(i);
                int digit = Character.digit(carac, 16);
                if (digit >= 0) {
                    result = result * 16;
                    result = result + digit;
                    p++;
                } else {
                    break;
                }
            }
            if (p == 0) {
                buf.append("\\u");
            } else {
                buf.appendCodePoint(result);
            }
            return currentIndex + 1 + p;
        } else {
            buf.append('\\');
            return currentIndex;
        }
    }

    private static int testFollowingSpace(CharSequence charSequence, int currentIndex, int remainingIndices, StringBuilder buf) {
        char currentChar = charSequence.charAt(currentIndex);
        buf.append(currentChar);
        if (remainingIndices == 0) {
            return currentIndex;
        }
        char nextChar = charSequence.charAt(currentIndex + 1);
        if ((nextChar != ' ') && (nextChar != '~')) {
            return currentIndex;
        }
        switch (currentChar) {
            case '\u00AB': //«
                buf.append('\u202F'); // &nnbsp;
                break;
            default:
                buf.append('\u00A0'); // &nbsp;
        }
        return currentIndex + 1;
    }

    private static int parseSimpleQuote(CharSequence charSequence, int currentIndex, int remainingIndices, int precedingIndices, StringBuilder buf) {
        if (remainingIndices == 0) {
            buf.append('\u2019');
            return currentIndex;
        }
        char next = charSequence.charAt(currentIndex + 1);
        if (precedingIndices == 0) {
            if (next == '\'') {
                buf.append('\u201C');
                return currentIndex + 1;
            } else {
                buf.append('\u2018');
                return currentIndex;
            }
        }
        int lastIndex = currentIndex + remainingIndices;
        char previous = charSequence.charAt(currentIndex - 1);
        if (Character.isLetterOrDigit(previous)) {
            if (next == '\'') {
                buf.append('\u201D');
                currentIndex++;
            } else {
                buf.append('\u2019');
            }
        } else if (Character.isLetterOrDigit(next)) {
            buf.append('\u2018');
        } else if (next == '\'') {
            if ((currentIndex + 1) == lastIndex) {
                buf.append('\u201D');
            } else {
                char nextOfNext = charSequence.charAt(currentIndex + 2);
                if (Character.isLetterOrDigit(nextOfNext)) {
                    buf.append('\u201C');
                } else if (Character.isWhitespace(nextOfNext)) {
                    if (Character.isWhitespace(previous)) {
                        buf.append('\'');
                        buf.append('\'');
                    } else {
                        buf.append('\u201D');
                    }
                } else if (Character.isWhitespace(previous)) {
                    buf.append('\u201C');
                } else {
                    buf.append('\'');
                    buf.append('\'');
                }
            }
            currentIndex++;
        } else if (Character.isWhitespace(next)) {
            if (Character.isWhitespace(previous)) {
                buf.append('\'');
            } else {
                buf.append('\u2019');
            }
        } else if (Character.isWhitespace(previous)) {
            buf.append('\u2018');
        } else {
            buf.append('\'');
        }
        return currentIndex;
    }

    private static int parseDoubleQuote(CharSequence charSequence, int currentIndex, int remainingIndices, int precedingIndices, StringBuilder buf, QuoteOptions quoteOptions) {
        if (remainingIndices == 0) {
            appendClosingQuote(buf, quoteOptions);
            return currentIndex;
        }
        if (precedingIndices == 0) {
            appendOpeningQuote(buf, quoteOptions);
            return currentIndex;
        }
        char next = charSequence.charAt(currentIndex + 1);
        char previous = charSequence.charAt(currentIndex - 1);
        if (Character.isLetterOrDigit(next)) {
            if (Character.isLetterOrDigit(previous)) {
                buf.append('\"');
            } else {
                appendOpeningQuote(buf, quoteOptions);
            }
        } else if (Character.isLetterOrDigit(previous)) {
            appendClosingQuote(buf, quoteOptions);
        } else if (Character.isWhitespace(next)) {
            if (Character.isWhitespace(previous)) {
                buf.append('\"');
            } else {
                appendClosingQuote(buf, quoteOptions);
            }
        } else if (Character.isWhitespace(previous)) {
            appendOpeningQuote(buf, quoteOptions);
        } else {
            buf.append('\"');
        }
        return currentIndex;
    }

    private static void appendOpeningQuote(StringBuilder buf, QuoteOptions quoteOptions) {
        buf.append(quoteOptions.getOpeningQuote());
        if (quoteOptions.isWithNonBreakingSpaces()) {
            buf.append('\u202F');
        }
    }

    private static void appendClosingQuote(StringBuilder buf, QuoteOptions quoteOptions) {
        if (quoteOptions.isWithNonBreakingSpaces()) {
            buf.append('\u202F');
        }
        buf.append(quoteOptions.getClosingQuote());
    }

    public static boolean hasSuperscriptForm(char c) {
        switch (c) {
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
            case 'i':
            case '+':
            case '-':
            case '=':
            case '(':
            case ')':
            case 'n':
                return true;
            default:
                return false;
        }
    }

    public static char getsSuperscriptForm(char c) {
        switch (c) {
            case '0':
                return '\u2070';
            case '1':
                return '\u00B9';
            case '2':
                return '\u00B2';
            case '3':
                return '\u00B3';
            case '4':
                return '\u2074';
            case '5':
                return '\u2075';
            case '6':
                return '\u2076';
            case '7':
                return '\u2077';
            case '8':
                return '\u2078';
            case '9':
                return '\u2079';
            case 'i':
                return '\u2071';
            case '+':
                return '\u207A';
            case '-':
                return '\u207B';
            case '=':
                return '\u207C';
            case '(':
                return '\u207D';
            case ')':
                return '\u207E';
            case 'n':
                return '\u207F';
            default:
                return '0';
        }
    }

}
