/*
 * Copyright (c) 2006-2013 Rogério Liesenfeld
 * This file is subject to the terms of the MIT license (see LICENSE.txt).
 */
package mockit.internal.expectations.mocking;

import java.util.*;
import java.util.regex.*;

import mockit.external.asm4.*;

final class MockingConfiguration
{
   private final List<RegexMockFilter> filtersToApply;
   private final boolean desiredFilterResultWhenMatching;

   MockingConfiguration(String[] filters, boolean desiredFilterResultWhenMatching)
   {
      filtersToApply = parseMockFilters(filters);
      this.desiredFilterResultWhenMatching = desiredFilterResultWhenMatching;
   }

   private List<RegexMockFilter> parseMockFilters(String[] mockFilters)
   {
      List<RegexMockFilter> filters = new ArrayList<RegexMockFilter>(mockFilters.length);

      for (String mockFilter : mockFilters) {
         filters.add(new RegexMockFilter(mockFilter));
      }

      return filters;
   }

   boolean matchesFilters(String name, String desc)
   {
      for (RegexMockFilter filter : filtersToApply) {
         if (filter.matches(name, desc)) {
            return desiredFilterResultWhenMatching;
         }
      }

      return !desiredFilterResultWhenMatching;
   }

   private static final class RegexMockFilter
   {
      private static final Pattern CONSTRUCTOR_NAME_REGEX = Pattern.compile("<init>");
      private static final String[] ANY_PARAMS = {};

      private final Pattern nameRegex;
      private final String[] paramTypeNames;

      private RegexMockFilter(String filter)
      {
         int lp = filter.indexOf('(');
         int rp = filter.indexOf(')');

         if (lp < 0 && rp >= 0 || lp >= 0 && lp >= rp) {
            throw new IllegalArgumentException("Invalid filter: " + filter);
         }

         if (lp == 0) {
            nameRegex = CONSTRUCTOR_NAME_REGEX;
         }
         else {
            nameRegex = Pattern.compile(lp < 0 ? filter : filter.substring(0, lp));
         }

         paramTypeNames = parseParameterTypes(filter, lp, rp);
      }

      private String[] parseParameterTypes(String filter, int lp, int rp)
      {
         if (lp < 0) {
            return ANY_PARAMS;
         }
         else if (lp == rp - 1) {
            return null;
         }
         else {
            String[] typeNames = filter.substring(lp + 1, rp).split(",");

            for (int i = 0; i < typeNames.length; i++) {
               typeNames[i] = typeNames[i].trim();
            }

            return typeNames;
         }
      }

      boolean matches(String name, String desc)
      {
         if (!nameRegex.matcher(name).matches()) {
            return false;
         }

         if (paramTypeNames == ANY_PARAMS) {
            return true;
         }
         else if (paramTypeNames == null) {
            return desc.charAt(1) == ')';
         }

         Type[] argTypes = Type.getArgumentTypes(desc);

         if (argTypes.length != paramTypeNames.length) {
            return false;
         }

         for (int i = 0; i < paramTypeNames.length; i++) {
            Type argType = argTypes[i];
            String paramTypeName = argType.getClassName();

            if (!paramTypeName.endsWith(paramTypeNames[i])) {
               return false;
            }
         }

         return true;
      }
   }
}
