package com.ruoyi.common.dev.mybatis;

import java.util.ArrayList;
import java.util.List;

import static java.lang.System.out;

public class SQLQuery
{
    private static class Error
    {
        public final String    log;
        public final Throwable e;

        public Error(String log, Throwable e)
        {
            this.log = log;
            this.e = e;
        }
    }

    private       String       mapper;
    private       String       sql;
    private       String       parms;
    private       String       columns;
    private       long         result;
    private final List<String> warnings;
    private final List<String> rows;
    private final List<Error>  errors;
    private       long         counter;
    private       long         takeTime;
    private       long         readRows;
    private       long         startTime;
    private       long         effect;

    private static final int      OUTPUT_ROWS = 5; // -1: not limit, 0: disable, positive number: max
    private static final Object       LOCK        = new Object();

    public SQLQuery()
    {
        warnings = new ArrayList<>();
        rows = new ArrayList<>();
        errors = new ArrayList<>();
        Clear();
    }

    public void Clear()
    {
        sql = "";
        parms = "";
        columns = "";
        result = -1;
        readRows = 0;
        takeTime = 0;
        effect = -1;
        startTime = System.currentTimeMillis();
        warnings.clear();
        rows.clear();
        errors.clear();
    }

    public void Mapper(String mapper)
    {
        this.mapper = mapper;
    }

    public void Debug(String mapper, String log)
    {
        Add(mapper, log);
    }

    public void Trace(String mapper, String log)
    {
        Add(mapper, log);
    }

    public void Warning(String mapper, String log)
    {
        warnings.add(log);
        Mapper(mapper);
    }

    public void Error(String mapper, String log)
    {
        AddError(log, null);
        Mapper(mapper);
    }

    public void Exception(String mapper, String log, Throwable e)
    {
        AddError(log, e);
        Mapper(mapper);
    }

    private void AddError(String log, Throwable e)
    {
        errors.add(new Error(log, e));
    }

    private void AddOther(String log)
    {
        // other.add(log);
    }

    private void SetResult(String log)
    {
        String s = log.substring("<==      Total: ".length());
        int index = s.lastIndexOf("<-- [");
        if(index >= 0)
            s = s.substring(0, index);
        s = s.trim();
        try
        {
            result = Long.parseLong(s);
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
        if(result <= 0 || result == readRows)
            EndQuery();
    }

    private void SetEffect(String log)
    {
        String s = log.substring("<==    Updates: ".length());
        try
        {
            effect = Long.parseLong(s);
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
        EndQuery();
    }

    private void EndQuery()
    {
        long l = System.currentTimeMillis();
        takeTime = l - startTime;

        Output();
    }

    private void AddRow(String log)
    {
        if(OUTPUT_ROWS < 0 || readRows < OUTPUT_ROWS)
            rows.add(log.substring("<==        Row: ".length()));
        readRows++;
        if(readRows == result)
            EndQuery();
    }

    public void Output()
    {
        if(null == sql || sql.isEmpty())
            return;
        synchronized(LOCK) {
            MybatisStdOutputLog.cout();
            String action = "查询";
            if(effect >= 0)
                action = "更新";

            // SQL
            MybatisStdOutputLog.cout("[Mybatis " + action + "  ] " + Thread.currentThread().getName() + ": " + counter + " -> " + mapper, MybatisStdOutputLog.SQL_START_STYLE);
            MybatisStdOutputLog.cout(sql, MybatisStdOutputLog.SQL_STATEMENT_STYLE);
            if(null != parms && !parms.isEmpty())
                MybatisStdOutputLog.cout(parms, MybatisStdOutputLog.SQL_PARAMETER_STYLE);

            SQLStatement sqlStatement = new SQLStatement(sql, parms);
            String parsedSql = sqlStatement.Parse();
            if(null != parsedSql)
                MybatisStdOutputLog.cout(/*"编译SQL: \n" + */parsedSql, MybatisStdOutputLog.SQL_STYLE);

            // Select query
            if(result >= 0)
            {
                MybatisStdOutputLog.cout("结果行数: " + result, MybatisStdOutputLog.SQL_RESULT_STYLE);
                MybatisStdOutputLog.cout(columns, MybatisStdOutputLog.SQL_COLUMNS_STYLE);
                int size = rows.size();
                if(OUTPUT_ROWS != 0 && size > 0)
                {
                    for(int i = 0; i < size; i++)
                    {
                        MybatisStdOutputLog.cout(String.format("%5d", i) + ": " + rows.get(i), MybatisStdOutputLog.SQL_ROWS_STYLE);
                    }
                    long remain = readRows - size;
                    if(remain > 0)
                        MybatisStdOutputLog.cout("更多 : " + remain + "条数据......", MybatisStdOutputLog.SQL_ROWS_STYLE);
                }
            }
            // Update
            if(effect >= 0)
            {
                MybatisStdOutputLog.cout("影响行数: " + effect, MybatisStdOutputLog.SQL_RESULT_STYLE);
            }

            // Warning
            if(!warnings.isEmpty())
            {
                MybatisStdOutputLog.cout("警告: " + warnings.size(), MybatisStdOutputLog.SQL_RESULT_STYLE);
                for(int i = 0; i < warnings.size(); i++)
                {
                    MybatisStdOutputLog.cout(String.format("%2d", i) + ": " + warnings.get(i), MybatisStdOutputLog.SQL_ROWS_STYLE);
                }
            }

            // Error / Exception
            if(!errors.isEmpty())
            {
                MybatisStdOutputLog.cout("错误: " + errors.size(), MybatisStdOutputLog.SQL_RESULT_STYLE);
                for(int i = 0; i < errors.size(); i++)
                {
                    Error error = errors.get(i);
                    MybatisStdOutputLog.cout(String.format("%2d", i) + ": " + error.log, MybatisStdOutputLog.SQL_ROWS_STYLE);
                    error.e.printStackTrace(out);
                }
            }
            MybatisStdOutputLog.cout("[Mybatis 结束  ] " + Thread.currentThread().getName() + ": " + counter + " -> Time(" + takeTime + " ms) " + mapper + " ---------------------------------------------------------", MybatisStdOutputLog.SQL_START_STYLE);
        }
        Clear();
    }

    private void Add(String mapper, String log)
    {
        if(log.startsWith("==>")) {
            if(log.startsWith("==>  Preparing: "))
            {
                counter++;
                Mapper(mapper);
                sql = log.substring("==>  Preparing: ".length());
            }
            else if(log.startsWith("==> Parameters: "))
            {
                parms = log.substring("==> Parameters: ".length());
                int index = parms.lastIndexOf("<-- [");
                if(index >= 0)
                    parms = parms.substring(0, index);
                parms = parms.trim();
            }
            else
            {
                AddOther(log);
            }
        }
        else if(log.startsWith("<==")) {
            if(log.startsWith("<==    Columns: "))
            {
                columns = log.substring("<==    Columns: ".length());
            }
            else if(log.startsWith("<==      Total: "))
            {
                SetResult(log);
            }
            else if(log.startsWith("<==        Row: "))
            {
                AddRow(log);
            }
            else if(log.startsWith("<==    Updates: "))
            {
                SetEffect(log);
            }
            else
            {
                AddOther(log);
            }
        }
        else
        {
            AddOther(log);
        }
    }
}
