package com.fasterxml.jackson.databind.struct;

import com.fasterxml.jackson.annotation.*;

import com.fasterxml.jackson.databind.BaseMapTest;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.exc.MismatchedInputException;
import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;

/**
 * Unit tests for "POJO as array" feature using Builder-style
 * POJO construction.
 */
public class TestPOJOAsArrayWithBuilder extends BaseMapTest
{
    @JsonDeserialize(builder=SimpleBuilderXY.class)
    @JsonFormat(shape=JsonFormat.Shape.ARRAY)
    @JsonPropertyOrder(alphabetic=true)
    static class ValueClassXY
    {
        final int _x, _y;

        protected ValueClassXY(int x, int y) {
            _x = x+1;
            _y = y+1;
        }
    }

    @JsonFormat(shape=JsonFormat.Shape.ARRAY)
    static class SimpleBuilderXY
    {
        public int x, y;

        protected SimpleBuilderXY() { }
        protected SimpleBuilderXY(int x0, int y0) {
            x = x0;
            y = y0;
        }
        
        public SimpleBuilderXY withX(int x0) {
            this.x = x0;
            return this;
        }

        public SimpleBuilderXY withY(int y0) {
            this.y = y0;
            return this;
        }

        public ValueClassXY build() {
            return new ValueClassXY(x, y);
        }
    }

    // Also, with creator:

    @JsonDeserialize(builder=CreatorBuilder.class)
    @JsonFormat(shape=JsonFormat.Shape.ARRAY)
    @JsonPropertyOrder(alphabetic=true)
    static class CreatorValue
    {
        final int a, b, c;

        protected CreatorValue(int a, int b, int c) {
            this.a = a;
            this.b = b;
            this.c = c;
        }
    }

    @JsonFormat(shape=JsonFormat.Shape.ARRAY)
    static class CreatorBuilder {
        private final int a, b;

        private int c;

        @JsonCreator
        public CreatorBuilder(@JsonProperty("a") int a,
                @JsonProperty("b") int b)
        {
            this.a = a;
            this.b = b;
        }
        
        @JsonView(String.class)
        public CreatorBuilder withC(int v) {
            c = v;
            return this;
        }

        public CreatorValue build() {
            return new CreatorValue(a, b, c);
        }
    }

    /*
    /*****************************************************
    /* Basic tests
    /*****************************************************
     */

    private final static ObjectMapper MAPPER = new ObjectMapper();

    public void testSimpleBuilder() throws Exception
    {
        // Ok, first, ensure that serializer will "black out" filtered properties
        ValueClassXY value = MAPPER.readValue("[1,2]", ValueClassXY.class);
        assertEquals(2, value._x);
        assertEquals(3, value._y);
    }

    // Won't work, but verify exception
    public void testBuilderWithUpdate() throws Exception
    {
        // Ok, first, simple case of all values being present
        try {
            /*value =*/ MAPPER.readerFor(ValueClassXY.class)
                    .withValueToUpdate(new ValueClassXY(6, 7))
                    .readValue("[1,2]");
            fail("Should not pass");
        } catch (InvalidDefinitionException e) {
            verifyException(e, "Deserialization of");
            verifyException(e, "by passing existing instance");
            verifyException(e, "ValueClassXY");
        }
    }

    /*
    /*****************************************************
    /* Creator test(s)
    /*****************************************************
     */
    
    // test to ensure @JsonCreator also works
    public void testWithCreator() throws Exception
    {
        CreatorValue value = MAPPER.readValue("[1,2,3]", CreatorValue.class);
        assertEquals(1, value.a);
        assertEquals(2, value.b);
        assertEquals(3, value.c);

        // and should be ok with partial too?
        value = MAPPER.readValue("[1,2]", CreatorValue.class);
        assertEquals(1, value.a);
        assertEquals(2, value.b);
        assertEquals(0, value.c);

        value = MAPPER.readValue("[1]", CreatorValue.class);
        assertEquals(1, value.a);
        assertEquals(0, value.b);
        assertEquals(0, value.c);

        value = MAPPER.readValue("[]", CreatorValue.class);
        assertEquals(0, value.a);
        assertEquals(0, value.b);
        assertEquals(0, value.c);
    }

    public void testWithCreatorAndView() throws Exception
    {
        ObjectReader reader = MAPPER.readerFor(CreatorValue.class);
        CreatorValue value;

        // First including values in view
        value = reader.withView(String.class).readValue("[1,2,3]");
        assertEquals(1, value.a);
        assertEquals(2, value.b);
        assertEquals(3, value.c);

        // then not including view
        value = reader.withView(Character.class).readValue("[1,2,3]");
        assertEquals(1, value.a);
        assertEquals(2, value.b);
        assertEquals(0, value.c);
    }

    /*
    /*****************************************************
    /* Failure tests
    /*****************************************************
     */

    public void testUnknownExtraProp() throws Exception
    {
        String json = "[1, 2, 3, 4]";
        try {
            MAPPER.readValue(json, ValueClassXY.class);
            fail("should not pass with extra element");
        } catch (MismatchedInputException e) {
            verifyException(e, "Unexpected JSON values");
        }

        // but actually fine if skip-unknown set
        ValueClassXY v = MAPPER.readerFor(ValueClassXY.class)
                .without(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
                .readValue(json);
        assertNotNull(v);
        // note: +1 for both so
        assertEquals(v._x, 2);
        assertEquals(v._y, 3);
    }
}
