package com.fasterxml.jackson.databind.struct;

import java.io.IOException;
import java.util.Map;
import java.util.TreeMap;

import com.fasterxml.jackson.annotation.*;

import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;

// Unit test for [JACKSON-890]
public class TestBackRefsWithPolymorphic extends BaseMapTest
{
    @JsonPropertyOrder(alphabetic=true)
    interface Entity
    {
        @JsonIgnore String getEntityType();
        Long getId();
        void setId(Long id);
        @JsonIgnore void setPersistable();
    }

    @JsonDeserialize(as = NestedPropertySheetImpl.class)
    interface NestedPropertySheet
        extends Property<PropertySheet>
    {
        @Override PropertySheet getValue();
        void setValue(PropertySheet propertySheet);
    }

    @JsonDeserialize(as = AbstractProperty.class)
    @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS,
        include  = JsonTypeInfo.As.PROPERTY,
        property = "@class")
    interface Property<T> extends Entity
    {
        String getName();
        PropertySheet getParentSheet();
        T getValue();
        void setName(String name);
        void setParentSheet(PropertySheet parentSheet);
    }

    @JsonDeserialize(as = PropertySheetImpl.class)
    @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS,
        include  = JsonTypeInfo.As.PROPERTY,
        property = "@class")
    @SuppressWarnings("rawtypes")
    interface PropertySheet extends Entity
    {
        void addProperty(Property property);
        Map<String, Property> getProperties();
        void setProperties(Map<String, Property> properties);
    }

    @JsonDeserialize(as = StringPropertyImpl.class)
    interface StringProperty
        extends Property<String>
    {
        @Override String getValue();
        void setValue(String value);
    }

    static class AbstractEntity implements Entity
    {
        private long id;

        @Override public String getEntityType() {
            return "";
        }

        @Override public Long getId() {
            return id;
        }

        @Override public void setId(Long id)
        {
            this.id = id;
        }

        @Override public void setPersistable() { }
    }

    abstract static class AbstractProperty<T>
        extends AbstractEntity
        implements Property<T>
    {
        private String        m_name;
        private PropertySheet m_parentSheet;

        protected AbstractProperty() { }

        protected AbstractProperty(String name) {
            m_name = name;
        }

        @Override public String getName() {
            return m_name;
        }

        @JsonBackReference("propertySheet-properties")
        @Override public PropertySheet getParentSheet() {
            return m_parentSheet;
        }

        @Override public void setName(String name) {
            m_name = name;
        }

        @Override public void setParentSheet(PropertySheet parentSheet) {
            m_parentSheet = parentSheet;
        }
    }

    @JsonPropertyOrder(alphabetic=true)
    static class NestedPropertySheetImpl
        extends AbstractProperty<PropertySheet>
        implements NestedPropertySheet
    {
        private PropertySheet m_propertySheet;

        protected NestedPropertySheetImpl(String name,
                PropertySheet propertySheet)
        {
            super(name);
            m_propertySheet = propertySheet;
        }

        NestedPropertySheetImpl() { }

        @Override public PropertySheet getValue() {
            return m_propertySheet;
        }

        @Override public void setValue(PropertySheet propertySheet) {
            m_propertySheet = propertySheet;
        }
    }

    @SuppressWarnings("rawtypes")
    static class PropertySheetImpl
        extends AbstractEntity
        implements PropertySheet
    {
        private Map<String, Property> m_properties;

        @Override public void addProperty(Property property)
        {
            if (m_properties == null) {
                m_properties = new TreeMap<String, Property>();
            }
            property.setParentSheet(this);
            m_properties.put(property.getName(), property);
        }

        @JsonDeserialize(as = TreeMap.class,
            keyAs     = String.class,
            contentAs = Property.class)
        @JsonManagedReference("propertySheet-properties")
        @Override public Map<String, Property> getProperties() {
            return m_properties;
        }

        @Override public void setProperties(Map<String, Property> properties) {
            m_properties = properties;
        }
    }

    static class StringPropertyImpl
        extends AbstractProperty<String>
        implements StringProperty
    {
        private String m_value;

        public StringPropertyImpl(String name, String value) {
            super(name);
            m_value = value;
        }

        StringPropertyImpl() { }

        @Override public String getValue() {
            return m_value;
        }

        @Override public void setValue(String value) {
            m_value = value;
        }
    }

    static class YetAnotherClass extends StringPropertyImpl { }

    /*
    /**********************************************************
    /* Test methods
    /**********************************************************
     */

    private final String CLASS_NAME = getClass().getName();
    // NOTE: order is arbitrary, test is fragile... has to work for now
    private final String JSON =
        "{\"@class\":\""+CLASS_NAME+"$PropertySheetImpl\",\"id\":0,\"properties\":{\"p1name\":{\"@class\":"
            +"\"" +CLASS_NAME+ "$StringPropertyImpl\",\"id\":0,\"name\":\"p1name\",\"value\":\"p1value\"},"
            +"\"p2name\":{\"@class\":\""+CLASS_NAME+"$StringPropertyImpl\",\"id\":0,"
            +"\"name\":\"p2name\",\"value\":\"p2value\"}}}";
    
    private final ObjectMapper MAPPER = new ObjectMapper();

    public void testDeserialize() throws IOException
    {
        PropertySheet input = MAPPER.readValue(JSON, PropertySheet.class);
        assertEquals(JSON, MAPPER.writeValueAsString(input));
    }

    public void testSerialize() throws IOException
    {
        PropertySheet sheet = new PropertySheetImpl();

        sheet.addProperty(new StringPropertyImpl("p1name", "p1value"));
        sheet.addProperty(new StringPropertyImpl("p2name", "p2value"));
        String actual = MAPPER.writeValueAsString(sheet);
        assertEquals(JSON, actual);
    }
}
