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

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

import org.apache.wicket.Component;
import org.apache.wicket.MarkupContainer;
import org.apache.wicket.behavior.Behavior;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.util.tester.DummyHomePage;
import org.apache.wicket.util.tester.WicketTester;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

/**
 * @author Marat Radchenko
 */
public class StatelessCheckerTest
{
	@StatelessComponent
	public static class StatelessPage extends DummyHomePage
	{
		private static final long serialVersionUID = 1L;
	}

	@StatelessComponent
	private static class StatelessLabel extends Label
	{
		private static final long serialVersionUID = 1L;

		public StatelessLabel(final String id)
		{
			super(id);
		}
	}

	@StatelessComponent
	private static class StatefulMarkupContainer extends MarkupContainer
	{
		private static final long serialVersionUID = 1L;

		public StatefulMarkupContainer(String id) {
			super(id);
		}

		@Override
		public boolean getStatelessHint()
		{
			return false;
		}
	}

	private static class StatefulBehavior extends Behavior
	{
		private static final long serialVersionUID = 1L;

		@Override
		public boolean getStatelessHint(Component component)
		{
			return false;
		}
	}

	private static class StatelessCheckerQuietly extends StatelessChecker
	{
		private StatelessCheckFailureException ex;

		protected void fail(StatelessCheckFailureException e)
		{
			this.ex = e;
		}

		public StatelessCheckFailureException getFailureException()
		{
			return ex;
		}
	}

	private StatelessChecker checker;

	private StatelessCheckerQuietly checkerQuietly;

	private WicketTester tester;

	@BeforeEach
	public void setUp()
	{
		tester = new WicketTester();
		checker = new StatelessChecker();
		checkerQuietly = new StatelessCheckerQuietly();
	}

	@AfterEach
	public void tearDown()
	{
		tester.destroy();
	}

	@Test
	public void testNonBookmarkablePage()
	{
		try
		{
			startNonBookmarkablePage(checker);
			fail("Expected tester.startPage() to fail with StatelessCheckFailureException");
		}
		catch (StatelessCheckFailureException ex)
		{
			assertNonBookmarkablePage(ex);
		}
	}

	@Test
	public void testNonBookmarkablePageQuietly()
	{
		startNonBookmarkablePage(checkerQuietly);
		StatelessCheckFailureException ex = checkerQuietly.getFailureException();
		assertNonBookmarkablePage(ex);
	}

	private void startNonBookmarkablePage(StatelessChecker checker)
	{
		tester.getApplication().getComponentPostOnBeforeRenderListeners().add(checker);
		tester.startPage(StatelessPage.class);
	}

	private void assertNonBookmarkablePage(StatelessCheckFailureException ex) {
		assertEquals("'[Page class = org.apache.wicket.devutils.stateless.StatelessCheckerTest$StatelessPage, id = 0, render count = 1]' claims to be stateless but isn't. Offending component: [TestLink [Component id = testPage]]", ex.getMessage());
		assertEquals(StatelessPage.class, ex.getComponent().getClass());
	}

	@Test
	public void testStatefulBehaviors()
	{
		try
		{
			startComponentInPage(checker, new StatelessLabel("foo").add(new StatefulBehavior()));
			fail("Expected tester.startComponentInPage() to fail with StatelessCheckFailureException");
		}
		catch (StatelessCheckFailureException ex)
		{
			assertStatefulBehaviors(ex);
		}
	}

	@Test
	public void testStatefulBehaviorsQuietly()
	{
		startComponentInPage(checkerQuietly, new StatelessLabel("foo").add(new StatefulBehavior()));
		StatelessCheckFailureException ex = checkerQuietly.getFailureException();
		assertStatefulBehaviors(ex);
	}

	private void startComponentInPage(StatelessChecker checker, Component foo) {
		tester.getApplication().getComponentPostOnBeforeRenderListeners().add(checker);
		tester.startComponentInPage(foo);
	}

	private void assertStatefulBehaviors(StatelessCheckFailureException ex) {
		assertEquals("'[Component id = foo]' claims to be stateless but isn't. Stateful behaviors: org.apache.wicket.devutils.stateless.StatelessCheckerTest$StatefulBehavior", ex.getMessage());
		assertEquals(StatelessLabel.class, ex.getComponent().getClass());
	}

	@Test
	public void testPositive()
	{
		startComponentInPage(checker, new StatelessLabel("foo"));
	}

	@Test
	public void testStatefulMarkupContainer()
	{
		try
		{
			startComponentInPage(checker, new StatefulMarkupContainer("foo"));
			fail("Expected tester.startComponentInPage() to fail with StatelessCheckFailureException");
		}
		catch (StatelessCheckFailureException ex)
		{
			assertStatefulMarkupContainer(ex);
		}
	}

	@Test
	public void testStatefulMarkupContainerQuietly()
	{
		startComponentInPage(checkerQuietly, new StatefulMarkupContainer("foo"));
		StatelessCheckFailureException ex = checkerQuietly.getFailureException();
		assertStatefulMarkupContainer(ex);

	}

	private void assertStatefulMarkupContainer(StatelessCheckFailureException ex)
	{
		assertEquals("'[StatefulMarkupContainer [Component id = foo]]' claims to be stateless but isn't. Possible reason: no stateless hint", ex.getMessage());
		assertEquals(StatefulMarkupContainer.class, ex.getComponent().getClass());
	}
}
