/*
 * Copyright 2015, The OpenNMS Group
 *
 * 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 org.opennms.newts.cassandra.search;

import java.util.List;
import java.util.regex.Pattern;

import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;

/**
 * A splitter that supports escaping the separator.
 *
 * @author jwhite
 */
public class EscapableResourceIdSplitter implements ResourceIdSplitter {
    private static final Pattern MATCH_COLON = Pattern.compile(":");
    private static final Pattern MATCH_BACKSLASH = Pattern.compile("\\\\");
    private static final Pattern MATCH_ESCAPED_COLON = Pattern.compile("\\\\:");
    private static final Pattern MATCH_ESCAPED_BACKSLASH = Pattern.compile("\\\\\\\\");

    /**
     * Splits a resource id into a list of elements.
     *
     * Elements in the resource id are delimited by colons (:).
     * Colons can be escaped by a backslash (\:).
     * Backslashes are escaped when paired (\\).
     *
     * For example, the following resources id:
     *    a:b\::c\\:d
     * will result in the following elements:
     *    a, b:, c\, d
     *
     * @param id the resource id
     * @return a list of elements
     */
    @Override
    public List<String> splitIdIntoElements(String id) {
        Preconditions.checkNotNull(id, "id argument");
        List<String> elements = Lists.newArrayList();

        int startOfNextElement = 0;
        int numConsecutiveEscapeCharacters = 0;
        char[] idChars = id.toCharArray();
        for (int i = 0; i < idChars.length; i++) {
            // If we hit a separator, only treat it as such if it is preceded by an even number of escape characters
            if(idChars[i] == SEPARATOR && numConsecutiveEscapeCharacters % 2 == 0) {
                maybeAddSanitizedElement(new String(idChars, startOfNextElement, i-startOfNextElement), elements);
                startOfNextElement = i+1;
            }

            if (idChars[i] == '\\') {
                numConsecutiveEscapeCharacters++;
            } else {
                numConsecutiveEscapeCharacters = 0;
            }
        }

        maybeAddSanitizedElement(new String(idChars, startOfNextElement, idChars.length - startOfNextElement), elements);
        return elements;
    }

    /**
     * Maybe adds to element to the list after being sanitized.
     */
    private static void maybeAddSanitizedElement(final String element, final List<String> elements) {
        // Trim the element and skip it when empty
        String sanitizedElement = element.trim();
        if (sanitizedElement.length() == 0) {
            return;
        }
        // \: -> :
        sanitizedElement = MATCH_ESCAPED_COLON.matcher(sanitizedElement).replaceAll(":");
        // \\ -> \
        sanitizedElement = MATCH_ESCAPED_BACKSLASH.matcher(sanitizedElement).replaceAll("\\\\");
        elements.add(sanitizedElement);
    }

    /**
     * Joins a list of elements into a resource id, escaping
     * special characters if required.
     *
     * See {@link #splitIdIntoElements(String)} for details.
     *
     * @param elements a list of elements
     * @return the resource id
     */
    @Override
    public String joinElementsToId(List<String> elements) {
        Preconditions.checkNotNull(elements, "elements argument");
        StringBuilder sb = new StringBuilder();
        for (String el : elements) {
            // Skip null elements
            if (el == null) {
                continue;
            }

            // Trim the elements and skip any empty ones
            String trimmedEl = el.trim();
            if (trimmedEl.length() < 1)  {
                continue;
            }

            // \ -> \\
            trimmedEl = MATCH_BACKSLASH.matcher(trimmedEl).replaceAll("\\\\\\\\");
            // : -> \:
            trimmedEl = MATCH_COLON.matcher(trimmedEl).replaceAll("\\\\:");

            if (sb.length() > 0) {
                sb.append(SEPARATOR);
            }
            sb.append(trimmedEl);
        }
        return sb.toString();
    }
}
