// Copyright 2006, 2007, 2008, 2010, 2011 The Apache Software Foundation
//
// Licensed 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.tapestry5.internal.services;

import org.apache.tapestry5.ComponentEventCallback;
import org.apache.tapestry5.EventContext;
import org.apache.tapestry5.TapestryMarkers;
import org.apache.tapestry5.internal.structure.ComponentPageElementResources;
import org.apache.tapestry5.internal.test.InternalBaseTestCase;
import org.apache.tapestry5.ioc.Invokable;
import org.apache.tapestry5.model.ComponentModel;
import org.apache.tapestry5.runtime.ComponentEvent;
import org.easymock.EasyMock;
import org.easymock.IAnswer;
import org.slf4j.Logger;
import org.testng.annotations.Test;

import static org.easymock.EasyMock.eq;
import static org.easymock.EasyMock.isA;

public class ComponentEventImplTest extends InternalBaseTestCase
{

    private ComponentPageElementResources mockResources()
    {
        ComponentPageElementResources resources = newMock(ComponentPageElementResources.class);

        expect(resources.invoke(EasyMock.isA(String.class), EasyMock.isA(Invokable.class))).andAnswer(new IAnswer<Object>()
        {
            public Object answer() throws Throwable
            {
                Invokable inv = (Invokable) EasyMock.getCurrentArguments()[1];

                return inv.invoke();
            }
        });

        return resources;
    }

    @Test
    public void matches_on_event_type()
    {
        ComponentEventCallback handler = mockComponentEventHandler();
        EventContext context = mockEventContext();
        Logger logger = mockLogger();
        ComponentModel model = mockComponentModel();

        train_isDebugEnabled(logger, false);

        train_getCount(context, 0);

        replay();

        ComponentEvent event = new ComponentEventImpl("eventType", "someId", context, handler, null, false, model, logger);

        assertTrue(event.matches("eventType", "someId", 0));
        assertFalse(event.matches("foo", "someId", 0));

        verify();
    }

    /**
     * @since 5.2.0
     */
    @Test
    public void no_match_one_event_is_aborted()
    {
        ComponentEventCallback handler = mockComponentEventHandler();
        EventContext context = mockEventContext();
        Logger logger = mockLogger();
        Object result = new Object();
        ComponentPageElementResources resources = mockResources();
        ComponentModel model = mockComponentModel();

        train_isDebugEnabled(logger, false);

        train_handleResult(handler, result, true);

        replay();

        ComponentEvent event = new ComponentEventImpl("eventType", "someId", context, handler, resources, false, model, logger);

        event.storeResult(result);

        assertFalse(event.matches("eventType", "someId", 0));

        verify();

    }

    @Test
    public void event_type_match_is_case_insensitive()
    {
        ComponentEventCallback handler = mockComponentEventHandler();
        EventContext context = mockEventContext();
        Logger logger = mockLogger();
        ComponentModel model = mockComponentModel();

        train_isDebugEnabled(logger, false);

        train_getCount(context, 0);

        replay();

        ComponentEvent event = new ComponentEventImpl("eventType", "someId", context, handler, null, false, model, logger);

        assertTrue(event.matches("EVENTTYPE", "someid", 0));

        verify();
    }

    @Test
    public void matches_on_component_id()
    {
        ComponentEventCallback handler = mockComponentEventHandler();
        EventContext context = mockEventContext();
        Logger logger = mockLogger();
        ComponentModel model = mockComponentModel();

        train_isDebugEnabled(logger, false);

        train_getCount(context, 0);

        replay();

        ComponentEvent event = new ComponentEventImpl("eventType", "someId", context, handler, null, false, model, logger);

        assertTrue(event.matches("eventType", "someId", 0));

        assertFalse(event.matches("eventtype", "bar", 0));

        verify();
    }

    @Test
    public void component_id_matches_are_case_insensitive()
    {
        ComponentEventCallback handler = mockComponentEventHandler();
        EventContext context = mockEventContext();
        Logger logger = mockLogger();
        ComponentModel model = mockComponentModel();

        train_isDebugEnabled(logger, false);

        train_getCount(context, 0);

        replay();

        ComponentEvent event = new ComponentEventImpl("eventType", "someId", context, handler, null, false, model, logger);

        assertTrue(event.matches("eventtype", "SOMEID", 0));

        verify();
    }

    @Test
    public void coerce_context()
    {
        ComponentEventCallback handler = mockComponentEventHandler();
        ComponentPageElementResources resources = mockComponentPageElementResources();
        EventContext context = mockEventContext();
        Integer value = Integer.valueOf(27);
        Logger logger = mockLogger();

        ComponentModel model = mockComponentModel();

        train_isDebugEnabled(logger, false);

        train_toClass(resources, "java.lang.Integer", Integer.class);

        train_getCount(context, 2);
        train_get(context, Integer.class, 0, value);

        replay();

        ComponentEvent event = new ComponentEventImpl("eventType", "someId", context, handler, resources, false, model, logger);

        assertSame(event.coerceContext(0, "java.lang.Integer"), value);

        verify();
    }

    @Test
    public void coerce_when_not_enough_context()
    {
        ComponentEventCallback handler = mockComponentEventHandler();
        EventContext context = mockEventContext();
        Logger logger = mockLogger();

        ComponentModel model = mockComponentModel();

        train_isDebugEnabled(logger, true);
        logger.debug(eq(TapestryMarkers.EVENT_HANDLER_METHOD), isA(String.class));

        train_getCount(context, 0);

        replay();

        ComponentEvent event = new ComponentEventImpl("eventType", "someId", context, handler, null, false, model, logger);

        event.setMethodDescription("foo.Bar.baz()");

        try
        {
            event.coerceContext(1, "java.lang.Integer");
        } catch (IllegalArgumentException ex)
        {
            assertEquals(ex.getMessage(),
                    "Method foo.Bar.baz() has more parameters than there are context values for this component event.");
        }

        verify();
    }

    @Test
    public void unable_to_coerce()
    {
        ComponentEventCallback handler = mockComponentEventHandler();
        EventContext context = mockEventContext();
        ComponentPageElementResources resources = mockComponentPageElementResources();
        Logger logger = mockLogger();
        ComponentModel model = mockComponentModel();

        train_isDebugEnabled(logger, true);
        logger.debug(eq(TapestryMarkers.EVENT_HANDLER_METHOD), isA(String.class));

        train_toClass(resources, Integer.class.getName(), Integer.class);

        train_getCount(context, 1);

        expect(context.get(Integer.class, 0)).andThrow(new NumberFormatException("Not so easy, is it?"));

        replay();

        ComponentEvent event = new ComponentEventImpl("eventType", "someId", context, handler, resources, false, model, logger);

        event.setMethodDescription("foo.Bar.baz()");

        try
        {
            event.coerceContext(0, "java.lang.Integer");
            unreachable();
        } catch (IllegalArgumentException ex)
        {
            // Different JVMs will report the conversion error slightly differently,
            // so we don't try to check that part of the error message.

            assertTrue(ex.getMessage().startsWith("Exception in method foo.Bar.baz(), parameter #1:"));
        }

        verify();
    }

    @Test
    public void store_result_and_abort()
    {
        Object result = new Object();
        String methodDescription = "foo.Bar.baz()";
        Logger logger = mockLogger();
        ComponentModel model = mockComponentModel();

        train_isDebugEnabled(logger, true);
        EasyMock.expectLastCall().atLeastOnce();

        ComponentPageElementResources resources = mockResources();

        logger.debug(eq(TapestryMarkers.EVENT_HANDLER_METHOD), isA(String.class));

        ComponentEventCallback handler = mockComponentEventHandler();

        train_handleResult(handler, result, true);

        replay();

        ComponentEvent event = new ComponentEventImpl("eventType", "someId", null, handler, resources, false, model, logger);

        event.setMethodDescription(methodDescription);

        assertFalse(event.isAborted());

        assertTrue(event.storeResult(result));

        assertTrue(event.isAborted());

        verify();
    }

    @Test
    public void store_result_and_continue()
    {
        Object result = new Object();
        String methodDescription = "foo.Bar.baz()";
        ComponentEventCallback handler = mockComponentEventHandler();
        Logger logger = mockLogger();
        ComponentPageElementResources resources = mockResources();
        ComponentModel model = mockComponentModel();

        train_isDebugEnabled(logger, true);
        logger.debug(eq(TapestryMarkers.EVENT_HANDLER_METHOD), isA(String.class));

        train_handleResult(handler, result, false);

        replay();

        ComponentEvent event = new ComponentEventImpl("eventType", "someId", null, handler, resources, false, model, logger);

        event.setMethodDescription(methodDescription);

        assertFalse(event.storeResult(result));

        assertFalse(event.isAborted());

        verify();
    }

    @Test
    public void store_null_result_does_not_abort_or_invoke_handler()
    {
        ComponentEventCallback handler = mockComponentEventHandler();
        Logger logger = mockLogger();
        ComponentModel model = mockComponentModel();

        train_isDebugEnabled(logger, true);
        logger.debug(eq(TapestryMarkers.EVENT_HANDLER_METHOD), isA(String.class));

        replay();

        ComponentEvent event = new ComponentEventImpl("eventType", "someId", null, handler, null, false, model, logger);

        event.setMethodDescription("foo.Bar.baz()");

        assertFalse(event.storeResult(null));

        assertFalse(event.isAborted());

        verify();
    }

    @SuppressWarnings("unchecked")
    @Test
    public void store_result_when_aborted_is_failure()
    {
        Object result = new Object();
        ComponentEventCallback handler = mockComponentEventHandler();
        Logger logger = mockLogger();
        ComponentPageElementResources resources = mockResources();
        ComponentModel model = mockComponentModel();

        train_isDebugEnabled(logger, true);
        EasyMock.expectLastCall().atLeastOnce();

        logger.debug(eq(TapestryMarkers.EVENT_HANDLER_METHOD), isA(String.class));

        EasyMock.expectLastCall().atLeastOnce();

        expect(handler.handleResult(result)).andReturn(true);

        replay();

        ComponentEvent event = new ComponentEventImpl("eventType", "someId", null, handler, resources, false, model, logger);

        event.setMethodDescription("foo.Bar.baz()");
        event.storeResult(result);

        try
        {
            event.setMethodDescription("foo.Bar.biff()");
            event.storeResult(null);
            unreachable();
        } catch (IllegalStateException ex)
        {
            assertEquals(ex.getMessage(), String.format("Can not store result from invoking method %s, because an event result value has already been obtained from some other event handler method.", "foo.Bar.biff()"));
        }

        verify();
    }
}
