/**
 * Copyright 2011-2015 John Ericksen
 *
 * 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.parceler;

import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

/**
 * @author John Ericksen
 */
@RunWith(RobolectricTestRunner.class)
@Config(manifest=Config.NONE)
public class CircularReferenceTest {

    @Parcel
    public static class One {
        Two two;
        int four;

        @ParcelConstructor
        public One(int four, Two two) {
            this.two = two;
            this.four = four;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof One)) return false;
            One one = (One) o;
            return EqualsBuilder.reflectionEquals(this, one);
        }

        @Override
        public int hashCode() {
            return HashCodeBuilder.reflectionHashCode(this);
        }
    }

    @Parcel
    public static class Two {
        One one;
        String three;

        @ParcelConstructor
        public Two(String three, One one) {
            this.one = one;
            this.three = three;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof Two)) return false;
            Two two = (Two) o;
            return EqualsBuilder.reflectionEquals(this, two);
        }

        @Override
        public int hashCode() {
            return HashCodeBuilder.reflectionHashCode(this);
        }
    }

    @Test
    public void testCircularReferences() {
        One target = new One(1,
                new Two("two",
                        new One(3,
                                new Two("four",
                                        new One(5,
                                                new Two("six", null))))));

        One unwrap = Parcels.unwrap(ParcelsTestUtil.wrap(target));

        assertEquals(target, unwrap);
    }

    @Parcel
    static class Three {
        Four four;
        String name;

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof Three)) return false;
            Three three = (Three) o;
            return EqualsBuilder.reflectionEquals(this, three, new String[]{"four"});
        }

        @Override
        public int hashCode() {
            return HashCodeBuilder.reflectionHashCode(this, new String[]{"four"});
        }
    }

    @Parcel
    static class Four {
        Three three;
        String name;

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof Four)) return false;
            Four four = (Four) o;
            return EqualsBuilder.reflectionEquals(this, four, new String[]{"three"});
        }

        @Override
        public int hashCode() {
            return HashCodeBuilder.reflectionHashCode(this, new String[]{"three"});
        }
    }

    @Test
    public void testCircularInstances() {
        Three three = new Three();
        Four four = new Four();

        three.name = "3";
        four.name = "4";

        three.four = four;
        four.three = three;

        Three unwrap = Parcels.unwrap(ParcelsTestUtil.wrap(three));

        assertEquals(three, unwrap);
        assertEquals(four, three.four);
    }

    @Parcel
    static class Five {
        Six a;
        Six b;
        Six c;
        String name;

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof Five)) return false;
            Five five = (Five) o;
            return EqualsBuilder.reflectionEquals(this, five, new String[]{"a", "b", "c"});
        }

        @Override
        public int hashCode() {
            return HashCodeBuilder.reflectionHashCode(this, new String[]{"a", "b", "c"});
        }
    }

    @Parcel
    static class Six{
        String name;

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof Six)) return false;
            Six six = (Six) o;
            return EqualsBuilder.reflectionEquals(this, six);
        }

        @Override
        public int hashCode() {
            return HashCodeBuilder.reflectionHashCode(this);
        }
    }

    @Test
    public void testMultipleReferences() {
        Five five = new Five();
        five.name = "5";
        Six six = new Six();
        five.a = six;
        five.b = six;
        five.c = six;
        six.name = "6";

        Five unwrap = Parcels.unwrap(ParcelsTestUtil.wrap(five));

        assertEquals(five, unwrap);
        assertEquals(five.a, unwrap.a);
        assertEquals(five.b, unwrap.b);
        assertEquals(five.c, unwrap.c);
        assertEquals(unwrap.a, unwrap.b);
        assertEquals(unwrap.a, unwrap.c);
    }

    @Parcel
    static class Seven {
        Eight eight;
        String name;
        @ParcelConstructor Seven(Eight eight, String name){
            this.eight = eight;
            this.name = name;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof Seven)) return false;
            Seven seven = (Seven) o;
            return EqualsBuilder.reflectionEquals(this, seven);
        }

        @Override
        public int hashCode() {
            return HashCodeBuilder.reflectionHashCode(this);
        }
    }

    @Parcel
    static class Eight {
        Seven seven;
        String name;

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof Eight)) return false;
            Eight eight = (Eight) o;
            return EqualsBuilder.reflectionEquals(this, eight, new String[]{"seven"});
        }

        @Override
        public int hashCode() {
            return HashCodeBuilder.reflectionHashCode(this, new String[]{"seven"});
        }
    }

    @Test
    public void testCircularConstructor(){
        Eight eight = new Eight();
        eight.name = "8";
        Seven seven = new Seven(eight, "7");
        eight.seven = seven;

        Eight unwrappedEight = Parcels.unwrap(ParcelsTestUtil.wrap(eight));

        assertEquals(eight, unwrappedEight);
        assertEquals(seven, unwrappedEight.seven);
        assertEquals(eight, unwrappedEight.seven.eight);

        try {
            Parcels.unwrap(ParcelsTestUtil.wrap(seven));
            assertTrue("Parcels.unwrap did not throw an exception", false);
        }
        catch (ParcelerRuntimeException e){}
    }

    @Parcel
    static class Nine {
        Ten ten;
        String name;

        public Nine(Ten ten, String name) {
            this.ten = ten;
            this.name = name;
        }

        @ParcelFactory
        public static Nine build(Ten ten, String name){
            return new Nine(ten, name);
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof Nine)) return false;
            Nine nine = (Nine) o;
            return EqualsBuilder.reflectionEquals(this, nine, new String[]{"ten"});
        }

        @Override
        public int hashCode() {
            return HashCodeBuilder.reflectionHashCode(this, new String[]{"ten"});
        }
    }

    @Parcel
    static class Ten {
        Nine nine;
        String name;

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof Ten)) return false;

            Ten ten = (Ten) o;

            return EqualsBuilder.reflectionEquals(this, ten, new String[]{"nine"});
        }

        @Override
        public int hashCode() {
            return HashCodeBuilder.reflectionHashCode(this, new String[]{"nine"});
        }
    }

    @Test
    public void testCircularFactory(){
        Ten ten = new Ten();
        ten.name = "10";
        Nine nine = Nine.build(ten, "9");
        ten.nine = nine;

        Ten unwrappedTen = Parcels.unwrap(ParcelsTestUtil.wrap(ten));

        assertEquals(ten, unwrappedTen);
        assertEquals(ten, unwrappedTen.nine.ten);
        assertEquals(nine, unwrappedTen.nine);

        try {
            Parcels.unwrap(ParcelsTestUtil.wrap(nine));
            assertTrue("Parcels.unwrap did not throw an exception", false);
        }
        catch (ParcelerRuntimeException e){}
    }
}