package com.fasterxml.jackson.databind.interop;

import java.io.*;

import com.fasterxml.jackson.databind.*;

/**
 * Simple test to ensure that we can make POJOs use Jackson
 * for JDK serialization, via {@link Externalizable}
 * 
 * @since 2.1
 */
public class TestExternalizable extends BaseMapTest
{
    /* Not pretty, but needed to make ObjectMapper accessible from
     * static context (alternatively could use ThreadLocal).
     */
    static class MapperHolder {
        private final ObjectMapper mapper = new ObjectMapper();
        private final static MapperHolder instance = new MapperHolder();
        public static ObjectMapper mapper() { return instance.mapper; }
    }

    /**
     * Helper class we need to adapt {@link ObjectOutput} as
     * {@link OutputStream}
     */
    final static class ExternalizableInput extends InputStream
    {
        private final ObjectInput in;

        public ExternalizableInput(ObjectInput in) {
            this.in = in;
        }

        @Override
        public int available() throws IOException {
            return in.available();
        }
        
        @Override
        public void close() throws IOException {
            in.close();
        }
        
        @Override
        public boolean  markSupported() {
            return false;
        }

        @Override
        public int read() throws IOException {
            return in.read();
        }

        @Override
        public int read(byte[] buffer) throws IOException {
            return in.read(buffer);
        }

        @Override
        public int read(byte[] buffer, int offset, int len) throws IOException {
            return in.read(buffer, offset, len);
        }
        
        @Override
        public long skip(long n) throws IOException {
            return in.skip(n);
        }
    }        

    /**
     * Helper class we need to adapt {@link ObjectOutput} as
     * {@link OutputStream}
     */
    final static class ExternalizableOutput extends OutputStream
    {
        private final ObjectOutput out;

        public ExternalizableOutput(ObjectOutput out) {
            this.out = out;
        }
        
        @Override
        public void flush() throws IOException {
            out.flush();
        }

        @Override
        public void close() throws IOException {
            out.close();
        }
        
        @Override
        public void write(int ch) throws IOException {
            out.write(ch);
        }

        @Override
        public void write(byte[] data) throws IOException {
            out.write(data);
        }
        
        @Override
        public void write(byte[] data, int offset, int len) throws IOException {
            out.write(data, offset, len);
        }
    }
    
//    @com.fasterxml.jackson.annotation.JsonFormat(shape=com.fasterxml.jackson.annotation.JsonFormat.Shape.ARRAY)
    @SuppressWarnings("resource")
    static class MyPojo implements Externalizable
    {
        public int id;
        public String name;
        public int[] values;

        public MyPojo() { } // for deserialization
        public MyPojo(int id, String name, int[] values)
        {
            this.id = id;
            this.name = name;
            this.values = values;
        }

        @Override
        public void readExternal(ObjectInput in) throws IOException
        {
//            MapperHolder.mapper().readValue(
            MapperHolder.mapper().readerForUpdating(this).readValue(new ExternalizableInput(in));
        }

        @Override
        public void writeExternal(ObjectOutput oo) throws IOException
        {
            MapperHolder.mapper().writeValue(new ExternalizableOutput(oo), this);
        }

        @Override
        public boolean equals(Object o)
        {
            if (o == this) return true;
            if (o == null) return false;
            if (o.getClass() != getClass()) return false;
            
            MyPojo other = (MyPojo) o;
            
            if (other.id != id) return false;
            if (!other.name.equals(name)) return false;
            
            if (other.values.length != values.length) return false;
            for (int i = 0, end = values.length; i < end; ++i) {
                if (values[i] != other.values[i]) return false;
            }
            return true;
        }
    }

    /*
    /**********************************************************
    /* Actual tests
    /**********************************************************
     */

    // Comparison, using JDK native
    static class MyPojoNative implements Serializable
    {
        private static final long serialVersionUID = 1L;

        public int id;
        public String name;
        public int[] values;

        public MyPojoNative(int id, String name, int[] values)
        {
            this.id = id;
            this.name = name;
            this.values = values;
        }
    }
    
    @SuppressWarnings("unused")
    public void testSerializeAsExternalizable() throws Exception
    {
        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
        ObjectOutputStream obs = new ObjectOutputStream(bytes);
        final MyPojo input = new MyPojo(13, "Foobar", new int[] { 1, 2, 3 } );
        obs.writeObject(input);
        obs.close();
        byte[] ser = bytes.toByteArray();

        // Ok: just verify it contains stuff it should
        byte[] json = MapperHolder.mapper().writeValueAsBytes(input);

        int ix = indexOf(ser, json);
        if (ix < 0) {
            fail("Serialization ("+ser.length+") does NOT contain JSON (of "+json.length+")");
        }
        
        // Sanity check:
        if (false) {
            bytes = new ByteArrayOutputStream();
            obs = new ObjectOutputStream(bytes);
            MyPojoNative p = new MyPojoNative(13, "Foobar", new int[] { 1, 2, 3 } );
            obs.writeObject(p);
            obs.close();
            System.out.println("Native size: "+bytes.size()+", vs JSON: "+ser.length);
        }
        
        // then read back!
        ObjectInputStream ins = new ObjectInputStream(new ByteArrayInputStream(ser));
        MyPojo output = (MyPojo) ins.readObject();
        ins.close();
        assertNotNull(output);
        
        assertEquals(input, output);
    }

    /*
    /**********************************************************
    /* Helper methods
    /**********************************************************
     */

    private int indexOf(byte[] full, byte[] fragment)
    {
        final byte first = fragment[0];
        for (int i = 0, end = full.length-fragment.length; i < end; ++i) {
            if (full[i] != first) continue;
            if (matches(full, i, fragment)) {
                return i;
            }
        }
        return -1;
    }

    private boolean matches(byte[] full, int index, byte[] fragment)
    {
        for (int i = 1, end = fragment.length; i < end; ++i) {
            if (fragment[i] != full[index+i]) {
                return false;
            }
        }
        return true;
    }
}
