// ***************************************************************************************************************************
// * 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.juneau.rest;

import static org.apache.juneau.internal.StringUtils.*;
import static org.apache.juneau.rest.RestCallLoggingDetail.*;

import java.util.logging.*;

import org.apache.juneau.*;
import org.apache.juneau.annotation.*;
import org.apache.juneau.json.*;
import org.apache.juneau.pojotools.*;

/**
 * Represents a logging rule used for determine how detailed requests should be logged at.
 */
public class RestCallLoggerRule {

	private final Matcher codeMatcher;
	private final Matcher exceptionMatcher;
	private final boolean debugOnly;
	private final Level level;
	private final Enablement disabled;
	private final RestCallLoggingDetail req, res;

	/**
	 * Constructor.
	 *
	 * @param b Builder
	 */
	RestCallLoggerRule(Builder b) {
		this.codeMatcher = isEmpty(b.codes) || "*".equals(b.codes) ? null : NumberMatcherFactory.DEFAULT.create(b.codes);
		this.exceptionMatcher = isEmpty(b.exceptions) ? null : StringMatcherFactory.DEFAULT.create(b.exceptions);
		boolean verbose = b.verbose == null ? false : b.verbose;
		this.disabled = b.disabled;
		this.debugOnly = b.debugOnly == null ? false : b.debugOnly;
		this.level = b.level;
		this.req = verbose ? LONG : b.req != null ? b.req : SHORT;
		this.res = verbose ? LONG : b.res != null ? b.res : SHORT;
	}

	/**
	 * Creates a new builder for this object.
	 *
	 * @return A new builder for this object.
	 */
	public static Builder create() {
		return new Builder();
	}

	/**
	 * Builder class for this object.
	 */
	@Bean(fluentSetters=true)
	public static class Builder {
		String codes, exceptions;
		Boolean verbose, debugOnly;
		Enablement disabled;
		Level level;
		RestCallLoggingDetail req, res;

		/**
		 * The code ranges that this logging rule applies to.
		 *
		 * <p>
		 * See {@link NumberMatcherFactory} for format of values.
		 *
		 * <p>
		 * <js>"*"</js> can be used to represent all values.
		 *
		 * @param value
		 * 	The new value for this property.
		 * 	<br>Can be <jk>null</jk> or an empty string.
		 * @return This object (for method chaining).
		 */
		public Builder codes(String value) {
			this.codes = value;
			String c = emptyIfNull(trim(codes));
			if (c.endsWith("-"))
				codes += "999";
			else if (c.startsWith("-"))
				codes = "0" + codes;
			return this;
		}

		/**
		 * The exception naming pattern that this rule applies to.
		 *
		 * <p>
		 * See {@link StringMatcherFactory} for format of values.
		 *
		 * <p>
		 * The pattern can be against either the fully-qualified or simple class name of the exception.
		 *
		 * @param value
		 * 	The new value for this property.
		 * 	<br>Can be <jk>null</jk> or an empty string.
		 * @return This object (for method chaining).
		 */
		public Builder exceptions(String value) {
			this.exceptions = value;
			return this;
		}

		/**
		 * Shortcut for specifying {@link RestCallLoggingDetail#LONG} for {@link #req(RestCallLoggingDetail)} and {@link #res(RestCallLoggingDetail)}.
		 *
		 * @param value
		 * 	The new value for this property.
		 * 	<br>Can be <jk>null</jk>.
		 * @return This object (for method chaining).
		 */
		public Builder verbose(Boolean value) {
			this.verbose = value;
			return this;
		}

		/**
		 * Shortcut for calling <c>verbose(<jk>true</jk>);</c>
		 *
		 * @return This object (for method chaining).
		 */
		public Builder verbose() {
			return this.verbose(true);
		}

		/**
		 * Shortcut for specifying {@link Level#OFF} for {@link #level(Level)}.
		 *
		 * @param value
		 * 	The new value for this property.
		 * 	<br>Can be <jk>null</jk>.
		 * @return This object (for method chaining).
		 */
		public Builder disabled(Enablement value) {
			this.disabled = value;
			return this;
		}

		/**
		 * Shortcut for calling <c>disabled(<jk>true</jk>);</c>
		 *
		 * @return This object (for method chaining).
		 */
		public Builder disabled() {
			return this.disabled(Enablement.TRUE);
		}

		/**
		 * This match only applies when debug is enabled on the request.
		 *
		 * @param value The new value for this property.
		 * @return This object (for method chaining).
		 */
		public Builder debugOnly(Boolean value) {
			this.debugOnly = value;
			return this;
		}

		/**
		 * Shortcut for calling <c>debugOnly(<jk>true</jk>);</c>
		 *
		 * @return This object (for method chaining).
		 */
		public Builder debugOnly() {
			return this.debugOnly(true);
		}

		/**
		 * The level of detail to log on a request.
		 *
		 * <p>
		 * The default value is {@link RestCallLoggingDetail#SHORT}.
		 *
		 * @param value
		 * 	The new value for this property.
		 * 	<br>Can be <jk>null</jk>
		 * @return This object (for method chaining).
		 */
		public Builder req(RestCallLoggingDetail value) {
			this.req = value;
			return this;
		}

		/**
		 * The level of detail to log on a response.
		 *
		 * <p>
		 * The default value is {@link RestCallLoggingDetail#SHORT}.
		 *
		 * @param value
		 * 	The new value for this property.
		 * 	<br>Can be <jk>null</jk>
		 * @return This object (for method chaining).
		 */
		public Builder res(RestCallLoggingDetail value) {
			this.res = value;
			return this;
		}

		/**
		 * The logging level to use for logging the request/response.
		 *
		 * <p>
		 * The default value is {@link Level#INFO}.
		 *
		 * @param value
		 * 	The new value for this property.
		 * 	<br>Can be <jk>null</jk>
		 * @return This object (for method chaining).
		 */
		public Builder level(Level value) {
			this.level = value;
			return this;
		}

		/**
		 * Instantiates a new {@link RestCallLoggerRule} object using the settings in this builder.
		 *
		 * @return A new {@link RestCallLoggerRule} object.
		 */
		public RestCallLoggerRule build() {
			return new RestCallLoggerRule(this);
		}
	}

	/**
	 * Returns <jk>true</jk> if this rule matches the specified parameters.
	 *
	 * @param statusCode The HTTP response status code.
	 * @param debug Whether debug is enabled on the request.
	 * @param e Exception thrown while handling the request.
	 * @return <jk>true</jk> if this rule matches the specified parameters.
	 */
	public boolean matches(int statusCode, boolean debug, Throwable e) {
		if (debugOnly && ! debug)
			return false;
		if (exceptionMatcher != null) {
			if (e == null)
				return false;
			Class<?> c = e.getClass();
			if (! (exceptionMatcher.matches(null, c.getName()) || exceptionMatcher.matches(null, c.getSimpleName())))
				return false;
		}
		if (codeMatcher == null || codeMatcher.matches(null, statusCode))
			return true;
		return false;
	}

	/**
	 * Returns the detail level for HTTP requests.
	 *
	 * @return the detail level for HTTP requests.
	 */
	public RestCallLoggingDetail getReqDetail() {
		return req;
	}

	/**
	 * Returns the detail level for HTTP responses.
	 *
	 * @return the detail level for HTTP responses.
	 */
	public RestCallLoggingDetail getResDetail() {
		return res;
	}

	/**
	 * Returns the log level.
	 *
	 * @return the log level.
	 */
	public Level getLevel() {
		return level;
	}

	/**
	 * Returns the disabled flag.
	 *
	 * @return the disabled flag.
	 */
	public Enablement getDisabled() {
		return disabled;
	}

	private ObjectMap toMap() {
		return new DefaultFilteringObjectMap()
			.append("codes", codeMatcher)
			.append("exceptions", exceptionMatcher)
			.append("debugOnly", debugOnly)
			.append("level", level)
			.append("req", req == SHORT ? null : req)
			.append("res", res == SHORT ? null : res);
	}

	@Override /* Object */
	public String toString() {
		return SimpleJsonSerializer.DEFAULT.toString(toMap());
	}
}
