package xyz.wbsite.wsqlite;

import xyz.wbsite.wsqlite.anonation.TableField;

import java.io.File;
import java.lang.reflect.Field;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * xyz.wbsite.wsqlite.Client
 *
 * @author wangbing
 */
public class ObjectClient extends Client {

    private Map<String, Class> classMap = new HashMap<>();

    /**
     * 构造函数
     *
     * @param dbFile    文件
     * @param classList 注册对象
     * @throws ClassNotFoundException
     * @throws SQLException
     */
    public ObjectClient(File dbFile, List<Class> classList) throws ClassNotFoundException, SQLException {
        super(dbFile);
        for (Class aClass : classList) {
            classMap.put(aClass.getName(), aClass);
        }
        for (String key : classMap.keySet()) {
            Class object = classMap.get(key);
            StringBuffer sql = new StringBuffer();
            String name = object.getSimpleName();

            sql.append("CREATE TABLE IF NOT EXISTS ");
            sql.append(name);
            sql.append(" (");

            Field[] fields = object.getDeclaredFields();
            for (Field f : fields) {
                if (f.isAnnotationPresent(TableField.class)) {
                    TableField bind = f.getAnnotation(TableField.class);
                    int length = bind.value();

                    if (f.getType() == String.class) {
                        sql.append(f.getName().toUpperCase());
                        sql.append(" VARCHAR(" + length + "),");
                    } else if (f.getType() == Boolean.class || f.getType() == boolean.class) {
                        sql.append(f.getName().toUpperCase());
                        sql.append(" BOOLEAN,");
                    } else if (f.getType() == Byte.class || f.getType() == byte.class ||
                            f.getType() == Short.class || f.getType() == short.class ||
                            f.getType() == Character.class || f.getType() == char.class ||
                            f.getType() == Integer.class || f.getType() == int.class ||
                            f.getType() == Long.class || f.getType() == long.class) {
                        sql.append(f.getName().toUpperCase());
                        sql.append(" INTEGER,");
                    } else if (f.getType() == Float.class || f.getType() == float.class ||
                            f.getType() == Double.class || f.getType() == double.class) {
                        sql.append(f.getName().toUpperCase());
                        sql.append(" REAL,");
                    } else if (f.getType() == Byte[].class || f.getType() == byte[].class) {
                        sql.append(f.getName().toUpperCase());
                        sql.append(" BLOB,");
                    } else if (f.getType() == Date.class) {
                        sql.append(f.getName().toUpperCase());
                        sql.append(" TIMESTAMP,");
                    }
                }
            }

            sql.replace(sql.length() - 1, sql.length(), "");
            sql.append(")");
            System.out.println("SQL ==> " + sql.toString());
            execute(sql.toString());
        }
    }

    public <T> int insert(Class<T> poClass, T po) throws SQLException, ClassNotFoundException {
        try {
            Class aClass = classMap.get(poClass.getName());
            if (aClass == null) {
                System.err.println(poClass.getName() + " not found.");
            } else {
                StringBuffer sql = new StringBuffer();
                sql.append("INSERT INTO ");
                sql.append(aClass.getSimpleName());
                sql.append("(");

                //获取字段列表
                List<Field> fs = getFields(poClass);

                StringBuffer fieldsSql = new StringBuffer();
                StringBuffer valueSql = new StringBuffer();
                Object[] values = new Object[fs.size()];

                for (int i = 0; i < fs.size(); i++) {
                    Field f = fs.get(i);
                    f.setAccessible(true);
                    fieldsSql.append(f.getName().toUpperCase());
                    valueSql.append("?");
                    Object o = f.get(po);
                    values[i] = o;
                    if (i != fs.size() - 1) {
                        fieldsSql.append(",");
                        valueSql.append(",");
                    }
                }

                sql.append(fieldsSql);
                sql.append(") VALUES (");
                sql.append(valueSql);
                sql.append(")");
                System.out.println("SQL ==> " + sql.toString());
                return executeUpdate(sql.toString(), values);
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } finally {
            destroyed();
        }
        return 0;
    }

    public <T> int delete(Class<T> poClass, Where where) throws SQLException, ClassNotFoundException {
        try {
            Class aClass = classMap.get(poClass.getName());
            if (aClass == null) {
                System.err.println(poClass.getName() + " not found.");
            } else {
                StringBuffer sql = new StringBuffer();
                sql.append("DELETE FROM ");
                sql.append(aClass.getSimpleName());
                sql.append(where.getSql());
                System.out.println("SQL ==> " + sql.toString());
                return executeUpdate(sql.toString(), where.getArgs());
            }
        } finally {
            destroyed();
        }
        return 0;
    }

    public <T> int update(Class<T> poClass, T po, Where where) throws SQLException, ClassNotFoundException {
        try {
            Class aClass = classMap.get(poClass.getName());
            if (aClass == null) {
                System.err.println(poClass.getName() + " not found.");
            } else {
                //获取字段列表
                List<Field> fs = getFields(poClass);

                StringBuffer sql = new StringBuffer();
                Object[] values = new Object[fs.size()];
                sql.append("UPDATE ");
                sql.append(aClass.getSimpleName());
                sql.append(" SET ");

                for (int i = 0; i < fs.size(); i++) {
                    Field f = fs.get(i);
                    f.setAccessible(true);
                    sql.append(f.getName());
                    sql.append(" = ?");
                    values[i] = f.get(po);
                    if (i != fs.size() - 1) {
                        sql.append(",");
                    }
                }

                // 条件
                sql.append(where.getSql());

                System.out.println("SQL ==> " + sql.toString());
                return executeUpdate(sql.toString(), concat(values, where.getArgs()));
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } finally {
            destroyed();
        }
        return 0;
    }

    public <T> List<T> select(Class<T> poClass, Where where, int pageNumber, int pageSize) throws SQLException, ClassNotFoundException {
        ArrayList<T> list = new ArrayList<>();
        try {
            Class aClass = classMap.get(poClass.getName());
            if (aClass == null) {
                System.err.println(poClass.getName() + " not found.");
            } else {
                StringBuffer sql = new StringBuffer();
                sql.append("SELECT ");

                //获取字段列表
                List<Field> fs = getFields(poClass);

                for (int i = 0; i < fs.size(); i++) {
                    Field f = fs.get(i);
                    sql.append(f.getName().toUpperCase());
                    if (i != fs.size() - 1) {
                        sql.append(",");
                    }
                }

                sql.append(" FROM ");
                sql.append(aClass.getSimpleName());

                // 条件
                sql.append(where.getSql());

                //分页参数
                if (pageSize > 0) {
                    sql.append(" LIMIT " + (pageNumber - 1) + "," + pageSize);
                }

                System.out.println("SQL ==> " + sql.toString());
                list.addAll(executeQuery(poClass, sql.toString(), where.getArgs()));
            }
        } finally {
            destroyed();
        }
        return list;
    }

    /**
     * 执行select查询，返回结果列表
     *
     * @param sql     sql select 语句
     * @param poClass 结果集的行数据处理类对象
     * @return
     * @throws SQLException
     * @throws ClassNotFoundException
     */
    public <T> List<T> executeQuery(Class<T> poClass, String sql, Object... args) throws SQLException, ClassNotFoundException {
        List<T> rsList = new ArrayList<T>();
        try {
            resultSet = executeQuery(sql, args);

            //获取字段列表
            List<Field> fs = getFields(poClass);

            while (resultSet.next()) {
                try {
                    T o = poClass.newInstance();
                    for (int i = 0; i < fs.size(); i++) {
                        Field f = fs.get(i);
                        f.setAccessible(true);

                        if (f.getType() == String.class) {
                            String v = resultSet.getString(f.getName());
                            f.set(o, v);
                        } else if (f.getType() == Boolean.class || f.getType() == boolean.class) {
                            boolean v = resultSet.getBoolean(f.getName());
                            f.set(o, v);
                        } else if (f.getType() == Byte.class || f.getType() == byte.class) {
                            byte v = resultSet.getByte(f.getName());
                            f.set(o, v);
                        } else if (f.getType() == Short.class || f.getType() == short.class) {
                            short v = resultSet.getShort(f.getName());
                            f.set(o, v);
                        } else if (f.getType() == Character.class || f.getType() == char.class) {
                            short v = resultSet.getShort(f.getName());
                            f.set(o, (char) v);
                        } else if (f.getType() == Integer.class || f.getType() == int.class) {
                            int v = resultSet.getInt(f.getName());
                            f.set(o, v);
                        } else if (f.getType() == Long.class || f.getType() == long.class) {
                            long v = resultSet.getLong(f.getName());
                            f.set(o, v);
                        } else if (f.getType() == Float.class || f.getType() == float.class) {
                            float v = resultSet.getFloat(f.getName());
                            f.set(o, v);
                        } else if (f.getType() == Double.class || f.getType() == double.class) {
                            double v = resultSet.getDouble(f.getName());
                            f.set(o, v);
                        } else if (f.getType() == Byte[].class || f.getType() == byte[].class) {
                            byte[] v = resultSet.getBytes(f.getName());
                            f.set(o, v);
                        } else if (f.getType() == Date.class) {
                            Date v = resultSet.getTimestamp(f.getName());
                            f.set(o, v);
                        } else {
                            String v = resultSet.getString(f.getName());
                            f.set(o, v);
                        }
                    }
                    rsList.add(o);
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        } finally {
            destroyed();
        }
        return rsList;
    }

    private List<Field> getFields(Class aClass) {
        List<Field> fs = new ArrayList<>();
        for (Field f : aClass.getDeclaredFields()) {
            if (f.isAnnotationPresent(TableField.class)) {
                fs.add(f);
            }
        }
        return fs;
    }

    private <T> T[] concat(T[] arr1, T[] arr2) {
        List<T> temp = new ArrayList<>();
        temp.addAll(Arrays.asList(arr1));
        temp.addAll(Arrays.asList(arr2));
        return (T[]) temp.toArray();
    }
}