/*
 * @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.Collection;
import java.util.Iterator;
import net.ranides.assira.reflection.IClass;

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

    private SerializationUtils() {
        // 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 bos = new ByteArrayOutputStream();
        try (ObjectOutputStream ostream = new ObjectOutputStream(bos)) {
            ostream.writeObject(value);
            ostream.flush();
            return bos.toByteArray();
        }
    }

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

    public static SizeInfo sizeof(Collection<?> values) throws IOException {
        ByteArrayOutputStream orstream = new ByteArrayOutputStream();
        int s1 = values.size() / 2;
        int s2 = values.size();
        try(ObjectOutputStream ostream =  new ObjectOutputStream(orstream)) {
            Iterator<?> iterator = values.iterator();
            for(int i=0; i<s1; i++) {
                ostream.writeObject(iterator.next());
            }
            ostream.flush();
            int b1 = orstream.size();
            for(int i=s1; i<s2; i++) {
                ostream.writeObject(iterator.next());
            }
            ostream.flush();
            int b2 = orstream.size();
            
            int element = (b2-b1) / (s2-s1);
            int overhead = b1 - s1*element;
            return new SizeInfo(b2, element, overhead);

        } finally {
            orstream.close();
        }
    }

    public static class SizeInfo {
        
        private final int size;
        private final int element;
        private final int overhead;

        public SizeInfo(int sum, int element, int overhead) {
            this.size = sum;
            this.element = element;
            this.overhead = overhead;
        }

        public int size() {
            return size;
        }

        public int element() {
            return element;
        }

        public int overhead() {
            return overhead;
        }

        @Override
        public String toString() {
            return String.format("SizeOf{ size=%d, element=%d, overhead=%d }", size(), element(), overhead());
        }

    }
    
    @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 IClass.typeinfo(clazz).construct(params);
        }

    }

}
