/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.openjpa.lib.identifier;

import java.util.ArrayList;
import java.util.Locale;

import org.apache.openjpa.lib.conf.Configurable;
import org.apache.openjpa.lib.conf.Configuration;
import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.lib.util.StringUtil;

/**
 * Implementation class for the base identifier impl.
 *
 */
public class IdentifierUtilImpl implements IdentifierUtil, Configurable {

    private static final Localizer _loc = Localizer.forPackage
        (IdentifierUtilImpl.class);

    private IdentifierConfiguration _config = null;

    public IdentifierUtilImpl() {

    }

    public IdentifierUtilImpl(IdentifierConfiguration config) {
        _config = config;
    }

    @Override
    public void setIdentifierConfiguration(IdentifierConfiguration config) {
        _config = config;
    }

    @Override
    public IdentifierConfiguration getIdentifierConfiguration() {
        return _config;
    }

    @Override
    public String combineNames(String rule, String name1, String name2) {
        return combineNames(getNamingRule(rule), name1, name2);
    }

    @Override
    public String combineNames(String rule, String[] names) {
        return combineNames(getNamingRule(rule), names);
    }

    public String combineNames(IdentifierConfiguration config, String rule, String[] names) {
        return combineNames(config, getNamingRule(rule), names);
    }

    @Override
    public String combineNames(IdentifierRule rule,
        IdentifierRule[] rules, String[] names) {
        return combineNames(_config, rule, rules, names);
    }

    public String combineNames(IdentifierConfiguration config, IdentifierRule rule,
        IdentifierRule[] rules, String[] names) {
        boolean delimited = false;
        StringBuilder combined = null;
        for (int i = 0; i < names.length; i++) {
            String name = names[i];
            if (isDelimited(rules[i], name)) {
                delimited = true;
                name = removeDelimiters(config, rules[i], name);
            }
            if (i == 0) {
                combined = new StringBuilder(name);
            }
            else {
                combined.append(config.getIdentifierConcatenator()).append(name);
            }
        }

        if (delimited) {
            combined = new StringBuilder(delimit(config, rule, combined.toString()));
        }

        return combined.toString();
    }

    public String combineNames(IdentifierConfiguration config, IdentifierRule rule, String name1, String name2) {
        boolean delimit = false;
        if (isDelimited(rule, name1)) {
            name1 = removeDelimiters(config, rule, name1);
            delimit = true;
        }
        if (isDelimited(rule, name2)) {
            name2 = removeDelimiters(config, rule, name2);
            delimit = true;
        }
        String name = name1 + config.getIdentifierConcatenator() + name2;
        return delimit(config, rule, name, delimit);
    }

    public String combineNames(IdentifierConfiguration config, IdentifierRule namingRule, String[] names) {
        boolean delimited = false;
        String combined = null;
        for (int i = 0; i < names.length; i++) {
            String name = names[i];
            if (isDelimited(config, namingRule, name)) {
                delimited = true;
                name = removeDelimiters(config, namingRule, name);
            }
            if (i == 0) {
                combined = name;
            }
            else {
                combined = combined + config.getIdentifierConcatenator() + name;
            }
        }
        if (delimited) {
            combined = delimit(config, namingRule, combined);
        }

        return combined;
    }

    @Override
    public String appendNames(IdentifierRule rule, String name1, String name2) {
        if (isDelimited(rule, name1)) {
            name1 = removeDelimiters(rule, name1);
        }
        if (isDelimited(rule, name2)) {
            name2 = removeDelimiters(rule, name2);
        }
        if (name1 == null) {
            name1 = IdentifierUtil.EMPTY;
        }
        if (name2 == null) {
            name2 = IdentifierUtil.EMPTY;
        }
        String name = name1 + name2;
        return delimit(rule, name);
    }

    /**
     * Joins multiple names together using the standard delimiting rules
     * ex. ( {"s", "t", "c"} --> "s"."t"."c" }
     */
    @Override
    public String joinNames(IdentifierRule[] rules, String[] names) {

        if (names == null || names.length == 0) {
            return null;
        }
        StringBuilder combinedName = new StringBuilder();
        for (int i = 0; i < names.length; i++) {
            combinedName.append(delimit(rules[i], names[i]));
            if (i < (names.length -1)) {
                combinedName.append(_config.getIdentifierDelimiter());
            }
        }
        return combinedName.toString();
    }

    @Override
    public String joinNames(String rule, String[] names) {
        return joinNames(_config, getNamingRule(rule), names, _config.getIdentifierDelimiter());
    }

    @Override
    public String joinNames(IdentifierRule rule, String[] names) {
        return joinNames(_config, rule, names, _config.getIdentifierDelimiter());
    }

    @Override
    public String joinNames(IdentifierRule rule, String[] names, String delimiter) {
        return joinNames(_config, rule, names, delimiter);
    }

    @Override
    public String joinNames(String rule, String[] names, String delimiter) {
        return joinNames(_config, getNamingRule(rule), names, delimiter);
    }

    /**
     * Join names using a single naming rule and specified delimiter
     * @param rule
     * @param names
     */
    public String joinNames(IdentifierConfiguration config, IdentifierRule rule, String[] names, String delimiter) {

        if (names == null || names.length == 0) {
            return null;
        }
        StringBuilder combinedName = new StringBuilder();
        for (int i = 0; i < names.length; i++) {
            if (names[i] != null) {
                combinedName.append(delimit(config, rule, names[i], false));
                if (i < (names.length -1)) {
                    combinedName.append(delimiter);
                }
            }
        }
        return combinedName.toString();
    }

    public String[] splitName(IdentifierConfiguration config, IdentifierRule nrule, String name) {
        return splitName(nrule, name, config.getIdentifierDelimiter());
    }

    @Override
    public String[] splitName(IdentifierRule nrule, String name) {
        return splitName(nrule, name, _config.getIdentifierDelimiter());
    }


    /**
     * Splits names using single naming rule and appropriate separators
     * @param name  the multi-value name
     * @return individual components of the name
     *         ex. schema.table --> { schema, table }
     */
    @Override
    public String[] splitName(IdentifierRule nrule, String name, String nameDelim) {
        if (!canSplit(nrule, name, nameDelim) || StringUtil.isEmpty(name)) {
            return new String[] {name};
        }
        // "schema"."table"
        // "sch.ma"."table"
        // "sch""ma".table

        // Split names by object delimiter not between name delimiters
        ArrayList<String> names = new ArrayList<>(2);
        String pname = name;

        // for each name
        int ndLen = nameDelim.length();
        while (!StringUtil.isEmpty(name)) {
            pname = splitNameCharDelimiters(name, nameDelim);
            names.add(pname);
            if ((pname.length() + ndLen) >= name.length()) {
                break;
            }
            name = name.substring(pname.length() + ndLen);
        }
        return names.toArray(new String[names.size()]);
    }

    /**
     * Gets the first part of a name when single character delimiters are
     * in use.
     * @param pname
     */
    private String splitNameCharDelimiters(String name, String nameDelim) {
        StringBuilder sname = new StringBuilder("");
        char ld = _config.getLeadingDelimiter().charAt(0);
        char td = _config.getTrailingDelimiter().charAt(0);
        char nd = nameDelim.charAt(0);
        int dlvl = 0;
        boolean wasLd = false;
        for (int i = 0; i < name.length(); i++) {
            char c = name.charAt(i);
            if (c == ld) {
                // Handle case where delimiters are the same
                if (td == ld && wasLd) {
                    dlvl--;
                    wasLd = false;
                } else {
                    wasLd = true;
                    dlvl++;
                }
            } else if (c == td) {
                dlvl--;
            } else if (c == nd) {
                if (dlvl == 0  && sname.length() > 0) {
                    return sname.toString();
                }
            }
            sname.append(c);
        }
        return sname.toString();
    }

    /**
     * Returns whether a name is double quoted
     */
    public static boolean isDoubleQuoted(String name) {
        if (name == null || name.length() < 3) {
            return false;
        }
        return name.startsWith(DOUBLE_QUOTE) &&
               name.endsWith(DOUBLE_QUOTE);
    }

    public String delimit(IdentifierConfiguration config, IdentifierRule rule, String name) {
        return delimit(config, rule, name, false);
    }


    @Override
    public String delimit(IdentifierRule rule, String name) {
        return delimit(_config, rule, name, false);
    }

    @Override
    public String delimit(IdentifierRule rule, String name, boolean force) {
        return delimit(_config, rule, name, force);
    }

    public String delimit(IdentifierConfiguration config, IdentifierRule rule, String name, boolean force) {
        if (!rule.getCanDelimit() || StringUtil.isEmpty(name)) {
            return name;
        }

        if ((force && !isDelimited(config, rule, name)) || requiresDelimiters(config, rule, name)) {
            return config.getLeadingDelimiter() + name + config.getTrailingDelimiter();
        }
        return name;
    }

    @Override
    public boolean isDelimited(IdentifierRule rule, String name) {
        return isDelimited(_config, rule, name);
    }

    public boolean isDelimited(IdentifierConfiguration config, IdentifierRule rule, String name) {
         if (name == null || name.length() < 3) {
            return false;
        }
        return name.startsWith(config.getLeadingDelimiter()) &&
            name.endsWith(config.getTrailingDelimiter());
    }

    @Override
    public String removeDelimiters(IdentifierConfiguration config, String rule,
        String name) {
        return removeDelimiters(_config, getNamingRule(rule), name, _config.getLeadingDelimiter(),
            _config.getTrailingDelimiter());
    }

    @Override
    public String removeDelimiters(IdentifierRule rule, String name) {
        return removeDelimiters(_config, rule, name, _config.getLeadingDelimiter(),
            _config.getTrailingDelimiter());
    }

    public String removeDelimiters(IdentifierConfiguration config, IdentifierRule rule, String name) {
        return removeDelimiters(config, rule, name, _config.getLeadingDelimiter(),
            _config.getTrailingDelimiter());
    }

    @Override
    public boolean requiresDelimiters(IdentifierRule rule, String name) {
        return requiresDelimiters(_config, rule, name);
    }

    public boolean requiresDelimiters(IdentifierConfiguration config, IdentifierRule rule, String name) {
        if (rule == null) {
            throw new IllegalArgumentException(_loc.get("no-rules-provided").getMessage());
        }
        if (rule.getCanDelimit() && !isDelimited(config, rule, name) && rule.requiresDelimiters(name)) {
            return true;
        }
        return false;
    }

    /**
     * Returns whether a name is considered a reserved word.
     */
    @Override
    public boolean isReservedWord(IdentifierRule rule, String name) {
        if (rule == null) {
            throw new IllegalArgumentException("Naming rule is null!");
        }
        if (rule.getReservedWords() == null) {
            return false;
        }
        if (!isDelimited(rule, name)) {
            name = name.toUpperCase(Locale.ENGLISH);
        }
        return rule.getReservedWords().contains(name);
    }

    @Override
    public boolean isReservedWord(String rule, String name) {
        return isReservedWord(_config.getIdentifierRule(rule), name);
    }


    protected String removeDelimiters(IdentifierConfiguration config, IdentifierRule rule, String name, String leading,
        String trailing) {
        if (name == null) {
            return null;
        }
        if (isDelimited(config, rule, name)) {
            String id = name.substring(leading.length(),
                (name.length() - trailing.length()));
            return id;
        }
        return name;
    }

    @Override
    public String combineNames(String rule, String[] rules, String[] names) {
        return combineNames(getNamingRule(rule), getNamingRules(rules), names);
    }

    @Override
    public String truncateName(String rule, String name, int length) {
        return truncateName(getNamingRule(rule), name, length);
    }

    @Override
    public String truncateName(IdentifierRule namingRule, String name, int length) {
        String tName = name;
        boolean delimited = isDelimited(namingRule, name);
        if (delimited) {
            tName = removeDelimiters(namingRule, name);
        }
        if (tName.length() <= length) {
            return name;
        }
        tName = tName.substring(0, tName.length() - length);
        if (delimited) {
            tName = delimit(namingRule, tName);
        }
        return tName;
    }

    @Override
    public String delimit(String rule, String name) {
        return delimit(getNamingRule(rule), name);
    }

    @Override
    public String delimit(String rule, String name, boolean force) {
        return delimit(getNamingRule(rule), name, force);
    }

    @Override
    public boolean isDelimited(String rule, String name) {
        return isDelimited(getNamingRule(rule), name);
    }

    @Override
    public String removeDelimiters(String rule, String name) {
        return removeDelimiters(getNamingRule(rule), name);
    }

    @Override
    public boolean requiresDelimiters(String rule, String name) {
        return requiresDelimiters(getNamingRule(rule), name);
    }

    public String[] splitName(IdentifierConfiguration config, String rule, String name) {
        return splitName(config, getNamingRule(rule), name);
    }

    @Override
    public String[] splitName(String rule, String name) {
        return splitName(_config, getNamingRule(rule), name);
    }

    @Override
    public String joinNames(String[] rules, String[] names) {
        return joinNames(getNamingRules(rules), names);
    }

    private IdentifierRule getNamingRule(String rule) {
        return _config.getIdentifierRule(rule);
    }

    @Override
    public String appendNames(String rule, String name1, String name2) {
        return appendNames(getNamingRule(rule), name1, name2);
    }

    @Override
    public String removeHungarianNotation(IdentifierRule rule, String name) {
        boolean delimited = isDelimited(rule, name);
        if (delimited) {
            name = removeDelimiters(rule, name);
        }
        char[] chname = name.toCharArray();
        int newStart = 0;

        for (int i = 0; i < chname.length; i++) {
            if (Character.isUpperCase(chname[i]))
            {
                newStart = i;
                break;
            }
        }

        name = name.substring(newStart);
        if (delimited) {
            name = delimit(rule, name);
        }
        return name;
    }

    @Override
    public String removeHungarianNotation(String rule, String name) {
        return removeHungarianNotation(getNamingRule(rule), name);
    }

    @Override
    public String[] splitName(String nrule, String name, String nameDelim) {
        return splitName(getNamingRule(nrule), name, nameDelim);
    }

    @Override
    public String convert(IdentifierConfiguration config, String rule, String name) {
        // Already using same delimiter, no need to convert
        if (!needsConversion(config)) {
            return name;
        }
        // Otherwise, remove delimiters and add appropriate delimiters
        IdentifierRule orule = getIdentifierConfiguration().getIdentifierRule(rule);
        IdentifierRule nrule = config.getIdentifierRule(rule);
        boolean delimit = isDelimited(config, orule, name);
        if (delimit) {
            name = removeDelimiters(config, orule, name, config.getLeadingDelimiter(),
                config.getTrailingDelimiter());
            return delimit(getIdentifierConfiguration(), nrule, name, delimit);
        }
        return name;
    }

    @Override
    public String convertFull(IdentifierConfiguration config, String rule, String fullName) {
        if (!needsConversion(config)) {
            return fullName;
        }
        // Split
        String[] names = splitName(config, rule, fullName);
        // Convert
        for (int i = 0; i < names.length; i++) {
            names[i] = convert(config, rule, names[i]);
        }
        // If a single part name, return it.
        if (names.length == 1) {
            return names[0];
        }
        // Join if multiple names
        return joinNames(getIdentifierConfiguration(), config.getIdentifierRule(rule), names,
            getIdentifierConfiguration().getIdentifierDelimiter());
    }

    public String combineFull(IdentifierConfiguration config, String rule, String fullName) {
        if (!needsConversion(config)) {
            return fullName;
        }
        // Split
        String[] names = splitName(config, rule, fullName);
        // Convert
        for (int i = 0; i < names.length; i++) {
            names[i] = convert(config, rule, names[i]);
        }
        // Join
        return joinNames(config, config.getIdentifierRule(rule), names, config.getIdentifierDelimiter());
    }


    protected boolean needsConversion(IdentifierConfiguration config) {
    	return (config != getIdentifierConfiguration())
           && !(config.getConversionKey().equals(getIdentifierConfiguration().getConversionKey()));
    }

    private IdentifierRule[] getNamingRules(String[] rules) {
        IdentifierRule[] nrules = new IdentifierRule[rules.length];
        for (int i = 0; i < rules.length; i++) {
            nrules[i] = _config.getIdentifierRule(rules[i]);
        }
        return nrules;
    }

    @Override
    public void endConfiguration() {
    }

    @Override
    public void setConfiguration(Configuration conf) {
    }

    @Override
    public void startConfiguration() {
    }

    @Override
    public boolean canSplit(String rule, String name) {
        return canSplit(getNamingRule(rule), name, _config.getIdentifierDelimiter());
    }

    @Override
    public boolean canSplit(IdentifierRule rule, String name) {
        return canSplit(rule, name, _config.getIdentifierDelimiter());
    }

    @Override
    public boolean canSplit(String rule, String name, String delim) {
        return canSplit(getNamingRule(rule), name);
    }

    @Override
    public boolean canSplit(IdentifierRule rule, String name, String delim) {
        if (name == null || name.length() == 0) {
            return false;
        }
        return name.contains(delim);
    }

    @Override
    public String combineNames(IdentifierRule rule, String[] names) {
        return combineNames(_config, rule, names);
    }

    @Override
    public String combineNames(IdentifierRule rule, String name1, String name2) {
        return combineNames(_config, rule, name1, name2);
    }
}
