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

import static org.junit.jupiter.api.Assertions.assertEquals;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.UUID;

import org.apache.wicket.MarkupContainer;
import org.apache.wicket.Page;
import org.apache.wicket.markup.IMarkupResourceStreamProvider;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.markup.repeater.Item;
import org.apache.wicket.markup.repeater.RefreshingView;
import org.apache.wicket.markup.repeater.util.ModelIteratorAdapter;
import org.apache.wicket.mock.MockApplication;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.LoadableDetachableModel;
import org.apache.wicket.model.PropertyModel;
import org.apache.wicket.util.WicketTestTag;
import org.apache.wicket.util.resource.IResourceStream;
import org.apache.wicket.util.resource.StringResourceStream;
import org.apache.wicket.util.tester.WicketTestCase;
import org.apache.wicket.util.tester.WicketTester;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;

@Tag(WicketTestTag.SLOW)
public class ComponentQueueingPerformanceTest extends WicketTestCase
{
	private void run(Class<? extends Page> pageClass)
	{
		WicketTester tester = new WicketTester(new MockApplication());
		try
		{
			tester.startPage(pageClass);
		}
		finally
		{
			tester.destroy();
		}
	}

	@Test
	void performance()
	{
		final int warmup = 30;
		final int performance = 60;

		tester.startPage(AddContactsPage.class);

		for (int i = 0; i < warmup; i++)
		{
			run(AddContactsPage.class);
		}
		long start = System.currentTimeMillis();
		for (int i = 0; i < performance; i++)
		{
			run(AddContactsPage.class);
		}
		long end = System.currentTimeMillis();
		long addDuration = end - start;

		for (int i = 0; i < warmup; i++)
		{
			run(QueueContactsPage.class);
		}
		start = System.currentTimeMillis();
		for (int i = 0; i < performance; i++)
		{
			run(QueueContactsPage.class);
		}
		end = System.currentTimeMillis();
		long queueDuration = end - start;


		System.out.println("add duration: " + addDuration + " queue duration: " + queueDuration);

	}


	@Test
	void consistency()
	{
		tester.startPage(new QueueContactsPage());
		String queue = tester.getLastResponseAsString();
		tester.startPage(new AddContactsPage());
		String add = tester.getLastResponseAsString();
		assertEquals(queue, add);
	}

	private static class PhoneNumber
	{
		String id = UUID.randomUUID().toString();
		String areacode = "234";
		String prefix = "342";
		String suffix = "3423";
	}

	private static class Address
	{
		String id = UUID.randomUUID().toString();
		String street = "2343 Jsdfjsf St.";
		String city = "Ksdfjsfs";
		String state = "AS";
		String zipcode = "32434";
	}

	private static class Contact
	{
		String id = UUID.randomUUID().toString();
		String first = "Jlkjsf";
		String last = "Kjwieojkjf";
		Address address = new Address();
		PhoneNumber work = new PhoneNumber();
		PhoneNumber cell = new PhoneNumber();

	}

	private static Store store = new Store();

	private static class Store
	{
		Map<String, PhoneNumber> phones = new HashMap<String, PhoneNumber>();
		Map<String, Address> addresses = new HashMap<String, Address>();
		Map<String, Contact> contacts = new HashMap<String, Contact>();

		public <T> T get(Class<T> clazz, String id)
		{
			if (PhoneNumber.class.equals(clazz))
			{
				return (T)phones.get(id);
			}
			else if (Address.class.equals(clazz))
			{
				return (T)addresses.get(id);
			}
			else if (Contact.class.equals(clazz))
			{
				return (T)contacts.get(id);
			}
			throw new RuntimeException();
		}

		Store()
		{
			for (int i = 0; i < 250; i++)
			{
				Contact contact = new Contact();
				contacts.put(contact.id, contact);
			}
		}

	}

	private static class ContactModel extends LoadableDetachableModel<Contact>
	{
		private String id;

		ContactModel(Contact contact)
		{
			super(contact);
			this.id = contact.id;
		}

		@Override
		protected Contact load()
		{
			return store.contacts.get(id);
		}

	}

	private static abstract class AbstractPhonePanel extends TestPanel
	{
		AbstractPhonePanel(String id, IModel<PhoneNumber> phone)
		{
			super(id);
			setPanelMarkup("<wicket:panel><span wicket:id='areacode'></span> <span wicket:id='prefix'></span>-<span wicket:id='suffix'></span></wicket:panel>");
		}
	}

	private static class AddPhonePanel extends AbstractPhonePanel
	{
		AddPhonePanel(String id, IModel<PhoneNumber> phone)
		{
			super(id, phone);
			add(new Label("areacode", new PropertyModel(phone, "areacode")));
			add(new Label("prefix", new PropertyModel(phone, "prefix")));
			add(new Label("suffix", new PropertyModel(phone, "suffix")));
		}
	}
	private static class QueuePhonePanel extends AbstractPhonePanel
	{
		public QueuePhonePanel(String id, IModel<PhoneNumber> phone)
		{
			super(id, phone);
			queue(new Label("areacode", new PropertyModel(phone, "areacode")));
			queue(new Label("prefix", new PropertyModel(phone, "prefix")));
			queue(new Label("suffix", new PropertyModel(phone, "suffix")));
		}
	}

	private static abstract class AbstractAddressPanel extends TestPanel
	{
		AbstractAddressPanel(String id, IModel<Address> addr)
		{
			super(id);
			setPanelMarkup("<wicket:panel><span wicket:id='street'></span><br/><span wicket:id='city'></span>, <span wicket:id='state'></span> <span wicket:id='zipcode'></span></wicket:panel>");
		}
	}

	private static class AddAddressPanel extends AbstractAddressPanel
	{
		AddAddressPanel(String id, IModel<Address> addr)
		{
			super(id, addr);
			add(new Label("street", new PropertyModel(addr, "street")));
			add(new Label("city", new PropertyModel(addr, "city")));
			add(new Label("state", new PropertyModel(addr, "state")));
			add(new Label("zipcode", new PropertyModel(addr, "zipcode")));
		}
	}
	private class QueueAddressPanel extends AbstractAddressPanel
	{
		public QueueAddressPanel(String id, IModel<Address> addr)
		{
			super(id, addr);
			queue(new Label("street", new PropertyModel(addr, "street")));
			queue(new Label("city", new PropertyModel(addr, "city")));
			queue(new Label("sate", new PropertyModel(addr, "state")));
			queue(new Label("zipcode", new PropertyModel(addr, "zipcode")));
		}
	}

	static class AbstractContactsPage extends TestPage
	{
		AbstractContactsPage()
		{
			// @formatter:off
			setPageMarkup(
				"  <div wicket:id='contacts'>"
				+ "  <span wicket:id='first'></span>"
				+ "  <span wicket:id='last'></span>"
				+ "  <div wicket:id='addr'></div>"
				+ "  <div wicket:id='work'></div>"
				+ "  <div wicket:id='cell'></div>"
				+ "</div>");
			// @formatter:on

		}
	}

	public static class AddContactsPage extends AbstractContactsPage
	{
		public AddContactsPage()
		{
			add(new RefreshingView<Contact>("contacts")
			{
				@Override
				protected Iterator<IModel<Contact>> getItemModels()
				{
					return new ModelIteratorAdapter<Contact>(store.contacts.values())
					{
						@Override
						protected IModel<Contact> model(Contact object)
						{
							return new ContactModel(object);
						}
					};
				}

				@Override
				protected void populateItem(Item<Contact> item)
				{
					IModel<Contact> model = item.getModel();
					item.add(new Label("first", new PropertyModel(model, "first")));
					item.add(new Label("last", new PropertyModel(model, "first")));
					item.add(new AddAddressPanel("addr", new PropertyModel<Address>(model, "address")));
					item.add(new AddPhonePanel("work", new PropertyModel<PhoneNumber>(model, "work")));
					item.add(new AddPhonePanel("cell", new PropertyModel<PhoneNumber>(model, "cell")));
				}
			});

		}
	}

	public static class QueueContactsPage extends AbstractContactsPage
	{
		public QueueContactsPage()
		{
			queue(new RefreshingView<Contact>("contacts")
			{
				@Override
				protected Iterator<IModel<Contact>> getItemModels()
				{
					return new ModelIteratorAdapter<Contact>(store.contacts.values())
					{
						@Override
						protected IModel<Contact> model(Contact object)
						{
							return new ContactModel(object);
						}
					};
				}

				@Override
				protected void populateItem(Item<Contact> item)
				{
					IModel<Contact> model = item.getModel();
					item.queue(new Label("first", new PropertyModel(model, "first")));
					item.queue(new Label("last", new PropertyModel(model, "first")));
					item.queue(new AddAddressPanel("addr", new PropertyModel<Address>(model, "address")));
					item.queue(new AddPhonePanel("work", new PropertyModel<PhoneNumber>(model, "work")));
					item.queue(new AddPhonePanel("cell", new PropertyModel<PhoneNumber>(model, "cell")));
				}
			});

		}
	}

	private static class TestPage extends WebPage implements IMarkupResourceStreamProvider
	{
		private String markup;

		TestPage()
		{
		}

		public TestPage(String markup)
		{
			this.markup = markup;
		}

		String getPageMarkup()
		{
			return markup;
		}

		void setPageMarkup(String markup)
		{
			this.markup = markup;
		}

		@Override
		public IResourceStream getMarkupResourceStream(MarkupContainer container,
			Class<?> containerClass)
		{
			return new StringResourceStream(getPageMarkup());
		}

	}

	private static class TestPanel extends Panel implements IMarkupResourceStreamProvider
	{

		private String markup;

		TestPanel(String id)
		{
			super(id);
		}

		void setPanelMarkup(String markup)
		{
			this.markup = markup;
		}

		String getPanelMarkup()
		{
			return markup;
		}

		@Override
		public IResourceStream getMarkupResourceStream(MarkupContainer container,
			Class<?> containerClass)
		{
			return new StringResourceStream(getPanelMarkup());
		}
	}
}
