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

import java.util.*;

import org.apache.juneau.html.*;
import org.apache.juneau.json.*;
import org.apache.juneau.serializer.*;
import org.apache.juneau.test.pojos.*;
import org.apache.juneau.uon.*;
import org.apache.juneau.urlencoding.*;
import org.apache.juneau.xml.*;
import org.junit.*;
import org.junit.runner.*;
import org.junit.runners.*;

/**
 * Exhaustive serialization tests DynaBean support.
 */
@RunWith(Parameterized.class)
@SuppressWarnings({"serial"})
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class MaxIndentTest {

	@Parameterized.Parameters
	public static Collection<Object[]> getParameters() {
		return Arrays.asList(new Object[][] {
			{ 	/* 0 */
				new Input(
					"List1dOfBeans-0",
					new List1dOfBeans().init1(),
					0,
					/* Json */		"[{a:1, b:'foo'}]",
					/* Xml */		"<array><object><a>1</a><b>foo</b></object></array>\n",
					/* Html */		"<table _type='array'><tr><th>a</th><th>b</th></tr><tr><td>1</td><td>foo</td></tr></table>\n",
					/* Uon */		"@((a=1,b=foo))",
					/* UrlEnc */	"0=(a=1,b=foo)"
				)
			},
			{ 	/* 1 */
				new Input(
					"List1dOfBeans-1",
					new List1dOfBeans().init1(),
					1,
					/* Json */		"[\n\t{a:1, b:'foo'}\n]",
					/* Xml */		"<array>\n\t<object><a>1</a><b>foo</b></object>\n</array>\n",
					/* Html */		"<table _type='array'>\n\t<tr><th>a</th><th>b</th></tr>\n\t<tr><td>1</td><td>foo</td></tr>\n</table>\n",
					/* Uon */		"@(\n\t(a=1,b=foo)\n)",
					/* UrlEnc */	"0=(\n\ta=1,\n\tb=foo\n)"
				)
			},
			{ 	/* 2 */
				new Input(
					"List1dOfBeans-2",
					new List1dOfBeans().init1(),
					2,
					/* Json */		"[\n\t{\n\t\ta: 1,\n\t\tb: 'foo'\n\t}\n]",
					/* Xml */		"<array>\n\t<object>\n\t\t<a>1</a>\n\t\t<b>foo</b>\n\t</object>\n</array>\n",
					/* Html */		"<table _type='array'>\n\t<tr>\n\t\t<th>a</th>\n\t\t<th>b</th>\n\t</tr>\n\t<tr>\n\t\t<td>1</td>\n\t\t<td>foo</td>\n\t</tr>\n</table>\n",
					/* Uon */		"@(\n\t(\n\t\ta=1,\n\t\tb=foo\n\t)\n)",
					/* UrlEnc */	"0=(\n\ta=1,\n\tb=foo\n)"
				)
			},
			{ 	/* 3 */
				new Input(
					"List2dOfBeans-0",
					new List2dOfBeans().init2(),
					0,
					/* Json */		"[[{a:1, b:'foo'}]]",
					/* Xml */		"<array><array><object><a>1</a><b>foo</b></object></array></array>\n",
					/* Html */		"<ul><li><table _type='array'><tr><th>a</th><th>b</th></tr><tr><td>1</td><td>foo</td></tr></table></li></ul>\n",
					/* Uon */		"@(@((a=1,b=foo)))",
					/* UrlEnc */	"0=@((a=1,b=foo))"
				)
			},
			{ 	/* 4 */
				new Input(
					"List2dOfBeans-1",
					new List2dOfBeans().init2(),
					1,
					/* Json */		"[\n\t[{a:1, b:'foo'}]\n]",
					/* Xml */		"<array>\n\t<array><object><a>1</a><b>foo</b></object></array>\n</array>\n",
					/* Html */		"<ul>\n\t<li><table _type='array'><tr><th>a</th><th>b</th></tr><tr><td>1</td><td>foo</td></tr></table></li>\n</ul>\n",
					/* Uon */		"@(\n\t@((a=1,b=foo))\n)",
					/* UrlEnc */	"0=@(\n\t(a=1,b=foo)\n)"
				)
			},
			{ 	/* 5 */
				new Input(
					"List2dOfBeans-2",
					new List2dOfBeans().init2(),
					2,
					/* Json */		"[\n\t[\n\t\t{a:1, b:'foo'}\n\t]\n]",
					/* Xml */		"<array>\n\t<array>\n\t\t<object><a>1</a><b>foo</b></object>\n\t</array>\n</array>\n",
					/* Html */		"<ul>\n\t<li>\n\t\t<table _type='array'><tr><th>a</th><th>b</th></tr><tr><td>1</td><td>foo</td></tr></table>\n\t</li>\n</ul>\n",
					/* Uon */		"@(\n\t@(\n\t\t(a=1,b=foo)\n\t)\n)",
					/* UrlEnc */	"0=@(\n\t(\n\t\ta=1,\n\t\tb=foo\n\t)\n)"
				)
			},
			{ 	/* 6 */
				new Input(
					"List2dOfBeans-3",
					new List2dOfBeans().init2(),
					3,
					/* Json */		"[\n\t[\n\t\t{\n\t\t\ta: 1,\n\t\t\tb: 'foo'\n\t\t}\n\t]\n]",
					/* Xml */		"<array>\n\t<array>\n\t\t<object>\n\t\t\t<a>1</a>\n\t\t\t<b>foo</b>\n\t\t</object>\n\t</array>\n</array>\n",
					/* Html */		"<ul>\n\t<li>\n\t\t<table _type='array'>\n\t\t\t<tr><th>a</th><th>b</th></tr>\n\t\t\t<tr><td>1</td><td>foo</td></tr>\n\t\t</table>\n\t</li>\n</ul>\n",
					/* Uon */		"@(\n\t@(\n\t\t(\n\t\t\ta=1,\n\t\t\tb=foo\n\t\t)\n\t)\n)",
					/* UrlEnc */	"0=@(\n\t(\n\t\ta=1,\n\t\tb=foo\n\t)\n)"
				)
			},
			{ 	/* 7 */
				new Input(
					"Map1dOfBeans-0",
					new Map1dOfBeans().init1(),
					0,
					/* Json */		"{a:{a:1, b:'foo'}}",
					/* Xml */		"<object><a><a>1</a><b>foo</b></a></object>\n",
					/* Html */		"<table><tr><td>a</td><td><table><tr><td>a</td><td>1</td></tr><tr><td>b</td><td>foo</td></tr></table></td></tr></table>\n",
					/* Uon */		"(a=(a=1,b=foo))",
					/* UrlEnc */	"a=(a=1,b=foo)"
				)
			},
			{ 	/* 8 */
				new Input(
					"Map1dOfBeans-1",
					new Map1dOfBeans().init1(),
					1,
					/* Json */		"{\n\ta: {a:1, b:'foo'}\n}",
					/* Xml */		"<object>\n\t<a><a>1</a><b>foo</b></a>\n</object>\n",
					/* Html */		"<table>\n\t<tr><td>a</td><td><table><tr><td>a</td><td>1</td></tr><tr><td>b</td><td>foo</td></tr></table></td></tr>\n</table>\n",
					/* Uon */		"(\n\ta=(a=1,b=foo)\n)",
					/* UrlEnc */	"a=(\n\ta=1,\n\tb=foo\n)"
				)
			},
			{ 	/* 9 */
				new Input(
					"Map1dOfBeans-2",
					new Map1dOfBeans().init1(),
					2,
					/* Json */		"{\n\ta: {\n\t\ta: 1,\n\t\tb: 'foo'\n\t}\n}",
					/* Xml */		"<object>\n\t<a>\n\t\t<a>1</a>\n\t\t<b>foo</b>\n\t</a>\n</object>\n",
					/* Html */		"<table>\n\t<tr>\n\t\t<td>a</td>\n\t\t<td><table><tr><td>a</td><td>1</td></tr><tr><td>b</td><td>foo</td></tr></table></td>\n\t</tr>\n</table>\n",
					/* Uon */		"(\n\ta=(\n\t\ta=1,\n\t\tb=foo\n\t)\n)",
					/* UrlEnc */	"a=(\n\ta=1,\n\tb=foo\n)"
				)
			},
			{ 	/* 10 */
				new Input(
					"Map2dOfBeans-0",
					new Map2dOfBeans().init2(),
					0,
					/* Json */		"{b:{a:{a:1, b:'foo'}}}",
					/* Xml */		"<object><b><a><a>1</a><b>foo</b></a></b></object>\n",
					/* Html */		"<table><tr><td>b</td><td><table><tr><td>a</td><td><table><tr><td>a</td><td>1</td></tr><tr><td>b</td><td>foo</td></tr></table></td></tr></table></td></tr></table>\n",
					/* Uon */		"(b=(a=(a=1,b=foo)))",
					/* UrlEnc */	"b=(a=(a=1,b=foo))"
				)
			},
			{ 	/* 11 */
				new Input(
					"Map2dOfBeans-1",
					new Map2dOfBeans().init2(),
					1,
					/* Json */		"{\n\tb: {a:{a:1, b:'foo'}}\n}",
					/* Xml */		"<object>\n\t<b><a><a>1</a><b>foo</b></a></b>\n</object>\n",
					/* Html */		"<table>\n\t<tr><td>b</td><td><table><tr><td>a</td><td><table><tr><td>a</td><td>1</td></tr><tr><td>b</td><td>foo</td></tr></table></td></tr></table></td></tr>\n</table>\n",
					/* Uon */		"(\n\tb=(a=(a=1,b=foo))\n)",
					/* UrlEnc */	"b=(\n\ta=(a=1,b=foo)\n)"
				)
			},
			{ 	/* 12 */
				new Input(
					"Map2dOfBeans-2",
					new Map2dOfBeans().init2(),
					2,
					/* Json */		"{\n\tb: {\n\t\ta: {a:1, b:'foo'}\n\t}\n}",
					/* Xml */		"<object>\n\t<b>\n\t\t<a><a>1</a><b>foo</b></a>\n\t</b>\n</object>\n",
					/* Html */		"<table>\n\t<tr>\n\t\t<td>b</td>\n\t\t<td><table><tr><td>a</td><td><table><tr><td>a</td><td>1</td></tr><tr><td>b</td><td>foo</td></tr></table></td></tr></table></td>\n\t</tr>\n</table>\n",
					/* Uon */		"(\n\tb=(\n\t\ta=(a=1,b=foo)\n\t)\n)",
					/* UrlEnc */	"b=(\n\ta=(\n\t\ta=1,\n\t\tb=foo\n\t)\n)"
				)
			},
			{ 	/* 13 */
				new Input(
					"Map2dOfBeans-3",
					new Map2dOfBeans().init2(),
					3,
					/* Json */		"{\n\tb: {\n\t\ta: {\n\t\t\ta: 1,\n\t\t\tb: 'foo'\n\t\t}\n\t}\n}",
					/* Xml */		"<object>\n\t<b>\n\t\t<a>\n\t\t\t<a>1</a>\n\t\t\t<b>foo</b>\n\t\t</a>\n\t</b>\n</object>\n",
					/* Html */		"<table>\n\t<tr>\n\t\t<td>b</td>\n\t\t<td>\n\t\t\t<table><tr><td>a</td><td><table><tr><td>a</td><td>1</td></tr><tr><td>b</td><td>foo</td></tr></table></td></tr></table>\n\t\t</td>\n\t</tr>\n</table>\n",
					/* Uon */		"(\n\tb=(\n\t\ta=(\n\t\t\ta=1,\n\t\t\tb=foo\n\t\t)\n\t)\n)",
					/* UrlEnc */	"b=(\n\ta=(\n\t\ta=1,\n\t\tb=foo\n\t)\n)"
				)
			},
		});
	}

	Input input;

	public MaxIndentTest(Input input) {
		this.input = input;
	}

	static class Input {
		String label;
		Object in;
		int maxDepth;
		String json, xml, html, uon, urlEnc;

		Input(String label, Object in, int maxDepth, String json, String xml, String html, String uon, String urlEnc) {
			this.label = label;
			this.in = in;
			this.maxDepth = maxDepth;
			this.json = json;
			this.xml = xml;
			this.html = html;
			this.uon = uon;
			this.urlEnc = urlEnc;
		}
	}

	public static class List1dOfBeans extends LinkedList<ABean> {
		public List1dOfBeans init1() {
			add(new ABean().init());
			return this;
		}
	}

	public static class List2dOfBeans extends LinkedList<List1dOfBeans> {
		public List2dOfBeans init2() {
			add(new List1dOfBeans().init1());
			return this;
		}
	}

	public static class Map1dOfBeans extends LinkedHashMap<String,ABean> {
		public Map1dOfBeans init1() {
			put("a", new ABean().init());
			return this;
		}
	}

	public static class Map2dOfBeans extends LinkedHashMap<String,Map1dOfBeans> {
		public Map2dOfBeans init2() {
			put("b", new Map1dOfBeans().init1());
			return this;
		}
	}

	@Test
	public void a1_serializeJson() throws Exception {
		WriterSerializer s = JsonSerializer.DEFAULT_LAX_READABLE.builder().maxIndent(input.maxDepth).build();
		testSerialize("json", s, input.json);
	}

	@Test
	public void b11_serializeXml() throws Exception {
		WriterSerializer s = XmlSerializer.DEFAULT_SQ_READABLE.builder().maxIndent(input.maxDepth).build();
		testSerialize("xml", s, input.xml);
	}

	@Test
	public void c11_serializeHtml() throws Exception {
		WriterSerializer s = HtmlSerializer.DEFAULT_SQ_READABLE.builder().maxIndent(input.maxDepth).build();
		testSerialize("html", s, input.html);
	}

	@Test
	public void d11_serializeUon() throws Exception {
		WriterSerializer s = UonSerializer.DEFAULT_READABLE.builder().maxIndent(input.maxDepth).build();
		testSerialize("uon", s, input.uon);
	}

	@Test
	public void e11_serializeUrlEncoding() throws Exception {
		WriterSerializer s = UrlEncodingSerializer.DEFAULT_READABLE.builder().maxIndent(input.maxDepth).build();
		testSerialize("urlEncoding", s, input.urlEnc);
	}

	private void testSerialize(String testName, Serializer s, String expected) throws Exception {
		try {
			String r = s.isWriterSerializer() ? ((WriterSerializer)s).serialize(input.in) : ((OutputStreamSerializer)s).serializeToHex(input.in);

			// Specifying "xxx" in the expected results will spit out what we should populate the field with.
			if (expected.equals("xxx")) {
				System.out.println(input.label + "/" + testName + "=\n" + r.replaceAll("\n", "\\\\n").replaceAll("\t", "\\\\t")); // NOT DEBUG
				System.out.println(r);
				return;
			}

			TestUtils.assertEquals(expected, r, "{0}/{1} parse-normal failed", input.label, testName);

		} catch (AssertionError e) {
			throw e;
		} catch (Exception e) {
			e.printStackTrace();
			throw new AssertionError(input.label + "/" + testName + " failed.  exception=" + e.getLocalizedMessage());
		}
	}

}