package play.db.helper;

import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class JdbcResultFactories {

    private JdbcResultFactories() {
    }


    public static <T> JdbcResultFactory<T> build(Class<T> objectClass) {
        return build(objectClass, (List<String>)null);
    }

    public static <T> JdbcResultFactory<T> build(Class<T> objectClass, String ... fields) {
        return build(objectClass, Arrays.asList(fields));
    }

    public static <T> JdbcResultFactory<T> build(Class<T> objectClass, List<String> fields) {
        return objectClass == Boolean.class
            || objectClass == Character.class
            || objectClass == Byte.class
            || objectClass == Short.class
            || objectClass == Integer.class
            || objectClass == Long.class
            || objectClass == Float.class
            || objectClass == Double.class
                ? new PrimitiveFactory<T>(objectClass, fields)
                : new ClassFactory<T>(objectClass, fields);
    }



    public static <T> JdbcResultFactory<T> buildPrimitive(Class<T> objectClass) {
        return buildPrimitive(objectClass, 1);
    }

    public static <T> JdbcResultFactory<T> buildPrimitive(Class<T> objectClass, int columnIndex) {
        return new PrimitiveFactory<T>(objectClass, columnIndex);
    }

    public static <T> JdbcResultFactory<T> buildPrimitive(Class<T> objectClass, String field) {
        return new PrimitiveFactory<T>(objectClass, field);
    }



    public static <T> JdbcResultFactory<T> buildClass(Class<T> objectClass) {
        return buildClass(objectClass, (List<String>)null);
    }

    public static <T> JdbcResultFactory<T> buildClass(Class<T> objectClass, String ... fields) {
        return buildClass(objectClass, Arrays.asList(fields));
    }

    public static <T> JdbcResultFactory<T> buildClass(Class<T> objectClass, List<String> fields) {
        return new ClassFactory<T>(objectClass, fields);
    }



    public static class PrimitiveFactory<T> implements JdbcResultFactory<T> {

        private final Class<T> objectClass;
        private final String field;
        private int columnIndex;

        public PrimitiveFactory(Class<T> objectClass, int columnIndex) {
            this.objectClass = objectClass;
            this.field = null;
            this.columnIndex = columnIndex;
        }

        public PrimitiveFactory(Class<T> objectClass, String field) {
            this.objectClass = objectClass;
            this.field = field;
            this.columnIndex = 1;
        }

        public PrimitiveFactory(Class<T> objectClass, List<String> fields) {
            this.objectClass = objectClass;
            this.field = fields == null || fields.isEmpty() ? null : fields.get(0);
            this.columnIndex = 1;
        }

        public void init(ResultSet result) throws SQLException {
            if (field != null) {
                ResultSetMetaData meta = result.getMetaData();
                int count = meta.getColumnCount();
                for (int i = 1; i <= count; i++) {
                    String label = meta.getColumnLabel(i);
                    if (label.equals(field)) {
                        columnIndex = i;
                        break;
                    }
                }
            }
        }

        @SuppressWarnings("unchecked")
        public T create(ResultSet result) throws SQLException {
            Object value = result.getObject(columnIndex);
            if (value instanceof BigDecimal) value = new Long(((BigDecimal)value).longValue());
            if (!objectClass.isInstance(value)) throw new IllegalArgumentException();
            return (T) value;
        }

    }

    public static class ClassFactory<T> implements JdbcResultFactory<T> {

        private final Class<T> objectClass;
        private List<String> fields;

        public ClassFactory(Class<T> objectClass, List<String> fields) {
            this.objectClass = objectClass;
            this.fields = fields;
        }

        public void init(ResultSet result) throws SQLException {
            if (fields == null) {
                fields = new ArrayList<String>();
                ResultSetMetaData meta = result.getMetaData();
                int count = meta.getColumnCount();
                for (int i = 1; i <= count; i++) {
                    String label = meta.getColumnLabel(i);
                    if (label.length()>0) fields.add(label);
                }
            }
        }

        public T create(ResultSet result) throws SQLException {
            try {
                T obj = objectClass.newInstance();
                for (String field : fields) {
                    Object value = result.getObject(field);
                    if (value instanceof BigDecimal) value = new Long(((BigDecimal)value).longValue());
                    objectClass.getDeclaredField(field).set(obj, value);
                }
                return obj;
            } catch (InstantiationException ex) {
                throw new RuntimeException(ex);
            } catch (NoSuchFieldException ex) {
                throw new RuntimeException(ex);
            } catch (IllegalAccessException ex) {
                throw new RuntimeException(ex);
            }
        }

    }

}
