package com.fasterxml.jackson.failing;

import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;

import com.fasterxml.jackson.databind.BaseMapTest;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.objectid.TestObjectId.Employee;
import com.fasterxml.jackson.failing.TestObjectIdDeserialization.EnumMapCompany.FooEnum;

/**
 * Unit test to verify handling of Object Id deserialization.
 *<p>
 * NOTE: only tests that still fail, even with initial forward-ref-handling
 * code (2.4), are included here. Other cases moved to successfully
 * passing tests.
 */
public class TestObjectIdDeserialization extends BaseMapTest
{
    static class ArrayCompany {
        public Employee[] employees;
    }

    static class ArrayBlockingQueueCompany {
        public ArrayBlockingQueue<Employee> employees;
    }

    static class EnumMapCompany {
        public EnumMap<FooEnum,Employee> employees;

        static enum FooEnum {
            A, B
        }
    }

    static class DefensiveCompany {
        public List<DefensiveEmployee> employees;

        static class DefensiveEmployee extends Employee {

            public void setReports(List<DefensiveEmployee> reports)
            {
                this.reports = new ArrayList<Employee>(reports);
            }
        }
    }

    private final ObjectMapper mapper = new ObjectMapper();

    /*
    /*****************************************************
    /* Unit tests, external id deserialization
    /*****************************************************
     */

    public void testForwardReferenceInArray() throws Exception
    {
        String json = "{\"employees\":["
                      + "{\"id\":1,\"name\":\"First\",\"manager\":null,\"reports\":[2]},"
                      + "2,"
                      +"{\"id\":2,\"name\":\"Second\",\"manager\":1,\"reports\":[]}"
                      + "]}";
        ArrayCompany company = mapper.readValue(json, ArrayCompany.class);
        assertEquals(3, company.employees.length);
        Employee firstEmployee = company.employees[0];
        Employee secondEmployee = company.employees[1];
        assertEmployees(firstEmployee, secondEmployee);
    }

    // Do a specific test for ArrayBlockingQueue since it has its own deser.
    public void testForwardReferenceInQueue() throws Exception
    {
        String json = "{\"employees\":["
                      + "{\"id\":1,\"name\":\"First\",\"manager\":null,\"reports\":[2]},"
                      + "2,"
                      +"{\"id\":2,\"name\":\"Second\",\"manager\":1,\"reports\":[]}"
                      + "]}";
        ArrayBlockingQueueCompany company = mapper.readValue(json, ArrayBlockingQueueCompany.class);
        assertEquals(3, company.employees.size());
        Employee firstEmployee = company.employees.take();
        Employee secondEmployee = company.employees.take();
        assertEmployees(firstEmployee, secondEmployee);
    }

    public void testForwardReferenceInEnumMap()
        throws Exception
   {
        String json = "{\"employees\":{"
                      + "\"A\":{\"id\":1,\"name\":\"First\",\"manager\":null,\"reports\":[2]},"
                      + "\"B\": 2,"
                      + "\"C\":{\"id\":2,\"name\":\"Second\",\"manager\":1,\"reports\":[]}"
                      + "}}";
        EnumMapCompany company = mapper.readValue(json, EnumMapCompany.class);
        assertEquals(3, company.employees.size());
        Employee firstEmployee = company.employees.get(FooEnum.A);
        Employee secondEmployee = company.employees.get(FooEnum.B);
        assertEmployees(firstEmployee, secondEmployee);
    }

    public void testForwardReferenceWithDefensiveCopy()
        throws Exception
    {
        String json = "{\"employees\":[" + "{\"id\":1,\"name\":\"First\",\"manager\":null,\"reports\":[2]},"
                + "{\"id\":2,\"name\":\"Second\",\"manager\":1,\"reports\":[]}" + "]}";
        DefensiveCompany company = mapper.readValue(json, DefensiveCompany.class);
        assertEquals(2, company.employees.size());
        Employee firstEmployee = company.employees.get(0);
        Employee secondEmployee = company.employees.get(1);
        assertEmployees(firstEmployee, secondEmployee);
    }

    private void assertEmployees(Employee firstEmployee, Employee secondEmployee)
    {
        assertEquals(1, firstEmployee.id);
        assertEquals(2, secondEmployee.id);
        assertEquals(1, firstEmployee.reports.size());
        assertSame(secondEmployee, firstEmployee.reports.get(0)); // Ensure that forward reference was properly resolved and in order.
        assertSame(firstEmployee, secondEmployee.manager); // And that back reference is also properly resolved.
    }
}
