package com.fasterxml.jackson.dataformat.xml.failing;

import java.io.UnsupportedEncodingException;
import java.net.*;
import java.util.*;

import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.*;

import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.dataformat.xml.*;
import com.fasterxml.jackson.dataformat.xml.jaxb.XmlJaxbAnnotationIntrospector;

public class Issue37AdapterTest extends XmlTestBase
{
    @XmlJavaTypeAdapter(URLEncoderMapDataAdapter.class)
    public static class MapData
    {
        public String key;
        public String value;

        public MapData() { }

        public MapData(String key, String value) {
            super();
            this.key = key;
            this.value = value;
        }
    }

    public static class URLEncoderMapDataAdapter extends XmlAdapter<MapData[], Map<String, String>>
    {
        public URLEncoderMapDataAdapter() { }

        @Override
        public MapData[] marshal(Map<String, String> arg0) throws Exception {
            MapData[] mapElements = new MapData[arg0.size()];
            int i = 0;
            for (Map.Entry<String, String> entry : arg0.entrySet()) {
                mapElements[i++] = new MapData(encodeKey(entry.getKey()), entry.getValue());
            }

            return mapElements;
        }

        @Override
        public Map<String, String> unmarshal(MapData[] arg0) throws Exception {
            Map<String, String> r = new HashMap<String, String>();
            for (MapData mapelement : arg0) {
                r.put(decodeKey(mapelement.key), mapelement.value);
            }
            return r;
        }

        private final static String ENCODING = "UTF-8";

        private String encodeKey(String key) throws UnsupportedEncodingException {
            return URLEncoder.encode(key, ENCODING);
        }

        private String decodeKey(String key) throws UnsupportedEncodingException {
            return URLDecoder.decode(key, ENCODING);
        }
    }

    @XmlRootElement(name = "DocWithMapData")
    public static class DocWithMapData
    {
        @XmlJavaTypeAdapter(value = URLEncoderMapDataAdapter.class) // type = MapData[].class)
        public Map<String, String> mapDatas;
    }

    @XmlRootElement(name = "DocWithMapDataSimpleAnnotation")
    public static class DocWithMapDataSimpleAnnotation
    {
        @XmlJavaTypeAdapter(URLEncoderMapDataAdapter.class)
        public Map<String, String> mapDatas;
    }

    private Map<String, String> simpleMapData = singletonMap("key", "value");

    private Map<String, String> needEncodingMapData = singletonMap("my/key", "my/value");

    private Map<String,String> singletonMap(String a, String b) {
        Map<String,String> map = new HashMap<String,String>();
        map.put(a,b);
        return map;
    }

    /*
    /**********************************************************************
    /* Set up
    /***********************************************************************
     */

    protected XmlMapper _jaxbMapper;

    protected XmlMapper _nonJaxbMapper;

    @Override
    public void setUp() throws Exception
    {
        super.setUp();
        _jaxbMapper = new XmlMapper();
        _nonJaxbMapper = new XmlMapper();
        // Use JAXB-then-Jackson annotation introspector
        AnnotationIntrospector intr =
            XmlAnnotationIntrospector.Pair.instance(new XmlJaxbAnnotationIntrospector(TypeFactory.defaultInstance()),
                new JacksonAnnotationIntrospector());
        _jaxbMapper.setAnnotationIntrospector(intr);
    }

    /*
    /**********************************************************************
    /* Unit tests
    /**********************************************************************
     */

    public void testSimpleKeyMap() throws Exception
    {
        DocWithMapData bean = new DocWithMapData();
        bean.mapDatas = simpleMapData;

        assertEquals("<DocWithMapData><mapDatas><key>value</key></mapDatas></DocWithMapData>",
            _jaxbMapper.writeValueAsString(bean));
    }

    public void testNeedEncodingKeyMap() throws Exception
    {
        DocWithMapData bean = new DocWithMapData();
        bean.mapDatas = needEncodingMapData;

        String xml = _jaxbMapper.writeValueAsString(bean);
        
        assertEquals("<DocWithMapData><mapDatas><my%2Fkey>my/value</my%2Fkey></mapDatas></DocWithMapData>",
                xml);
    }

    public void testSimpleKeyMapSimpleAnnotation() throws Exception
    {
        DocWithMapDataSimpleAnnotation bean = new DocWithMapDataSimpleAnnotation();
        bean.mapDatas = simpleMapData;

        assertEquals(
            "<DocWithMapDataSimpleAnnotation><mapDatas><key>value</key></mapDatas></DocWithMapDataSimpleAnnotation>",
            _jaxbMapper.writeValueAsString(bean));
    }

    public void testNeedEncodingKeyMapSimpleAnnotation() throws Exception
    {
        DocWithMapDataSimpleAnnotation bean = new DocWithMapDataSimpleAnnotation();
        bean.mapDatas = needEncodingMapData;

        assertEquals(
            "<DocWithMapDataSimpleAnnotation><mapDatas><my%2Fkey>my/value</my%2Fkey></mapDatas></DocWithMapDataSimpleAnnotation>",
            _jaxbMapper.writeValueAsString(bean));
    }

    public void testNeedEncodingKeyMap_nonJaxb() throws Exception
    {
        DocWithMapData bean = new DocWithMapData();
        bean.mapDatas = needEncodingMapData;

        assertEquals(
            "<DocWithMapData><mapDatas><my%2Fkey>my/value</my%2Fkey></mapDatas></DocWithMapData>",
            _nonJaxbMapper.writeValueAsString(bean));
    }

    public void testNeedEncodingKeyMapSimpleAnnotation_nonJaxb() throws Exception
    {
        DocWithMapDataSimpleAnnotation bean = new DocWithMapDataSimpleAnnotation();
        bean.mapDatas = needEncodingMapData;

        assertEquals(
            "<DocWithMapDataSimpleAnnotation><mapDatas><my%2Fkey>my/value</my%2Fkey></mapDatas></DocWithMapDataSimpleAnnotation>",
            _nonJaxbMapper.writeValueAsString(bean));
    }
}
