package org.codehaus.jackson.map.deser.std;

import java.io.*;
import java.net.InetAddress;
import java.net.URI;
import java.net.URL;
import java.util.*;
import java.util.regex.Pattern;

import org.codehaus.jackson.JsonProcessingException;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.JsonToken;
import org.codehaus.jackson.map.DeserializationContext;

/**
 * Base class for simple deserializer which only accept JSON String
 * values as the source.
 * 
 * @since 1.9 (moved from higher-level package)
 */
public abstract class FromStringDeserializer<T>
    extends StdScalarDeserializer<T>
{
    protected FromStringDeserializer(Class<?> vc) {
        super(vc);
    }

    public static Iterable<FromStringDeserializer<?>>all()
    {
        ArrayList<FromStringDeserializer<?>> all = new ArrayList<FromStringDeserializer<?>>();

        all.add(new UUIDDeserializer());
        all.add(new URLDeserializer());
        all.add(new URIDeserializer());
        all.add(new CurrencyDeserializer());
        all.add(new PatternDeserializer());
        // since 1.7:
        all.add(new LocaleDeserializer());
        // 1.8:
        all.add(new InetAddressDeserializer());
        all.add(new TimeZoneDeserializer());

        return all;
    }
    
    @SuppressWarnings("unchecked")
    @Override
    public final T deserialize(JsonParser jp, DeserializationContext ctxt)
        throws IOException, JsonProcessingException
    {
        if (jp.getCurrentToken() == JsonToken.VALUE_STRING) {
            String text = jp.getText().trim();
            // 15-Oct-2010, tatu: Empty String usually means null, so
            if (text.length() == 0) {
                return null;
            }
            try {
                T result = _deserialize(text, ctxt);
                if (result != null) {
                    return result;
                }
            } catch (IllegalArgumentException iae) {
                // nothing to do here, yet? We'll fail anyway
            }
            throw ctxt.weirdStringException(_valueClass, "not a valid textual representation");
        }
        if (jp.getCurrentToken() == JsonToken.VALUE_EMBEDDED_OBJECT) {
            // Trivial cases; null to null, instance of type itself returned as is
            Object ob = jp.getEmbeddedObject();
            if (ob == null) {
                return null;
            }
            if (_valueClass.isAssignableFrom(ob.getClass())) {
                return (T) ob;
            }
            return _deserializeEmbedded(ob, ctxt);
        }
        throw ctxt.mappingException(_valueClass);
    }
        
    protected abstract T _deserialize(String value, DeserializationContext ctxt)
        throws IOException, JsonProcessingException;

    protected T _deserializeEmbedded(Object ob, DeserializationContext ctxt)
        throws IOException, JsonProcessingException
    {
        // default impl: error out
        throw ctxt.mappingException("Don't know how to convert embedded Object of type "
                +ob.getClass().getName()+" into "+_valueClass.getName());
    }
    
    /*
    /**********************************************************
    /* Then concrete implementations
    /**********************************************************
     */

    public static class UUIDDeserializer
        extends FromStringDeserializer<UUID>
    {
        public UUIDDeserializer() { super(UUID.class); }

        @Override
        protected UUID _deserialize(String value, DeserializationContext ctxt)
            throws IOException, JsonProcessingException
        {
            return UUID.fromString(value);
        }

        @Override
        protected UUID _deserializeEmbedded(Object ob, DeserializationContext ctxt)
            throws IOException, JsonProcessingException
        {
            if (ob instanceof byte[]) {
                byte[] bytes = (byte[]) ob;
                if (bytes.length != 16) {
                    ctxt.mappingException("Can only construct UUIDs from 16 byte arrays; got "+bytes.length+" bytes");
                }
                // clumsy, but should work for now...
                DataInputStream in = new DataInputStream(new ByteArrayInputStream(bytes));
                long l1 = in.readLong();
                long l2 = in.readLong();
                return new UUID(l1, l2);
            }
            super._deserializeEmbedded(ob, ctxt);
            return null; // never gets here
        }
    }

    public static class URLDeserializer
        extends FromStringDeserializer<URL>
    {
        public URLDeserializer() { super(URL.class); }
        
        @Override
        protected URL _deserialize(String value, DeserializationContext ctxt)
            throws IOException
        {
            return new URL(value);
        }
    }

    public static class URIDeserializer
        extends FromStringDeserializer<URI>
    {
        public URIDeserializer() { super(URI.class); }

        @Override
        protected URI _deserialize(String value, DeserializationContext ctxt)
            throws IllegalArgumentException
        {
            return URI.create(value);
        }
    }

    public static class CurrencyDeserializer
        extends FromStringDeserializer<Currency>
    {
        public CurrencyDeserializer() { super(Currency.class); }
        
        @Override
        protected Currency _deserialize(String value, DeserializationContext ctxt)
            throws IllegalArgumentException
        {
            // will throw IAE if unknown:
            return Currency.getInstance(value);
        }
    }

    public static class PatternDeserializer
        extends FromStringDeserializer<Pattern>
    {
        public PatternDeserializer() { super(Pattern.class); }
        
        @Override
        protected Pattern _deserialize(String value, DeserializationContext ctxt)
            throws IllegalArgumentException
        {
            // will throw IAE (or its subclass) if malformed
            return Pattern.compile(value);
        }
    }

    /**
     * Kept protected as it's not meant to be extensible at this point
     * 
     * @since 1.7
     */
    protected static class LocaleDeserializer
        extends FromStringDeserializer<Locale>
    {
        public LocaleDeserializer() { super(Locale.class); }
        
        @Override
        protected Locale _deserialize(String value, DeserializationContext ctxt)
            throws IOException
        {
            int ix = value.indexOf('_');
            if (ix < 0) { // single argument
                return new Locale(value);
            }
            String first = value.substring(0, ix);
            value = value.substring(ix+1);
            ix = value.indexOf('_');
            if (ix < 0) { // two pieces
                return new Locale(first, value);
            }
            String second = value.substring(0, ix);
            return new Locale(first, second, value.substring(ix+1));
        }
    }

    /**
     * As per [JACKSON-484], also need special handling for InetAddress...
     * 
     * @since 1.7.4
     */
    protected static class InetAddressDeserializer
        extends FromStringDeserializer<InetAddress>
    {
        public InetAddressDeserializer() { super(InetAddress.class); }

        @Override
        protected InetAddress _deserialize(String value, DeserializationContext ctxt)
            throws IOException
        {
            return InetAddress.getByName(value);
        }
    }

    /**
     * As per [JACKSON-522], also need special handling for InetAddress...
     * 
     * @since 1.7.4
     */
    protected static class TimeZoneDeserializer
        extends FromStringDeserializer<TimeZone>
    {
        public TimeZoneDeserializer() { super(TimeZone.class); }

        @Override
        protected TimeZone _deserialize(String value, DeserializationContext ctxt)
            throws IOException
        {
            return TimeZone.getTimeZone(value);
        }
    }
}
