/*
* Copyright 2014 Basis Technology Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.basistech.tclre;

import java.util.concurrent.ExecutionException;

import com.google.common.base.Throwables;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.ibm.icu.lang.UCharacter;
import com.ibm.icu.text.UnicodeSet;

import it.unimi.dsi.fastutil.objects.Object2CharMap;
import it.unimi.dsi.fastutil.objects.Object2CharOpenHashMap;

/**
 * Code from locale.
 */
final class Locale {

/* ASCII character-name table */

    static final Object2CharMap<String> CNAME;
    static final LoadingCache<String, UnicodeSet> KNOWN_SETS_CS = CacheBuilder.newBuilder()
            .build(
                    new CacheLoader<String, UnicodeSet>() {
                        public UnicodeSet load(String cclass) throws RegexException {
                            String className = "[:" + cclass + ":]";
                            try {
                                return new UnicodeSet(className, UnicodeSet.ADD_CASE_MAPPINGS);
                            }  catch (IllegalArgumentException iae) {
                                throw new RegexException("Invalid character class name " + cclass);
                            }
                        }
                    });
    static final LoadingCache<String, UnicodeSet> KNOWN_SETS_CI = CacheBuilder.newBuilder()
            .build(
                    new CacheLoader<String, UnicodeSet>() {
                        public UnicodeSet load(String cclass) throws RegexException {
                            String className = "[:" + cclass + ":]";
                            try {
                                return new UnicodeSet(className, 0);
                            }  catch (IllegalArgumentException iae) {
                                throw new RegexException("Invalid character class name " + cclass);
                            }
                        }
                    });

    //CHECKSTYLE:OFF
    static {
        CNAME = new Object2CharOpenHashMap<String>();

        CNAME.put("NUL", '\0');
        CNAME.put("SOH", '\001');
        CNAME.put("STX", '\002');
        CNAME.put("ETX", '\003');
        CNAME.put("EOT", '\004');
        CNAME.put("ENQ", '\005');
        CNAME.put("ACK", '\006');
        CNAME.put("BEL", '\007');
        CNAME.put("alert", '\007');
        CNAME.put("BS", '\010');
        CNAME.put("backspace", '\b');
        CNAME.put("HT", '\011');
        CNAME.put("tab", '\t');
        CNAME.put("LF", '\012');
        CNAME.put("newline", '\n');
        CNAME.put("VT", '\013');
        CNAME.put("vertical-tab", '\u000b');
        CNAME.put("FF", '\014');
        CNAME.put("form-feed", '\f');
        CNAME.put("CR", '\015');
        CNAME.put("carriage-return", '\r');
        CNAME.put("SO", '\016');
        CNAME.put("SI", '\017');
        CNAME.put("DLE", '\020');
        CNAME.put("DC1", '\021');
        CNAME.put("DC2", '\022');
        CNAME.put("DC3", '\023');
        CNAME.put("DC4", '\024');
        CNAME.put("NAK", '\025');
        CNAME.put("SYN", '\026');
        CNAME.put("ETB", '\027');
        CNAME.put("CAN", '\030');
        CNAME.put("EM", '\031');
        CNAME.put("SUB", '\032');
        CNAME.put("ESC", '\033');
        CNAME.put("IS4", '\034');
        CNAME.put("FS", '\034');
        CNAME.put("IS3", '\035');
        CNAME.put("GS", '\035');
        CNAME.put("IS2", '\036');
        CNAME.put("RS", '\036');
        CNAME.put("IS1", '\037');
        CNAME.put("US", '\037');
        CNAME.put("space", ' ');
        CNAME.put("exclamation-mark", '!');
        CNAME.put("quotation-mark", '"');
        CNAME.put("number-sign", '#');
        CNAME.put("dollar-sign", '$');
        CNAME.put("percent-sign", '%');
        CNAME.put("ampersand", '&');
        CNAME.put("apostrophe", '\'');
        CNAME.put("left-parenthesis", '(');
        CNAME.put("right-parenthesis", ')');
        CNAME.put("asterisk", '*');
        CNAME.put("plus-sign", '+');
        CNAME.put("comma", ',');
        CNAME.put("hyphen", '-');
        CNAME.put("hyphen-minus", '-');
        CNAME.put("period", '.');
        CNAME.put("full-stop", '.');
        CNAME.put("slash", '/');
        CNAME.put("solidus", '/');
        CNAME.put("zero", '0');
        CNAME.put("one", '1');
        CNAME.put("two", '2');
        CNAME.put("three", '3');
        CNAME.put("four", '4');
        CNAME.put("five", '5');
        CNAME.put("six", '6');
        CNAME.put("seven", '7');
        CNAME.put("eight", '8');
        CNAME.put("nine", '9');
        CNAME.put("colon", ':');
        CNAME.put("semicolon", ';');
        CNAME.put("less-than-sign", '<');
        CNAME.put("equals-sign", '=');
        CNAME.put("greater-than-sign", '>');
        CNAME.put("question-mark", '?');
        CNAME.put("commercial-at", '@');
        CNAME.put("left-square-bracket", '[');
        CNAME.put("backslash", '\\');
        CNAME.put("reverse-solidus", '\\');
        CNAME.put("right-square-bracket", ']');
        CNAME.put("circumflex", '^');
        CNAME.put("circumflex-accent", '^');
        CNAME.put("underscore", '_');
        CNAME.put("low-line", '_');
        CNAME.put("grave-accent", '`');
        CNAME.put("left-brace", '{');
        CNAME.put("left-curly-bracket", '{');
        CNAME.put("vertical-line", '|');
        CNAME.put("right-brace", '}');
        CNAME.put("right-curly-bracket", '}');
        CNAME.put("tilde", '~');
        CNAME.put("DEL", '\177');
    }
    //CHECKSTYLE:ON


    private Locale() {
        //
    }


    static int element(String what) throws RegexException {
        // this is a single character or the name of a character.
        // Surrogate pairs? we can't deal yet. This function
        // returns 'int' but no one upstairs is home yet.

        if (what.length() == 1) {
            return what.charAt(0);
        }

        if (CNAME.containsKey(what)) {
            return CNAME.get(what);
        }

        int uc = UCharacter.getCharFromName(what); // what if someone names a non-BMP char?
        if (uc != -1) {
            if (uc > 0xffff) {
                throw new RegexException(String.format(
                        "Limitation: cannot handle equivalence outside of the BMP: %s not possible.",
                        what));
            }
            return uc;
        }

        return -1;
    }

    /**
     * eclass - Because we have no MCCE support, this
     * just processing single characters.
     */
    static UnicodeSet eclass(char c, boolean cases) {

    /* otherwise, none */
        if (cases) {
            return allcases(c);
        } else {
            UnicodeSet set = new UnicodeSet();
            set.add(c);
            return set;
        }
    }

    /**
     * allcases - supply cvec for all case counterparts of a chr (including itself)
     * This is a shortcut, preferably an efficient one, for simple characters;
     * messy cases are done via range().
     */
    static UnicodeSet allcases(int c) {
        UnicodeSet set = new UnicodeSet();
        set.add(c);
        set.closeOver(UnicodeSet.ADD_CASE_MAPPINGS);
        return set;
    }

    /**
     * Return a UnicodeSet for a character class name.
     * It appears that the names that TCL accepts are also acceptable to ICU.
     *
     * @param cclassName class name
     * @param casefold whether to include casefolding
     * @return set
     */
    public static UnicodeSet cclass(String cclassName, boolean casefold) throws RegexException {
        try {
            if (casefold) {
                return KNOWN_SETS_CI.get(cclassName);
            } else {
                return KNOWN_SETS_CS.get(cclassName);
            }
        } catch (ExecutionException e) {
            Throwables.propagateIfInstanceOf(e.getCause(), RegexException.class);
            throw new RegexRuntimeException(e.getCause());
        }
    }
}
