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

import java.util.*;

import org.apache.juneau.json.*;
import org.apache.juneau.marshall.*;
import org.apache.juneau.parser.*;
import org.apache.juneau.rest.annotation.*;
import org.apache.juneau.rest.client.*;
import org.apache.juneau.http.remote.*;
import org.apache.juneau.serializer.*;

/**
 * Creates a mocked interface against a REST resource class to use for creating test remote resource interfaces.
 *
 * <ul class='seealso'>
 * 	<li class='link'>{@doc juneau-rest-mock.MockRemote}
 * </ul>
 *
 * @param <T> The interface class.
 */
public class MockRemote<T> {

	private MockRest.Builder mrb;
	private RestClientBuilder rcb = RestClient.create().json();
	private final Class<T> intf;

	/**
	 * Constructor.
	 *
	 * @param intf
	 * 	The remote interface annotated with {@link Remote @Remote}.
	 * @param impl
	 * 	The REST implementation bean or bean class annotated with {@link Rest @Rest}.
	 * 	<br>If a class, it must have a no-arg constructor.
	 */
	protected MockRemote(Class<T> intf, Object impl) {
		this.intf = intf;
		mrb = MockRest.create(impl);
	}

	/**
	 * Create a new builder using the specified remote resource interface and REST implementation bean or bean class.
	 *
	 * <p>
	 * Uses {@link JsonSerializer#DEFAULT} and {@link JsonParser#DEFAULT} for serializing and parsing by default.
	 *
	 *
	 * @param intf
	 * 	The remote interface annotated with {@link Remote @Remote}.
	 * @param impl
	 * 	The REST implementation bean or bean class annotated with {@link Rest @Rest}.
	 * 	<br>If a class, it must have a no-arg constructor.
	 * @return A new builder.
	 */
	public static <T> MockRemote<T> create(Class<T> intf, Object impl) {
		return new MockRemote<>(intf, impl);
	}

	/**
	 * Create a new builder using the specified remote resource interface and REST implementation bean or bean class.
	 *
	 * <p>
	 * Uses the serializer and parser defined on the specified marshall for serializing and parsing by default.
	 *
	 * @param intf
	 * 	The remote interface annotated with {@link Remote @Remote}.
	 * @param impl
	 * 	The REST implementation bean or bean class annotated with {@link Rest @Rest}.
	 * 	<br>If a class, it must have a no-arg constructor.
	 * @param m
	 * 	The marshall to use for serializing and parsing the HTTP bodies.
	 * @return A new builder.
	 */
	public static <T> MockRemote<T> create(Class<T> intf, Object impl, Marshall m) {
		return new MockRemote<>(intf, impl).marshall(m);
	}

	/**
	 * Create a new builder using the specified remote resource interface and REST implementation bean or bean class.
	 *
	 * <p>
	 * Uses the serializer and parser defined on the specified marshall for serializing and parsing by default.
	 *
	 * @param intf
	 * 	The remote interface annotated with {@link Remote @Remote}.
	 * @param impl
	 * 	The REST implementation bean or bean class annotated with {@link Rest @Rest}.
	 * 	<br>If a class, it must have a no-arg constructor.
	 * @param s
	 * 	The serializer to use for serializing request bodies.
	 * 	<br>Can be <jk>null</jk> to force no serializer to be used and no <c>Content-Type</c> header.
	 * @param p
	 * 	The parser to use for parsing response bodies.
	 * 	<br>Can be <jk>null</jk> to force no parser to be used and no <c>Accept</c> header.
	 * @return A new builder.
	 */
	public static <T> MockRemote<T> create(Class<T> intf, Object impl, Serializer s, Parser p) {
		return new MockRemote<>(intf, impl).serializer(s).parser(p);
	}

	/**
	 * Constructs a remote proxy interface based on the settings of this builder.
	 *
	 * @return A new remote proxy interface.
	 */
	public T build() {
		MockRest mr = mrb.build();
		return rcb.httpClientConnectionManager(new MockHttpClientConnectionManager(mr)).rootUrl("http://localhost").headers(mr.getHeaders()).build().getRemote(intf);
	}

	/**
	 * Convenience method for getting a remote resource interface.
	 *
	 * <p>
	 * Uses {@link JsonSerializer#DEFAULT} and {@link JsonParser#DEFAULT} for serializing and parsing by default.
	 *
	 * <p>
	 * Equivalent to calling the following:
	 * <p class='bcode w800'>
	 * 	MockRemoteResource.<jsf>create</jsf>(intf, impl).build();
	 * </p>
	 *
	 * @param intf
	 * 	The remote interface annotated with {@link Remote @Remote}.
	 * @param impl
	 * 	The REST implementation bean or bean class annotated with {@link Rest @Rest}.
	 * 	<br>If a class, it must have a no-arg constructor.
	 * @return A new proxy interface.
	 */
	public static <T> T build(Class<T> intf, Object impl) {
		return create(intf, impl).build();
	}

	/**
	 * Convenience method for getting a remote resource interface.
	 *
	 * <p>
	 * Uses the serializer and parser defined on the specified marshall for serializing and parsing by default.
	 *
	 * <p>
	 * Equivalent to calling the following:
	 * <p class='bcode w800'>
	 * 	MockRemoteResource.<jsf>create</jsf>(intf, impl).marshall(m).build();
	 * </p>
	 *
	 * @param intf
	 * 	The remote interface annotated with {@link Remote @Remote}.
	 * @param impl
	 * 	The REST implementation bean or bean class annotated with {@link Rest @Rest}.
	 * 	<br>If a class, it must have a no-arg constructor.
	 * @param m
	 * 	The marshall to use for serializing request bodies and parsing response bodies.
	 * 	<br>Can be <jk>null</jk> to force no serializer or parser to be used and no <c>Accept</c> or <c>Content-Type</c> header.
	 * @return A new proxy interface.
	 */
	public static <T> T build(Class<T> intf, Object impl, Marshall m) {
		return create(intf, impl).marshall(m).build();
	}

	/**
	 * Convenience method for getting a remote resource interface.
	 *
	 * <p>
	 * Uses the specified serializer and parser for serializing and parsing by default.
	 *
	 * <p>
	 * Equivalent to calling the following:
	 * <p class='bcode w800'>
	 * 	MockRemoteResource.<jsf>create</jsf>(intf, impl).serializer(s).parser(p).build();
	 * </p>
	 *
	 * @param intf
	 * 	The remote interface annotated with {@link Remote @Remote}.
	 * @param impl
	 * 	The REST implementation bean or bean class annotated with {@link Rest @Rest}.
	 * 	<br>If a class, it must have a no-arg constructor.
	 * @param s
	 * 	The serializer to use for serializing request bodies.
	 * 	<br>Can be <jk>null</jk> to force no serializer to be used and no <c>Content-Type</c> header.
	 * @param p
	 * 	The parser to use for parsing response bodies.
	 * 	<br>Can be <jk>null</jk> to force no parser to be used and no <c>Accept</c> header.
	 * @return A new proxy interface.
	 */
	public static <T> T build(Class<T> intf, Object impl, Serializer s, Parser p) {
		return create(intf, impl).serializer(s).parser(p).build();
	}

	/**
	 * Enable debug mode.
	 *
	 * @return This object (for method chaining).
	 */
	public MockRemote<T> debug() {
		mrb.debug();
		rcb.debug();
		return this;
	}

	/**
	 * Adds a header to every request.
	 *
	 * @param name The header name.
	 * @param value
	 * 	The header value.
	 * 	<br>Can be <jk>null</jk> (will be skipped).
	 * @return This object (for method chaining).
	 */
	public MockRemote<T> header(String name, Object value) {
		mrb.header(name, value);
		rcb.header(name, value);
		return this;
	}

	/**
	 * Adds the specified headers to every request.
	 *
	 * @param value
	 * 	The header values.
	 * 	<br>Can be <jk>null</jk> (existing values will be cleared).
	 * 	<br><jk>null</jk> null map values will be ignored.
	 * @return This object (for method chaining).
	 */
	public MockRemote<T> headers(Map<String,Object> value) {
		mrb.headers(value);
		rcb.headers(value);
		return this;
	}

	/**
	 * Adds an <c>Accept</c> header to every request.
	 *
	 * @param value The <c>Accept</c> header value.
	 * @return This object (for method chaining).
	 */
	public MockRemote<T> accept(String value) {
		mrb.accept(value);
		rcb.accept(value);
		return this;
	}

	/**
	 * Adds a <c>Content-Type</c> header to every request.
	 *
	 * @param value The <c>Content-Type</c> header value.
	 * @return This object (for method chaining).
	 */
	public MockRemote<T> contentType(String value) {
		mrb.contentType(value);
		rcb.contentType(value);
		return this;
	}

	/**
	 * Convenience method for setting <c>Accept</c> and <c>Content-Type</c> headers to <js>"application/json"</js>.
	 *
	 * @return This object (for method chaining).
	 */
	public MockRemote<T> json() {
		marshall(Json.DEFAULT);
		return this;
	}

	/**
	 * Convenience method for setting <c>Accept</c> and <c>Content-Type</c> headers to <js>"application/json+simple"</js>.
	 *
	 * @return This object (for method chaining).
	 */
	public MockRemote<T> simpleJson() {
		marshall(SimpleJson.DEFAULT);
		return this;
	}

	/**
	 * Convenience method for setting <c>Accept</c> and <c>Content-Type</c> headers to <js>"text/xml"</js>.
	 *
	 * @return This object (for method chaining).
	 */
	public MockRemote<T> xml() {
		marshall(Xml.DEFAULT);
		return this;
	}

	/**
	 * Convenience method for setting <c>Accept</c> and <c>Content-Type</c> headers to <js>"text/html"</js>.
	 *
	 * @return This object (for method chaining).
	 */
	public MockRemote<T> html() {
		marshall(Html.DEFAULT);
		return this;
	}

	/**
	 * Convenience method for setting <c>Accept</c> and <c>Content-Type</c> headers to <js>"text/plain"</js>.
	 *
	 * @return This object (for method chaining).
	 */
	public MockRemote<T> plainText() {
		marshall(PlainText.DEFAULT);
		return this;
	}

	/**
	 * Convenience method for setting <c>Accept</c> and <c>Content-Type</c> headers to <js>"octal/msgpack"</js>.
	 *
	 * @return This object (for method chaining).
	 */
	public MockRemote<T> msgpack() {
		marshall(MsgPack.DEFAULT);
		return this;
	}

	/**
	 * Convenience method for setting <c>Accept</c> and <c>Content-Type</c> headers to <js>"text/uon"</js>.
	 *
	 * @return This object (for method chaining).
	 */
	public MockRemote<T> uon() {
		marshall(Uon.DEFAULT);
		return this;
	}

	/**
	 * Convenience method for setting <c>Accept</c> and <c>Content-Type</c> headers to <js>"application/x-www-form-urlencoded"</js>.
	 *
	 * @return This object (for method chaining).
	 */
	public MockRemote<T> urlEnc() {
		marshall(UrlEncoding.DEFAULT);
		return this;
	}

	/**
	 * Convenience method for setting <c>Accept</c> and <c>Content-Type</c> headers to <js>"text/openapi"</js>.
	 *
	 * @return This object (for method chaining).
	 */
	public MockRemote<T> openapi() {
		marshall(OpenApi.DEFAULT);
		return this;
	}

	/**
	 * Associates the specified {@link Marshall} with this client.
	 *
	 * <p>
	 * This is shorthand for calling <c>serializer(x)</c> and <c>parser(x)</c> using the inner
	 * serializer and parser of the marshall object.
	 *
	 * @param value
	 * 	The marshall to use for serializing and parsing HTTP bodies.
	 * 	<br>Can be <jk>null</jk> (will remote the existing serializer/parser).
	 * @return This object (for method chaining).
	 */
	public MockRemote<T> marshall(Marshall value) {
		if (value != null)
			serializer(value.getSerializer()).parser(value.getParser());
		else
			serializer(null).parser(null);
		return this;
	}

	/**
	 * Associates the specified {@link Serializer} with this client.
	 *
	 * @param value
	 * 	The serializer to use for serializing HTTP bodies.
	 * 	<br>Can be <jk>null</jk> (will remote the existing serializer).
	 * @return This object (for method chaining).
	 */
	public MockRemote<T> serializer(Serializer value) {
		rcb.serializer(value);
		contentType(value == null ? null : value.getPrimaryMediaType().toString());
		return this;
	}

	/**
	 * Associates the specified {@link Parser} with this client.
	 *
	 * @param value
	 * 	The parser to use for parsing HTTP bodies.
	 * 	<br>Can be <jk>null</jk> (will remote the existing parser).
	 * @return This object (for method chaining).
	 */
	public MockRemote<T> parser(Parser value) {
		rcb.parser(value);
		accept(value == null ? null : value.getPrimaryMediaType().toString());
		return this;
	}
}
