/*
 * Copyright 2002-2008 the original author or authors.
 *
 * 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.springframework.config.java.internal.parsing.asm;

import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.*;
import static org.springframework.config.java.internal.parsing.asm.MutableAnnotationUtils.createMutableAnnotation;
import static org.springframework.config.java.internal.util.AnnotationExtractionUtils.extractMethodAnnotation;

import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;

import org.junit.Ignore;
import org.junit.Test;
import org.springframework.config.java.annotation.Bean;
import org.springframework.config.java.annotation.Meta;
import org.springframework.config.java.internal.util.MethodAnnotationPrototype;


/** TODO: JAVADOC */
public class MutableAnnotationTests {

    @Test
    public void testFooAnnoWithDefaultAttrib() {
        @Foo
        class Config { }

        Foo expected = Config.class.getAnnotation(Foo.class);

        Foo actual = createMutableAnnotation(Foo.class);

        assertEquals("forward equals failed: ", expected, actual);
        assertEquals("reverse equals failed: ", actual, expected);
    }

    @Test
    public void testFooAnnoWithNonDefaultAttrib() {
        @Foo(b = false)
        class Config { }

        Foo expected = Config.class.getAnnotation(Foo.class);

        Foo actual = createMutableAnnotation(Foo.class);
        ((MutableAnnotation)actual).setAttributeValue("b", false);

        assertEquals("forward equals failed: ", expected, actual);
        assertEquals("reverse equals failed: ", actual, expected);
    }

    @Test
    public void testToString() {
        Foo foo = createMutableAnnotation(Foo.class);
        ((MutableAnnotation)foo).setAttributeValue("b", false);
        String actual = foo.toString();

        assertTrue(actual.contains("@" + Foo.class.getName()));
        assertTrue(actual.contains("a=true"));
        assertTrue(actual.contains("b=false"));
    }

    /**
     * Mutable annotations should follow the Annotation equals contract precisely.
     *
     * @see  Annotation#equals(Object)
     */
    @Test
    public void testEquals() {
        Bean a1 = createMutableAnnotation(Bean.class);
        assertTrue(a1.equals(a1));

        Bean a2 = createMutableAnnotation(Bean.class);
        assertTrue(a1.equals(a2));
        assertTrue(a2.equals(a1));

        assertFalse(a1.equals(null));

        ((MutableAnnotation)a1).setAttributeValue("allowOverriding", false); // not the default value
        assertFalse(a1.equals(a2));
        ((MutableAnnotation)a2).setAttributeValue("allowOverriding", false); // not the default value
        assertTrue(a1.equals(a2));

        ((MutableAnnotation)a1).setAttributeValue("allowOverriding", true); // return to default state
        ((MutableAnnotation)a2).setAttributeValue("allowOverriding", true); // return to default state

        Bean real1 = extractMethodAnnotation(Bean.class, new MethodAnnotationPrototype() {
                @Bean
                public void targetMethod() { }
            }.getClass());

        assertTrue(real1.equals(a1));
        assertTrue(a1.equals(real1));

        ((MutableAnnotation)a1).setAttributeValue("allowOverriding", false); // just change something on a1 ...
        assertFalse(real1.equals(a1)); // and now the two should
        assertFalse(a1.equals(real1)); // no longer be equal

        // now create a 'real' annotation that matches a1's current settings
        Bean real2 = extractMethodAnnotation(Bean.class, new MethodAnnotationPrototype() {
                @Bean(allowOverriding = false)
                public void targetMethod() { }
            }.getClass());

        assertTrue(real2.equals(a1)); // and they should then
        assertTrue(a1.equals(real2)); // be equivalent

        // just for good measure, change a1's allowOverriding back to 'true'
        ((MutableAnnotation)a1).setAttributeValue("allowOverriding", true);
        assertFalse(real2.equals(a1)); // and prove that they are unequal
        assertFalse(a1.equals(real2));
    }

    /**
     * Ensure that special rules around double/float are supported.
     *
     * @see  Annotation#equals(Object)
     */
    @Test
    public void testSpecialNumberEquals() {

        NumberAnno real = extractMethodAnnotation(NumberAnno.class, new MethodAnnotationPrototype() {
                @NumberAnno
                public void targetMethod() { }
            }.getClass());

        NumberAnno mutable = createMutableAnnotation(NumberAnno.class);

        assertTrue(mutable.equals(real));
        assertTrue(real.equals(mutable));

        ((MutableAnnotation) mutable).setAttributeValue("d", Double.MAX_VALUE);
        assertFalse(mutable.equals(real));
        assertFalse(real.equals(mutable));

        real = extractMethodAnnotation(NumberAnno.class, new MethodAnnotationPrototype() {
                @NumberAnno(d = Double.MAX_VALUE)
                public void targetMethod() { }
            }.getClass());

        assertTrue(mutable.equals(real));
        assertTrue(real.equals(mutable));

        ((MutableAnnotation) mutable).setAttributeValue("f", Float.MAX_VALUE);
        assertFalse(mutable.equals(real));
        assertFalse(real.equals(mutable));

        real = extractMethodAnnotation(NumberAnno.class, new MethodAnnotationPrototype() {
                @NumberAnno(d = Double.MAX_VALUE, f = Float.MAX_VALUE)
                public void targetMethod() { }
            }.getClass());

        assertTrue(mutable.equals(real));
        assertTrue(real.equals(mutable));
    }

    @Retention(RetentionPolicy.RUNTIME)
    static @interface NumberAnno {
        double d() default 1d;
        float f() default 1f;
    }


    @Test
    public void testHashCode() {
        Simple real = extractMethodAnnotation(Simple.class, new MethodAnnotationPrototype() {
                @Simple
                public void targetMethod() { }
            }.getClass());

        Simple mutable = createMutableAnnotation(Simple.class);

        assertEquals(real.hashCode(), mutable.hashCode());

        ((MutableAnnotation)mutable).setAttributeValue("strings", new String[] { "bogus" });
        assertThat(real.hashCode(), not(equalTo(mutable.hashCode())));

        real = extractMethodAnnotation(Simple.class, new MethodAnnotationPrototype() {
                @Simple(strings = { "bogus" })
                public void targetMethod() { }
            }.getClass());

        assertThat(real.hashCode(), equalTo(mutable.hashCode()));
    }

    @Retention(RetentionPolicy.RUNTIME)
    static @interface Simple {
        int i() default 1;
        double d() default 1;
        String s() default "foo";
        String[] strings() default { "foo", "bar" };
        Meta[] meta() default { };
    }

    @Ignore @Test
    public void delta() {
        Bean anno = extractMethodAnnotation(Bean.class, new MethodAnnotationPrototype() {
                @Bean
                public void targetMethod() { }
            }.getClass());

        assertArrayEquals(anno.dependsOn(), createMutableAnnotation(Bean.class).dependsOn());
        assertEquals(anno, createMutableAnnotation(Bean.class));
        assertEquals(createMutableAnnotation(Bean.class), anno);

        assertArrayEquals(new Bean[] { anno }, new Bean[] { createMutableAnnotation(Bean.class) });

        ArrayList<Bean> one = new ArrayList<Bean>();
        one.add(anno);
        ArrayList<Bean> two = new ArrayList<Bean>();
        two.add(createMutableAnnotation(Bean.class));
        assertEquals(one, two);

        assertEquals(createMutableAnnotation(Bean.class), createMutableAnnotation(Bean.class));

        // HashSet<Bean> h1 = new HashSet<Bean>();
        // h1.add(new MutableBean());
        // HashSet<Bean> h2 = new HashSet<Bean>();
        // h2.add(new MutableBean());
        // assertEquals(h1, h2);
    }

}

@Retention(RetentionPolicy.RUNTIME)
@interface Foo {
    boolean c() default true;
    boolean b() default true;
    boolean a() default true;
}
