package de.davherrmann.efficiently.view;

import static com.google.common.collect.Lists.newArrayList;
import static de.davherrmann.efficiently.view.ComponentsTest.TestElement.TESTELEMENT;
import static java.lang.String.format;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;
import static org.junit.rules.ExpectedException.none;

import java.util.function.Supplier;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

import com.google.common.collect.ImmutableMap;

import de.davherrmann.efficiently.view.ComponentsTest.TestElement.TestElementProperties;
import de.davherrmann.immutable.PathRecorder;

public class ComponentsTest
{
    @Rule
    public ExpectedException thrown = none();

    private final Components components = new Components(State.class);
    private final State path = PathRecorder.pathInstanceFor(State.class);

    @Test
    public void create_addsElementTypeInformation() throws Exception
    {
        // given
        final Element element = create(TESTELEMENT);

        // then
        assertThat(element.template(), is(ImmutableMap.builder() //
            .put("type", "TestElement") //
            .build()));
    }
    @Test
    public void bindingDirectProperty_worksWithNonNestedBinding() throws Exception
    {
        // given
        final TestElement element = create(TESTELEMENT);

        // when
        element.title(path::specialTitle);

        // then
        assertThat(element.template(), is(ImmutableMap.builder() //
            .put("type", "TestElement") //
            .put("title", "specialTitle") //
            .build()));
    }

    @Test
    public void bindingDirectProperty_worksWithNestedBinding() throws Exception
    {
        // given
        final TestElement element = create(TESTELEMENT);

        // when
        element.title(path.nested()::specialTitle);

        // then
        assertThat(element.template(), is(ImmutableMap.builder() //
            .put("type", "TestElement") //
            .put("title", "nested.specialTitle") //
            .build()));
    }

    @Test
    @SuppressWarnings("unchecked")
    public void content_addsNestedElement() throws Exception
    {
        // given
        final TestElement element = create(TESTELEMENT);

        // when
        element.content(create(TESTELEMENT));

        // then
        assertThat(element.template(), is(ImmutableMap.builder() //
            .put("type", "TestElement") //
            .put("content", newArrayList( //
                ImmutableMap.builder() //
                    .put("type", "TestElement") //
                    .build())) //
            .build()));
    }

    @Test
    public void bindAll_createsAllBindings_whenElementIsABindable() throws Exception
    {
        // given
        final TestElement element = create(TESTELEMENT);

        // when
        element.bindAll(path::testElementState);

        // then
        assertThat(element.template(), is(ImmutableMap.builder() //
            .put("type", "TestElement") //
            .put("title", "testElementState.title") //
            .build()));
    }

    @Test
    public void bindAll_createsAllBindings_whenSuperElementIsABindable() throws Exception
    {
        // given
        final ExtendedTestElement element = create(ExtendedTestElement.class);

        // when
        // TODO we should forbid extending elements:
        // reason: ExtendedTestElement.bindAll(...) returns TestElement!
        element.bindAll(path::testElementState);

        // then
        assertThat(element.template(), is(ImmutableMap.builder() //
            .put("type", "ExtendedTestElement") //
            .put("title", "testElementState.title") //
            .build()));
    }

    @Test
    public void bind_createsSpecificBinding() throws Exception
    {
        // given
        final TestElement element = create(TESTELEMENT);

        // when
        element.bind(properties -> properties::title).to(path::specialTitle);

        // then
        assertThat(element.template(), is(ImmutableMap.builder() //
            .put("type", "TestElement") //
            .put("title", "specialTitle") //
            .build()));
    }

    @Test
    public void usingUnsupportedMethod_throwsMeaningfulException() throws Exception
    {
        // TODO add documentation to Element
        // then
        thrown.expect(UnsupportedOperationException.class);
        thrown.expectMessage(format("Method '%s#%s' is not supported. Please see %s for more information.", //
            TestElement.class.getName(), //
            "unsupportedMethod", //
            Element.class.getName()));

        // when
        create(TESTELEMENT).unsupportedMethod("Foo");
    }

    public interface TestElement
        extends HasContent<TestElement>, Bindable<TestElement, TestElement.TestElementProperties>
    {
        Class<TestElement> TESTELEMENT = TestElement.class;

        TestElement title(Supplier<String> title);

        TestElement unsupportedMethod(String unsupported);

        interface TestElementProperties
        {
            String title();
        }
    }

    public interface ExtendedTestElement extends TestElement
    {
    }

    private interface State
    {
        String specialTitle();

        State nested();

        TestElementProperties testElementState();
    }

    private <T extends Element> T create(Class<T> type)
    {
        return components.create(type);
    }
}
@@ -357,16 +357,38 @@
 .none;%0A%0A
+import java.util.Map;%0A
 import j
@@ -415,16 +415,16 @@
 pplier;%0A
-
 %0Aimport 
@@ -1614,32 +1614,45 @@
   .put(%22title%22, 
+noDerivation(
 %22specialTitle%22) 
@@ -1642,32 +1642,33 @@
 (%22specialTitle%22)
+)
  //%0A            
@@ -2079,16 +2079,29 @@
 title%22, 
+noDerivation(
 %22nested.
@@ -2106,32 +2106,33 @@
 d.specialTitle%22)
+)
  //%0A            
@@ -3117,32 +3117,45 @@
   .put(%22title%22, 
+noDerivation(
 %22testElementStat
@@ -3155,32 +3155,33 @@
 entState.title%22)
+)
  //%0A            
@@ -3755,16 +3755,29 @@
 title%22, 
+noDerivation(
 %22testEle
@@ -3789,24 +3789,25 @@
 tate.title%22)
+)
  //%0A        
@@ -4231,16 +4231,29 @@
 title%22, 
+noDerivation(
 %22special
@@ -4259,16 +4259,17 @@
 lTitle%22)
+)
  //%0A    
@@ -4755,32 +4755,32 @@
         // when%0A
-
         create(T
@@ -4816,32 +4816,267 @@
 (%22Foo%22);%0A    %7D%0A%0A
+    private Map%3CString, String%3E noDerivation(final String value)%0A    %7B%0A        return ImmutableMap.%3CString, String%3Ebuilder() //%0A            .put(%22name%22, %22none%22) //%0A            .put(%22sourceValue%22, value) //%0A            .build();%0A    %7D%0A%0A
     public inter

fix test - consider derivation wrapper