
package com.fasterxml.jackson.databind.module;

import java.io.File;
import java.io.IOException;
import java.util.*;

import org.junit.Test;

import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.core.type.TypeReference;

import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import com.fasterxml.jackson.databind.node.ObjectNode;

@SuppressWarnings("serial")
public class TestCustomEnumKeyDeserializer extends BaseMapTest
{
    @JsonSerialize(using = TestEnumSerializer.class, keyUsing = TestEnumKeySerializer.class)
    @JsonDeserialize(using = TestEnumDeserializer.class, keyUsing = TestEnumKeyDeserializer.class)
    public enum TestEnumMixin { }

    enum KeyEnum {
        replacements,
        rootDirectory,
        licenseString
    }

    enum TestEnum {
        RED("red"),
        GREEN("green");

        private final String code;

        TestEnum(String code) {
            this.code = code;
        }

        public static TestEnum lookup(String lower) {
            for (TestEnum item : values()) {
                if (item.code().equals(lower)) {
                    return item;
                }
            }
            throw new IllegalArgumentException("Invalid code " + lower);
        }

        public String code() {
            return code;
        }
    }

    static class TestEnumSerializer extends JsonSerializer<TestEnum> {
        @Override
        public void serialize(TestEnum languageCode, JsonGenerator g, SerializerProvider serializerProvider) throws IOException {
            g.writeString(languageCode.code());
        }

        @Override
        public Class<TestEnum> handledType() {
            return TestEnum.class;
        }
    }

    static class TestEnumKeyDeserializer extends KeyDeserializer {
        @Override
        public Object deserializeKey(String key, DeserializationContext ctxt) throws IOException {
            try {
                return TestEnum.lookup(key);
            } catch (IllegalArgumentException e) {
                throw ctxt.weirdKeyException(TestEnum.class, key, "Unknown code");
            }
        }
    }

    static class TestEnumDeserializer extends StdDeserializer<TestEnum> {
        public TestEnumDeserializer() {
            super(TestEnum.class);
        }

        @Override
        public TestEnum deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
            String code = jp.getText();
            try {
                return TestEnum.lookup(code);
            } catch (IllegalArgumentException e) {
                throw new InvalidFormatException("Undefined ISO-639 language code", jp.getCurrentLocation(), code, TestEnum.class);
            }
        }
    }

    static class TestEnumKeySerializer extends JsonSerializer<TestEnum> {
        @Override
        public void serialize(TestEnum test, JsonGenerator g, SerializerProvider serializerProvider) throws IOException {
            g.writeFieldName(test.code());
        }

        @Override
        public Class<TestEnum> handledType() {
            return TestEnum.class;
        }
    }

    static class Bean {
        private File rootDirectory;
        private String licenseString;
        private Map<TestEnum, Map<String, String>> replacements;

        public File getRootDirectory() {
            return rootDirectory;
        }

        public void setRootDirectory(File rootDirectory) {
            this.rootDirectory = rootDirectory;
        }

        public String getLicenseString() {
            return licenseString;
        }

        public void setLicenseString(String licenseString) {
            this.licenseString = licenseString;
        }

        public Map<TestEnum, Map<String, String>> getReplacements() {
            return replacements;
        }

        public void setReplacements(Map<TestEnum, Map<String, String>> replacements) {
            this.replacements = replacements;
        }
    }

    static class TestEnumModule extends SimpleModule {
        public TestEnumModule() {
            super(Version.unknownVersion());
        }

        @Override
        public void setupModule(SetupContext context) {
            context.setMixInAnnotations(TestEnum.class, TestEnumMixin.class);
            SimpleSerializers keySerializers = new SimpleSerializers();
            keySerializers.addSerializer(new TestEnumKeySerializer());
            context.addKeySerializers(keySerializers);
        }
    }

    /*
    /**********************************************************
    /* Test methods
    /**********************************************************
     */
    
    // Test passing with the fix
    @Test
    public void testWithEnumKeys() throws Exception {
        ObjectMapper plainObjectMapper = new ObjectMapper();
        JsonNode tree = plainObjectMapper.readTree(aposToQuotes("{'red' : [ 'a', 'b']}"));

        ObjectMapper fancyObjectMapper = new ObjectMapper().registerModule(new TestEnumModule());

        // this line is might throw with Jackson 2.6.2.
        Map<TestEnum, Set<String>> map = fancyObjectMapper.convertValue(tree,
                new TypeReference<Map<TestEnum, Set<String>>>() { } );
        assertNotNull(map);
    }

    // and another still failing
    // NOTE: temporarily named as non-test to ignore it; JsonIgnore doesn't work for some reason
//    public void testWithTree749() throws Exception
    public void withTree749() throws Exception
    {
        ObjectMapper mapper = new ObjectMapper().registerModule(new TestEnumModule());

        Map<KeyEnum, Object> inputMap = new LinkedHashMap<KeyEnum, Object>();
        Map<TestEnum, Map<String, String>> replacements = new LinkedHashMap<TestEnum, Map<String, String>>();
        Map<String, String> reps = new LinkedHashMap<String, String>();
        reps.put("1", "one");
        replacements.put(TestEnum.GREEN, reps);
        inputMap.put(KeyEnum.replacements, replacements);

        JsonNode tree = mapper.valueToTree(inputMap);
        ObjectNode ob = (ObjectNode) tree;

        JsonNode inner = ob.get("replacements");
        String firstFieldName = inner.fieldNames().next();
        assertEquals("green", firstFieldName);
    }
}
