package database;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Map;

import com.google.common.collect.Maps;

public class Database {

    private static Database instance = new Database();
    private Connection conn;

    private static final String username = "atg";
    private static final String password = "CvWKY34DqtlVgjt_9";
    private static final String database = "atg";
    private static final String url = "jdbc:mysql://nordh.xyz:3306/";
    private static final String timezoneFix = "?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC";

    private  float avgTime = 0;
    private final Map<Integer, Float> avgTimeDistance = Maps.newHashMap();

    private final Map<Integer, Float> avgTimeDistanceLimit5 = Maps.newHashMap();
    private final Map<Integer, Float> avgTimeDistanceLimit10 = Maps.newHashMap();
    private final Map<Integer, Float> avgTimeDistanceLimit15 = Maps.newHashMap();

    private Float avgTimeLimit5 = 0f;
    private Float avgTimeLimit10 = 0f;
    private Float avgTimeLimit15 = 0f;

    protected Database() {}

    public static Database getDatabase() {
        return instance;
    }

    protected Connection getConnection() {
        if (conn == null) {
            try {
                conn = DriverManager.getConnection(url + database + timezoneFix, username, password);
            } catch (final SQLException e) {
                throw new RuntimeException(e.getMessage(), e);
            }
        }
        return conn;
    }

    public float getAvgTime() {
        if (avgTime <= 0) {
            avgTime = getAvgTimeByDistanceLimit(0, 0);
        }

        return avgTime;
    }

    public float getAvgTimeByDistance(int distance) {
        if (avgTimeDistance.get(distance) == null) {
            avgTimeDistance.put(distance, getAvgTimeByDistanceLimit(distance, 0));
        }

        return avgTimeDistance.get(distance);
    }

    public float getAvgTimeWithLimit(int raceLimit) {
        final float returnValue;
        switch (raceLimit) {
        case 5:
            if (avgTimeLimit5 <= 0) {
                avgTimeLimit5 = getAvgTimeByDistanceLimit(0, raceLimit);
            }
            returnValue = avgTimeLimit5;
            break;
        case 10:
            if (avgTimeLimit10 <= 0) {
                avgTimeLimit10 = getAvgTimeByDistanceLimit(0, raceLimit);
            }
            returnValue = avgTimeLimit10;
            break;
        case 15:
            if (avgTimeLimit15 <= 0) {
                avgTimeLimit15 = getAvgTimeByDistanceLimit(0, raceLimit);
            }
            returnValue = avgTimeLimit15;
            break;

        default:
            returnValue = getAvgTimeByDistanceLimit(0, raceLimit);
            break;
        }
        return returnValue;
    }

    public float getAvgTimeByDistanceLimit(int distance, int raceLimit) {
        final float returnValue;
        if (distance > 0 && raceLimit > 0) {
            switch (raceLimit) {
            case 5:
                if (avgTimeDistanceLimit5.get(distance) == null) {
                    avgTimeDistanceLimit5.put(distance, getNewAvgTimeByDistanceLimit(0, raceLimit));
                }
                returnValue = avgTimeDistanceLimit5.get(distance);
                break;
            case 10:
                if (avgTimeDistanceLimit10.get(distance) == null) {
                    avgTimeDistanceLimit10.put(distance, getNewAvgTimeByDistanceLimit(0, raceLimit));
                }
                returnValue = avgTimeDistanceLimit10.get(distance);
                break;
            case 15:
                if (avgTimeDistanceLimit15.get(distance) == null) {
                    avgTimeDistanceLimit15.put(distance, getNewAvgTimeByDistanceLimit(0, raceLimit));
                }
                returnValue = avgTimeDistanceLimit15.get(distance);
                break;

            default:
                returnValue = getNewAvgTimeByDistanceLimit(0, raceLimit);
                break;
            }
        } else {
            returnValue = getNewAvgTimeByDistanceLimit(distance, raceLimit);
        }
        return returnValue;
    }

    public float getNewAvgTimeByDistanceLimit(int distance, int raceLimit) {
        float returnValue = -1f;

        final String sql = "SELECT ROUND(AVG(t.Time), 2) as avgTime FROM (SELECT AVG(Time) as Time FROM Results WHERE Time > 0 AND Lane > 0 AND RaceDate < NOW()";
        final String distanceSql = " AND distance BETWEEN ? AND ?";
        final String groupBySql = " GROUP BY RaceDate";
        final String orderBySql = " ORDER BY RaceDate";
        final String limitSql = " DESC LIMIT ?";

        final StringBuilder sb = new StringBuilder();
        sb.append(sql);
        boolean orderAppended = false;
        if (distance > 0) {
            sb.append(distanceSql);
            sb.append(groupBySql);
            sb.append(orderBySql);
            orderAppended = true;
        }
        if (raceLimit > 0) {
            if (!orderAppended) {
                sb.append(groupBySql);
                sb.append(orderBySql);
            }
            orderAppended = true;
            sb.append(limitSql);
        }

        if (!orderAppended) {
            sb.append(groupBySql);
            sb.append(orderBySql);
        }

        sb.append(") t");

        try {
            final PreparedStatement stat = getConnection().prepareStatement(sb.toString());

            if (distance > 0) {
                stat.setInt(1, distance - 50);
                stat.setInt(2, distance + 50);
            }
            if (distance > 0 && raceLimit > 0) {
                stat.setInt(3, raceLimit);
            } else if (distance <= 0 && raceLimit > 0) {
                stat.setInt(1, raceLimit);
            }

            final ResultSet rs = stat.executeQuery();

            while (rs.next()) {
                returnValue = rs.getFloat("avgTime");
            }
        } catch (final SQLException e) {
            System.out.println(sb.toString());
            System.out.println(e.getMessage());
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return returnValue;
    }
}
