/**
 * Copyright 2007 Google Inc.
 *
 * 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.tonicsystems.jarjar;

import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.ArrayList;
import java.util.Arrays;

class Wildcard
{
    private static Pattern dstar = Pattern.compile("\\*\\*");
    private static Pattern star  = Pattern.compile("\\*");
    private static Pattern estar = Pattern.compile("\\+\\??\\)\\Z");

    private final Pattern pattern;
    private final int count;
    private final ArrayList<Object> parts = new ArrayList<Object>(16); // kept for debugging
    private final String[] strings;
    private final int[] refs;

    public Wildcard(String pattern, String result) {
        if (pattern.equals("**"))
            throw new IllegalArgumentException("'**' is not a valid pattern");
        if (!checkIdentifierChars(pattern, "/*"))
            throw new IllegalArgumentException("Not a valid package pattern: " + pattern);
        if (pattern.indexOf("***") >= 0)
            throw new IllegalArgumentException("The sequence '***' is invalid in a package pattern");
        
        String regex = pattern;
        regex = replaceAllLiteral(dstar, regex, "(.+?)");
        regex = replaceAllLiteral(star, regex, "([^/]+)");
        regex = replaceAllLiteral(estar, regex, "*)");
        this.pattern = Pattern.compile("\\A" + regex + "\\Z");
        this.count = this.pattern.matcher("foo").groupCount();

        // TODO: check for illegal characters
        char[] chars = result.toCharArray();
        int max = 0;
        for (int i = 0, mark = 0, state = 0, len = chars.length; i < len + 1; i++) {
            char ch = (i == len) ? '@' : chars[i];
            if (state == 0) {
                if (ch == '@') {
                    parts.add(new String(chars, mark, i - mark));
                    mark = i + 1;
                    state = 1;
                }
            } else {
                switch (ch) {
                case '0': case '1': case '2': case '3': case '4':
                case '5': case '6': case '7': case '8': case '9':
                    break;
                default:
                    if (i == mark)
                        throw new IllegalArgumentException("Backslash not followed by a digit");
                    int n = Integer.parseInt(new String(chars, mark, i - mark));
                    if (n > max)
                        max = n;
                    parts.add(new Integer(n));
                    mark = i--;
                    state = 0;
                }
            }
        }
        int size = parts.size();
        strings = new String[size];
        refs = new int[size];
        Arrays.fill(refs, -1);
        for (int i = 0; i < size; i++) {
            Object v = parts.get(i);
            if (v instanceof String) {
                strings[i] = ((String)v).replace('.', '/');
            } else {
                refs[i] = ((Integer)v).intValue();
            }
        }
        if (count < max)
            throw new IllegalArgumentException("Result includes impossible placeholder \"@" + max + "\": " + result);
        // System.err.println(this);
    }

    public boolean matches(String value) {
        return getMatcher(value) != null;
    }

    public String replace(String value) {
        Matcher matcher = getMatcher(value);
        if (matcher != null) {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < strings.length; i++)
                sb.append((refs[i] >= 0) ? matcher.group(refs[i]) : strings[i]);
            return sb.toString();
        }
        return null;
    }

    private Matcher getMatcher(String value) {
        Matcher matcher = pattern.matcher(value);
        if (matcher.matches() && checkIdentifierChars(value, "/"))
            return matcher;
        return null;
    }

    private static boolean checkIdentifierChars(String expr, String extra) {
      // package-info violates the spec for Java Identifiers.
      // Nevertheless, expressions that end with this string are still legal.
      // See 7.4.1.1 of the Java language spec for discussion.
      if (expr.endsWith("package-info")) {
          expr = expr.substring(0, expr.length() - "package-info".length());
      }
      for (int i = 0, len = expr.length(); i < len; i++) {
          char c = expr.charAt(i);
          if (extra.indexOf(c) >= 0)
              continue;
          if (!Character.isJavaIdentifierPart(c))
              return false;
      }
      return true;
    }

    private static String replaceAllLiteral(Pattern pattern, String value, String replace) {
        replace = replace.replaceAll("([$\\\\])", "\\\\$0");
        return pattern.matcher(value).replaceAll(replace);
    }

    public String toString() {
        return "Wildcard{pattern=" + pattern + ",parts=" + parts + "}";
    }
}
