/*
 * JBoss, Home of Professional Open Source
 * Copyright 2010, Red Hat, Inc. and individual contributors
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.richfaces.cdk.templatecompiler;

import static org.easymock.EasyMock.anyObject;
import static org.easymock.EasyMock.eq;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.isNull;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.Set;

import javax.faces.event.ActionEvent;
import javax.faces.render.RenderKitFactory;
import javax.faces.validator.Validator;

import org.easymock.EasyMock;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.richfaces.cdk.As;
import org.richfaces.cdk.CdkTestBase;
import org.richfaces.cdk.CdkTestRunner;
import org.richfaces.cdk.FileManager;
import org.richfaces.cdk.Mock;
import org.richfaces.cdk.Source;
import org.richfaces.cdk.Sources;
import org.richfaces.cdk.model.ClassName;
import org.richfaces.cdk.model.ComponentLibrary;
import org.richfaces.cdk.model.EventName;
import org.richfaces.cdk.model.FacesId;
import org.richfaces.cdk.model.MethodSignature;
import org.richfaces.cdk.model.PropertyBase;
import org.richfaces.cdk.model.RenderKitModel;
import org.richfaces.cdk.model.RendererModel;
import org.richfaces.cdk.templatecompiler.model.Template;
import org.richfaces.cdk.xmlconfig.JAXB;
import org.richfaces.cdk.xmlconfig.JAXBBinding;
import org.xml.sax.InputSource;
import org.xml.sax.ext.EntityResolver2;

import com.google.common.collect.Lists;
import com.google.inject.Inject;

/**
 * @author Nick Belaevski
 *
 */
@RunWith(CdkTestRunner.class)
public class RendererTemplateParserTest extends CdkTestBase {
    @Inject
    @As(JAXBBinding.class)
    private JAXB binding;
    @Inject
    private ComponentLibrary library;
    @Inject
    private RendererTemplateParser parser;
    @Mock
    private EntityResolver2 resolver;
    @Mock
    @Source(Sources.RENDERER_TEMPLATES)
    private FileManager templatesSource;

    private void assertNoDefaultValue(PropertyBase property) {
        assertNull(property.getDefaultValue());
    }

    private void assertNoEventNames(PropertyBase property) {
        Set<EventName> eventNames = property.getEventNames();
        assertNotNull(eventNames);
        assertTrue(eventNames.isEmpty());
    }

    private void assertNoSignature(PropertyBase property) {
        MethodSignature signature = property.getSignature();
        assertNull(signature);
    }

    private void assertNotRequired(PropertyBase property) {
        assertFalse(property.isRequired());
    }

    @Test
    public void basicComponentTest() throws Exception {
        Template template = parser.parseTemplate(getJavaFile("org/richfaces/cdk/templatecompiler/basic.template.xml"));
        assertNotNull(template);

        parser.mergeTemplateIntoModel(template, null);

        RenderKitModel renderKit = getRenderkitFromModel(RenderKitFactory.HTML_BASIC_RENDER_KIT);
        RendererModel renderer = getFirstRendererFromRenderkit(renderKit);

        assertEquals(new ClassName("org.richfaces.renderkit.html.BasicRendererImpl"), renderer.getRendererClass());
        assertFalse(renderer.isRendersChildren());
    }

    /**
     * Checks that method signature satisfies the following declaration: <code>java.lang.String action()</code>
     *
     * @param actionProperty
     */
    private void checkDummyComponentAction(PropertyBase actionProperty) {
        assertNotNull(actionProperty);
        assertNoEventNames(actionProperty);

        assertNotNull(actionProperty.getSignature());
        assertEquals(0, actionProperty.getSignature().getParameters().size());
        assertEquals(String.class.getName(), actionProperty.getSignature().getReturnType().toString());
    }

    /**
     * Checks that method signature satisfies the following declaration:
     * <code>void actionListener(javax.faces.event.ActionEvent)</code>
     *
     * @param actionListenerProperty
     */
    private void checkDummyComponentActionListener(PropertyBase actionListenerProperty) {
        assertNotNull(actionListenerProperty);
        assertNoEventNames(actionListenerProperty);

        assertEquals(Lists.newArrayList(new ClassName(ActionEvent.class)), actionListenerProperty.getSignature()
                .getParameters());
    }

    /**
     * Checks that method signature satisfies the following declaration:
     * <code>float coolMethod(int, java.lang.String, javax.faces.validator.Validator)</code>
     *
     * @param coolMethodProperty
     */
    private void checkDummyComponentCoolMethod(PropertyBase coolMethodProperty) {
        assertNotNull(coolMethodProperty);
        assertNoEventNames(coolMethodProperty);

        ArrayList<ClassName> expectedSignature = Lists.newArrayList(new ClassName(int.class), new ClassName(String.class),
                new ClassName(Validator.class));

        assertEquals(expectedSignature, coolMethodProperty.getSignature().getParameters());
    }

    private void checkDummyComponentImportedAttribute(PropertyBase importedAttribute, Class<?> type) {
        assertNotNull(importedAttribute);
        assertNoEventNames(importedAttribute);
        assertNoSignature(importedAttribute);
        assertNoDefaultValue(importedAttribute);
        assertFalse(importedAttribute.isRequired());

        assertEquals(type.getName(), importedAttribute.getType().getName());
    }

    /**
     * @param integerAttribute
     */
    private void checkDummyComponentIntegerAttribute(PropertyBase integerAttribute) {
        assertNotNull(integerAttribute);
        assertNoEventNames(integerAttribute);
        assertNoSignature(integerAttribute);
        assertNotRequired(integerAttribute);

        assertEquals(new ClassName(Integer.class), integerAttribute.getType());
        assertEquals("-1", integerAttribute.getDefaultValue());
    }

    /**
     * Checks the following conditions for attribute:<br />
     * - signature is empty<br />
     * - There's a single "change" event name that is not a default event<br />
     *
     * @param onchangeAttr
     */
    private void checkDummyComponentOnchange(PropertyBase onchangeAttr) {
        assertNotNull(onchangeAttr);
        assertNoSignature(onchangeAttr);

        Set<EventName> changeEvents = onchangeAttr.getEventNames();
        assertNotNull(changeEvents);

        EventName changeEventName;
        Iterator<EventName> changeEventsIterator = changeEvents.iterator();
        assertTrue(changeEventsIterator.hasNext());

        changeEventName = changeEventsIterator.next();
        assertEquals("change", changeEventName.getName());
        assertFalse(changeEventName.isDefaultEvent());

        assertFalse(changeEventsIterator.hasNext());
    }

    /**
     * Checks the following conditions for attribute:<br />
     *
     * - signature is empty<br />
     * - First event name is "click" and it's not a default event<br />
     * - Second event name is "action" and it is a default event<br />
     *
     * @param onclickAttr
     */
    private void checkDummyComponentOnclick(PropertyBase onclickAttr) {
        assertNotNull(onclickAttr);
        assertNoSignature(onclickAttr);

        Set<EventName> clickEvents = onclickAttr.getEventNames();
        assertNotNull(clickEvents);

        EventName clickEventName;
        Iterator<EventName> clickEventsIterator = clickEvents.iterator();
        assertTrue(clickEventsIterator.hasNext());

        clickEventName = clickEventsIterator.next();
        assertEquals("click", clickEventName.getName());
        assertFalse(clickEventName.isDefaultEvent());

        assertTrue(clickEventsIterator.hasNext());

        clickEventName = clickEventsIterator.next();
        assertEquals("action", clickEventName.getName());
        assertTrue(clickEventName.isDefaultEvent());

        assertFalse(clickEventsIterator.hasNext());
    }

    /**
     * @param requiredAttribute
     */
    private void checkDummyComponentRequiredAttribute(PropertyBase requiredAttribute) {
        assertNotNull(requiredAttribute);
        assertNoEventNames(requiredAttribute);
        assertNoSignature(requiredAttribute);
        assertNoDefaultValue(requiredAttribute);

        assertTrue(requiredAttribute.isRequired());
        assertEquals("That's a required attribute", requiredAttribute.getDescription());
        assertEquals("Required Attribute", requiredAttribute.getDisplayName());
    }

    @Test
    // TODO - fix import-attributes.
    public void dummyComponentTest() throws Exception {
        expect(resolver.getExternalSubset(EasyMock.<String>eq("cdk:root"), (String) anyObject())).andReturn(null);
        expect(
                resolver.resolveEntity((String) isNull(),
                        eq("urn:resource:org/richfaces/cdk/templatecompiler/dummy-attributes.xml")))
                .andReturn(
                        new InputSource(this.getClass().getResourceAsStream(
                                "/org/richfaces/cdk/templatecompiler/dummy-attributes.xml")));
        expect(resolver.getExternalSubset(EasyMock.<String>eq("cdk:properties"), (String) isNull())).andReturn(null);
        expect(resolver.resolveEntity((String) isNull(), eq("urn:attributes:dummy-template-props.xml"))).andReturn(
                new InputSource(this.getClass().getResourceAsStream("/META-INF/cdk/attributes/dummy-template-props.xml")));
        expect(resolver.getExternalSubset(EasyMock.<String>eq("cdk:properties"), (String) isNull())).andReturn(null);

        replay(resolver, templatesSource);
        Template template = parser.parseTemplate(getJavaFile("org/richfaces/cdk/templatecompiler/dummy.template.xml"));
        assertNotNull(template);

        parser.mergeTemplateIntoModel(template, null);
        verify(resolver, templatesSource);

        RenderKitModel renderKit = getRenderkitFromModel("org.richfaces.CUSTOM_RENDERKIT");
        RendererModel renderer = getFirstRendererFromRenderkit(renderKit);

        assertEquals(new ClassName("org.richfaces.renderkit.html.DummyRendererImpl"), renderer.getRendererClass());

        assertTrue(renderer.isRendersChildren());
        assertEquals(FacesId.parseId("org.richfaces.Dummy"), renderer.getFamily());
        assertSame(template, renderer.getTemplate());

        Collection<PropertyBase> attributes = renderer.getAttributes();
        assertNotNull(attributes);

        checkDummyComponentOnclick(getAttribute(attributes, "onclick"));
        checkDummyComponentOnchange(getAttribute(attributes, "onchange"));
        checkDummyComponentAction(getAttribute(attributes, "action"));
        checkDummyComponentActionListener(getAttribute(attributes, "actionListener"));
        checkDummyComponentCoolMethod(getAttribute(attributes, "coolMethod"));
        checkDummyComponentIntegerAttribute(getAttribute(attributes, "integerAttribute"));
        checkDummyComponentRequiredAttribute(getAttribute(attributes, "requiredAttribute"));

        checkDummyComponentImportedAttribute(getAttribute(attributes, "anotherImportedStringProperty"), String.class);
        checkDummyComponentImportedAttribute(getAttribute(attributes, "anotherImportedProperty"), Object.class);
        checkDummyComponentImportedAttribute(getAttribute(attributes, "importedBooleanProperty"), boolean.class);
        checkDummyComponentImportedAttribute(getAttribute(attributes, "importedIntegerProperty"), Integer.class);

        assertEquals(11, attributes.size());
    }

    private PropertyBase getAttribute(Collection<PropertyBase> attributes, String string) {
        for (PropertyBase property : attributes) {
            if (string.equals(property.getName())) {
                return property;
            }
        }
        return null;
    }

    private RendererModel getFirstRendererFromRenderkit(RenderKitModel renderKit) {
        Collection<RendererModel> renderers = renderKit.getRenderers();
        assertNotNull(renderers);

        Iterator<RendererModel> renderersIterator = renderers.iterator();
        assertTrue(renderersIterator.hasNext());

        RendererModel renderer = renderersIterator.next();
        assertNotNull(renderer);

        return renderer;
    }

    private RenderKitModel getRenderkitFromModel(String renderkitId) {
        RenderKitModel renderKit = library.getRenderKit(new FacesId(renderkitId));
        assertNotNull(renderKit);

        return renderKit;
    }
}
