/*
 * 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.flink.cep.pattern;

import org.apache.flink.util.Preconditions;

import java.util.EnumSet;
import java.util.Objects;

/**
 * A quantifier describing the Pattern. There are three main groups of {@link Quantifier}.
 *
 * <p><ol>
 *     <li>Single</li>
 *     <li>Looping</li>
 *     <li>Times</li>
 * </ol>
 *
 * <p>Each {@link Pattern} can be optional and have a {@link ConsumingStrategy}. Looping and Times also hava an
 * additional inner consuming strategy that is applied between accepted events in the pattern.
 */
public class Quantifier {

	private final EnumSet<QuantifierProperty> properties;

	private final ConsumingStrategy consumingStrategy;

	private ConsumingStrategy innerConsumingStrategy = ConsumingStrategy.SKIP_TILL_NEXT;

	private Quantifier(
			final ConsumingStrategy consumingStrategy,
			final QuantifierProperty first,
			final QuantifierProperty... rest) {
		this.properties = EnumSet.of(first, rest);
		this.consumingStrategy = consumingStrategy;
	}

	public static Quantifier one(final ConsumingStrategy consumingStrategy) {
		return new Quantifier(consumingStrategy, QuantifierProperty.SINGLE);
	}

	public static Quantifier looping(final ConsumingStrategy consumingStrategy) {
		return new Quantifier(consumingStrategy, QuantifierProperty.LOOPING);
	}

	public static Quantifier times(final ConsumingStrategy consumingStrategy) {
		return new Quantifier(consumingStrategy, QuantifierProperty.TIMES);
	}

	public boolean hasProperty(QuantifierProperty property) {
		return properties.contains(property);
	}

	public ConsumingStrategy getInnerConsumingStrategy() {
		return innerConsumingStrategy;
	}

	public ConsumingStrategy getConsumingStrategy() {
		return consumingStrategy;
	}

	private static void checkPattern(boolean condition, Object errorMessage) {
		if (!condition) {
			throw new MalformedPatternException(String.valueOf(errorMessage));
		}
	}

	public void combinations() {
		checkPattern(!hasProperty(QuantifierProperty.SINGLE), "Combinations not applicable to " + this + "!");
		checkPattern(innerConsumingStrategy != ConsumingStrategy.STRICT, "You can apply apply either combinations or consecutive, not both!");
		checkPattern(innerConsumingStrategy != ConsumingStrategy.SKIP_TILL_ANY, "Combinations already applied!");

		innerConsumingStrategy = ConsumingStrategy.SKIP_TILL_ANY;
	}

	public void consecutive() {
		checkPattern(hasProperty(QuantifierProperty.LOOPING) || hasProperty(QuantifierProperty.TIMES), "Combinations not applicable to " + this + "!");
		checkPattern(innerConsumingStrategy != ConsumingStrategy.SKIP_TILL_ANY, "You can apply apply either combinations or consecutive, not both!");
		checkPattern(innerConsumingStrategy != ConsumingStrategy.STRICT, "Combinations already applied!");

		innerConsumingStrategy = ConsumingStrategy.STRICT;
	}

	public void optional() {
		checkPattern(!hasProperty(QuantifierProperty.OPTIONAL), "Optional already applied!");
		checkPattern(!(consumingStrategy == ConsumingStrategy.NOT_NEXT ||
					consumingStrategy == ConsumingStrategy.NOT_FOLLOW), "NOT pattern cannot be optional");

		properties.add(Quantifier.QuantifierProperty.OPTIONAL);
	}

	public void greedy() {
		checkPattern(!(innerConsumingStrategy == ConsumingStrategy.SKIP_TILL_ANY),
			"Option not applicable to FollowedByAny pattern");
		checkPattern(!hasProperty(Quantifier.QuantifierProperty.SINGLE),
			"Option not applicable to singleton quantifier");

		properties.add(QuantifierProperty.GREEDY);
	}

	@Override
	public boolean equals(Object o) {
		if (this == o) {
			return true;
		}
		if (o == null || getClass() != o.getClass()) {
			return false;
		}
		Quantifier that = (Quantifier) o;
		return Objects.equals(properties, that.properties) &&
				consumingStrategy == that.consumingStrategy &&
				innerConsumingStrategy == that.innerConsumingStrategy;
	}

	@Override
	public int hashCode() {
		return Objects.hash(properties, consumingStrategy, innerConsumingStrategy);
	}

	@Override
	public String toString() {
		return "Quantifier{" +
			"properties=" + properties +
			", consumingStrategy=" + consumingStrategy +
			", innerConsumingStrategy=" + innerConsumingStrategy +
			'}';
	}

	/**
	 * Properties that a {@link Quantifier} can have. Not all combinations are valid.
	 */
	public enum QuantifierProperty {
		SINGLE,
		LOOPING,
		TIMES,
		OPTIONAL,
		GREEDY
	}

	/**
	 * Describes strategy for which events are matched in this {@link Pattern}. See docs for more info.
	 */
	public enum ConsumingStrategy {
		STRICT,
		SKIP_TILL_NEXT,
		SKIP_TILL_ANY,

		NOT_FOLLOW,
		NOT_NEXT
	}

	/**
	 * Describe the times this {@link Pattern} can occur.
	 */
	public static class Times {
		private final int from;
		private final int to;

		private Times(int from, int to) {
			Preconditions.checkArgument(from > 0, "The from should be a positive number greater than 0.");
			Preconditions.checkArgument(to >= from, "The to should be a number greater than or equal to from: " + from + ".");
			this.from = from;
			this.to = to;
		}

		public int getFrom() {
			return from;
		}

		public int getTo() {
			return to;
		}

		public static Times of(int from, int to) {
			return new Times(from, to);
		}

		public static Times of(int times) {
			return new Times(times, times);
		}

		@Override
		public boolean equals(Object o) {
			if (this == o) {
				return true;
			}
			if (o == null || getClass() != o.getClass()) {
				return false;
			}
			Times times = (Times) o;
			return from == times.from &&
				to == times.to;
		}

		@Override
		public int hashCode() {
			return Objects.hash(from, to);
		}
	}
}

@@ -2915,38 +2915,32 @@
  %22You can apply 
-apply 
 either combinati
@@ -3270,34 +3270,33 @@
 .TIMES), %22Co
-mbinations
+nsecutive
  not applica
@@ -3408,22 +3408,16 @@
 n apply 
-apply 
 either c
@@ -3523,34 +3523,33 @@
 .STRICT, %22Co
-mbinations
+nsecutive
  already app

fix typos in cep quantifier error message