package com.taobao.diamond.server.service;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.Properties;

import javax.annotation.PostConstruct;

import org.apache.commons.dbcp.BasicDataSource;
import org.apache.commons.lang.StringUtils;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementSetter;
import org.springframework.jdbc.core.simple.ParameterizedRowMapper;
import org.springframework.stereotype.Service;

import com.taobao.diamond.domain.ConfigInfo;
import com.taobao.diamond.domain.Page;
import com.taobao.diamond.server.utils.PaginationHelper;
import com.taobao.diamond.utils.ResourceUtils;
import com.taobao.diamond.utils.TimeUtils;


/**
 * 数据库服务,提供ConfigInfo在数据库的存取<br>
 * 
 * @author boyan
 * @author leiwen.zh
 * @since 1.0
 */

@Service
public class PersistService {

    private static final String JDBC_DRIVER_NAME = "com.mysql.jdbc.Driver";

    // 最大记录条数
    private static final int MAX_ROWS = 10000;
    // JDBC执行超时时间, 单位秒
    private static final int QUERY_TIMEOUT = 2;

    private static final ConfigInfoRowMapper CONFIG_INFO_ROW_MAPPER = new ConfigInfoRowMapper();

    private static final class ConfigInfoRowMapper implements ParameterizedRowMapper<ConfigInfo> {
        public ConfigInfo mapRow(ResultSet rs, int rowNum) throws SQLException {
            ConfigInfo info = new ConfigInfo();
            info.setId(rs.getLong("id"));
            info.setDataId(rs.getString("data_id"));
            info.setGroup(rs.getString("group_id"));
            info.setContent(rs.getString("content"));
            info.setMd5(rs.getString("md5"));
            return info;
        }
    }


    private static String ensurePropValueNotNull(String srcValue) {
        if (srcValue == null) {
            throw new IllegalArgumentException("property is illegal:" + srcValue);
        }

        return srcValue;
    }

    private JdbcTemplate jt;


    /**
     * 单元测试用
     * 
     * @return
     */
    public JdbcTemplate getJdbcTemplate() {
        return this.jt;
    }


    @PostConstruct
    public void initDataSource() throws Exception {
        // 读取jdbc.properties配置, 加载数据源
        Properties props = ResourceUtils.getResourceAsProperties("jdbc.properties");
        BasicDataSource ds = new BasicDataSource();
        ds.setDriverClassName(JDBC_DRIVER_NAME);
        ds.setUrl(ensurePropValueNotNull(props.getProperty("db.url")));
        ds.setUsername(ensurePropValueNotNull(props.getProperty("db.user")));
        ds.setPassword(ensurePropValueNotNull(props.getProperty("db.password")));
        ds.setInitialSize(Integer.parseInt(ensurePropValueNotNull(props.getProperty("db.initialSize"))));
        ds.setMaxActive(Integer.parseInt(ensurePropValueNotNull(props.getProperty("db.maxActive"))));
        ds.setMaxIdle(Integer.parseInt(ensurePropValueNotNull(props.getProperty("db.maxIdle"))));
        ds.setMaxWait(Long.parseLong(ensurePropValueNotNull(props.getProperty("db.maxWait"))));
        ds.setPoolPreparedStatements(Boolean.parseBoolean(ensurePropValueNotNull(props
            .getProperty("db.poolPreparedStatements"))));

        this.jt = new JdbcTemplate();
        this.jt.setDataSource(ds);
        // 设置最大记录数,防止内存膨胀
        this.jt.setMaxRows(MAX_ROWS);
        // 设置JDBC执行超时时间
        this.jt.setQueryTimeout(QUERY_TIMEOUT);
    }


    public void addConfigInfo(final ConfigInfo configInfo) {
        final Timestamp time = TimeUtils.getCurrentTime();

        this.jt.update(
            "insert into config_info (data_id,group_id,content,md5,gmt_create,gmt_modified) values(?,?,?,?,?,?)",
            new PreparedStatementSetter() {
                public void setValues(PreparedStatement ps) throws SQLException {
                    int index = 1;
                    ps.setString(index++, configInfo.getDataId());
                    ps.setString(index++, configInfo.getGroup());
                    ps.setString(index++, configInfo.getContent());
                    ps.setString(index++, configInfo.getMd5());
                    ps.setTimestamp(index++, time);
                    ps.setTimestamp(index++, time);
                }
            });
    }


    public void removeConfigInfo(final ConfigInfo configInfo) {
        this.jt.update("delete from config_info where data_id=? and group_id=?", new PreparedStatementSetter() {
            public void setValues(PreparedStatement ps) throws SQLException {
                int index = 1;
                ps.setString(index++, configInfo.getDataId());
                ps.setString(index++, configInfo.getGroup());
            }
        });
    }


    public void updateConfigInfo(final ConfigInfo configInfo) {
        final Timestamp time = TimeUtils.getCurrentTime();

        this.jt.update("update config_info set content=?,md5=?,gmt_modified=? where data_id=? and group_id=?",
            new PreparedStatementSetter() {

                public void setValues(PreparedStatement ps) throws SQLException {
                    int index = 1;
                    ps.setString(index++, configInfo.getContent());
                    ps.setString(index++, configInfo.getMd5());
                    ps.setTimestamp(index++, time);
                    ps.setString(index++, configInfo.getDataId());
                    ps.setString(index++, configInfo.getGroup());
                }
            });
    }


    public ConfigInfo findConfigInfo(final String dataId, final String group) {
        try {
            return this.jt.queryForObject(
                "select id,data_id,group_id,content,md5 from config_info where data_id=? and group_id=?",
                new Object[] { dataId, group }, CONFIG_INFO_ROW_MAPPER);
        }
        catch (EmptyResultDataAccessException e) {
            // 是EmptyResultDataAccessException, 表明数据不存在, 返回null
            return null;
        }
    }


    public ConfigInfo findConfigInfo(long id) {
        try {
            return this.jt.queryForObject("select id,data_id,group_id,content,md5 from config_info where id=?",
                new Object[] { id }, CONFIG_INFO_ROW_MAPPER);
        }
        catch (EmptyResultDataAccessException e) {
            return null;
        }
    }


    public Page<ConfigInfo> findConfigInfoByDataId(final int pageNo, final int pageSize, final String dataId) {
        PaginationHelper<ConfigInfo> helper = new PaginationHelper<ConfigInfo>();
        return helper.fetchPage(this.jt, "select count(id) from config_info where data_id=?",
            "select id,data_id,group_id,content,md5 from config_info where data_id=?", new Object[] { dataId }, pageNo,
            pageSize, CONFIG_INFO_ROW_MAPPER);
    }


    public Page<ConfigInfo> findConfigInfoByGroup(final int pageNo, final int pageSize, final String group) {
        PaginationHelper<ConfigInfo> helper = new PaginationHelper<ConfigInfo>();
        return helper.fetchPage(this.jt, "select count(id) from config_info where group_id=?",
            "select id,data_id,group_id,content,md5 from config_info where group_id=?", new Object[] { group }, pageNo,
            pageSize, CONFIG_INFO_ROW_MAPPER);
    }


    public Page<ConfigInfo> findAllConfigInfo(final int pageNo, final int pageSize) {
        PaginationHelper<ConfigInfo> helper = new PaginationHelper<ConfigInfo>();
        return helper.fetchPage(this.jt, "select count(id) from config_info order by id",
            "select id,data_id,group_id,content,md5 from config_info order by id ", new Object[] {}, pageNo, pageSize,
            CONFIG_INFO_ROW_MAPPER);
    }


    public Page<ConfigInfo> findConfigInfoLike(final int pageNo, final int pageSize, final String dataId,
            final String group) {
        if (StringUtils.isBlank(dataId) && StringUtils.isBlank(group)) {
            return this.findAllConfigInfo(pageNo, pageSize);
        }

        PaginationHelper<ConfigInfo> helper = new PaginationHelper<ConfigInfo>();

        String sqlCountRows = "select count(id) from config_info where ";
        String sqlFetchRows = "select id,data_id,group_id,content,md5 from config_info where ";
        boolean wasFirst = true;
        if (!StringUtils.isBlank(dataId)) {
            sqlCountRows += "data_id like ? ";
            sqlFetchRows += "data_id like ? ";
            wasFirst = false;
        }
        if (!StringUtils.isBlank(group)) {
            if (wasFirst) {
                sqlCountRows += "group_id like ? ";
                sqlFetchRows += "group_id like ? ";
            }
            else {
                sqlCountRows += "and group_id like ? ";
                sqlFetchRows += "and group_id like ? ";
            }
        }

        Object[] args = null;
        if (!StringUtils.isBlank(dataId) && !StringUtils.isBlank(group)) {
            args = new Object[] { generateLikeArgument(dataId), generateLikeArgument(group) };
        }
        else if (!StringUtils.isBlank(dataId)) {
            args = new Object[] { generateLikeArgument(dataId) };
        }
        else if (!StringUtils.isBlank(group)) {
            args = new Object[] { generateLikeArgument(group) };
        }

        return helper.fetchPage(this.jt, sqlCountRows, sqlFetchRows, args, pageNo, pageSize, CONFIG_INFO_ROW_MAPPER);
    }


    private String generateLikeArgument(String s) {
        if (s.indexOf("*") >= 0)
            return s.replaceAll("\\*", "%");
        else {
            return "%" + s + "%";
        }
    }

}