/*
 * @author Ranides Atterwim <ranides@gmail.com>
 * @copyright Ranides Atterwim
 * @license WTFPL
 * @url http://ranides.net/projects/assira
 */
package net.ranides.assira.generic;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.List;
import net.ranides.assira.io.FileHelper;
import net.ranides.assira.reflection.util.ObjectUtils;

/**
 *
 * @author ranides
 */
public final class Serializer {

    private Serializer() {
        // utility class
    }

    /**
     * "Klonuje" obiekt serializując i deserializując go.
     * @param <T>
     * @param value
     * @return
     * @throws IOException
     */
    @SuppressWarnings("unchecked")
    public static <T> T copy(T value) throws IOException {
        return read( (Class<T>)value.getClass(), write(value));
    }

    public static byte[] write(Object value) throws IOException {
        ByteArrayOutputStream orstream = new ByteArrayOutputStream();
        ObjectOutputStream ostream = null;
        try {
            ostream = new ObjectOutputStream(orstream);
            ostream.writeObject(value);
            return orstream.toByteArray();
        } finally {
            FileHelper.close(ostream);
        }
    }

    public static <T> T read(Class<T> clazz, byte[] data) throws IOException {
        try {
            ByteArrayInputStream irstream = new ByteArrayInputStream(data);
            ObjectInputStream istream = new ObjectInputStream(irstream);
            return clazz.cast(istream.readObject());
        } catch(ClassNotFoundException cause) {
            throw new AssertionError(cause);
        }
    }
    
    public static Object proxy(Object that, Object... params) {
        return new Proxy(that, params);
    }

    public static SizeInfo sizeof(List<?> list) throws IOException {
        ByteArrayOutputStream orstream = new ByteArrayOutputStream();
        ObjectOutputStream ostream = null;
        int h1 = list.size() / 2;
        int h2 = list.size();
        int s1 = 0, s2 = 0;
        try {
            ostream = new ObjectOutputStream(orstream);
            for(int i=0; i<h1; i++) {
                ostream.writeObject(list.get(i));
            }
            s1 = orstream.size();
            for(int i=h1; i<h2; i++) {
                ostream.writeObject(list.get(i));
            }
            s2 = orstream.size();
        } finally {
            FileHelper.close(ostream);
            FileHelper.close(orstream);
        }
        int self = (s2-s1) / (h2-h1);
        int attached = s1 - h1*self;
        return new SizeInfo(s2, self, attached);
    }

    public static class SizeInfo {
        private final int sum;
        private final int self;
        private final int attached;

        public SizeInfo(int sum, int self, int attached) {
            this.sum = sum;
            this.self = self;
            this.attached = attached;
        }

        public int sum() {
            return sum;
        }

        public int self() {
            return self;
        }

        public int attached() {
            return attached;
        }

        @Override
        public String toString() {
            return String.format("sizeof = { sum : %d, self : %d, attached : %d }", sum, self, attached);
        }

    }
    
    @SuppressWarnings("PMD")
    private static class Proxy implements Serializable {

        private static final long serialVersionUID = 4L;

        private final Class<?> clazz;
        private final Object[] params;

        public Proxy(Object that, Object... params) {
            this.clazz = that.getClass();
            this.params = params;
        }

        private Object readResolve() {
            return ObjectUtils.create(clazz, params);
        }

    }

}
