using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.Data;  // DataTable
using System.Diagnostics;   // Process

namespace Post
{
    class Program
    {
        public static ProcessDatabase processDatabase;

        static void Main(string[] args)
        {
            ConfigPost config = new ConfigPost();
            ConfigConn configConn = new ConfigConn();
            ConvertQuery convertQuery = new ConvertQuery();
            ConstValues constValues = new ConstValues();
            ConstFunc constFunc = new ConstFunc();

            string homePath = Environment.GetEnvironmentVariable("POST_HOME");
            SqlFileCreater sqlFileCreater = new SqlFileCreater(homePath, "test3");

            List<Struct.TraceDml> tracingRecordDmlList = new List<Struct.TraceDml>();
            List<Struct.TracingRecordDataDefinition> tracingDefinitionDataList = new List<Struct.TracingRecordDataDefinition>();

            List<string> dmlQueryList = new List<string>();

            //if(args.Length == 0)
            //{
            //    Console.WriteLine("no required argument");
            //    return;
            //}

            //config = cxs.LoadPostConfig(args[0]);
            config = config.LoadConfig("test");
            if (config == null)
            {
                KillProcess(config.DB_TYPE, null);
            }
            Console.WriteLine("write loggg");
            constFunc.WriteLog(constValues.CONST_LOGGER_TYPE_INFO, "Successed Load Configuration file.", constValues.CONST_LOGGER_POST, "test3", config);
            configConn = configConn.LoadConn(config.DBCONN);

            // connection db
            processDatabase = new ProcessDatabase(config.DB_TYPE);
            //bool isConn = processDatabase.ConnectionDB(configConn.IP, configConn.PORT, configConn.DBNAME, configConn.USERNAME, configConn.PASSWORD);
            Object conn = processDatabase.ConnectionDB(configConn.IP, configConn.PORT, configConn.DBNAME, configConn.USERNAME, configConn.PASSWORD);
            if (conn == null)
            {
                KillProcess(config.DB_TYPE, null);
            }

            // load tracing file record
            ProcessTracing(config, config.DB_TYPE, conn, ref tracingRecordDmlList, ref tracingDefinitionDataList);
            Console.WriteLine(">>> tracingRecordDmlList : " + tracingRecordDmlList.Count);
            Console.WriteLine(">>> tracingDefinitionDataList : " + tracingDefinitionDataList.Count);

            // 1. ddl 쿼리 생성 후 적용 / 파일 생성
            // create query
            //convertQuery.CreateQuery(config.DB_TYPE, conn, tracingRecordDmlList, tracingDefinitionDataList, ref commandList);
            string ddlQuery = convertQuery.ConvertDdlQuery(config.DB_TYPE, conn, tracingDefinitionDataList);
            if(ddlQuery == "")
            {
                Console.WriteLine("ddl 쿼리 변환에 실패하였습니다.");
                //KillProcess(config.DB_TYPE, conn);
            }

            Console.WriteLine("\nddlQuery : " + ddlQuery);

            if(config.CREATE_TABLE != "NONE")
            {
                Console.WriteLine("config.CREATE_TABLE : " + config.CREATE_TABLE);
                if(config.CREATE_TABLE != "ON_DB")
                {
                    Console.WriteLine("no on_db");
                    // create sql file
                    bool isWrite = sqlFileCreater.WriteFile(ddlQuery);
                    if(!isWrite)
                    {
                        Console.WriteLine("failed write sql file");
                    }
                }

                Console.WriteLine("+++++++++++++++++++++++++++++++ : "+ config.CREATE_TABLE);
                if (config.CREATE_TABLE != "ON_SCRIPT")
                {
                    Console.WriteLine("no_onscript");
                    bool isExecute = processDatabase.ExecuteDdl(ddlQuery);
                    Console.WriteLine("isExecute :: " + isExecute);
                    if(!isExecute)
                    {
                        Console.WriteLine("해당 테이블이 이미 존재 합니다.");
                    }
                }
            }

            // 2. dml 쿼리 생성 후 적용
            //dmlQueryList = convertQuery.ConvertDmlQuery(config.DB_TYPE, conn, tracingRecordDmlList);

        }

        static void ProcessTracing(ConfigPost config, String type, Object conn, ref List<Struct.TraceDml> tracingRecordDmlList, ref List<Struct.TracingRecordDataDefinition> tracingDefinitionDataList)
        {
            Console.WriteLine("------ProcessTracing------");
            ConstValues constValues = new ConstValues();
            ConstValuesTracing constValuesTracing = new ConstValuesTracing();
            ConstFunc constFunc = new ConstFunc();
            TracingFunc trcFunc = new TracingFunc(constValues.CONST_LOGGER_POST, config.TRACINGFILE, config);
            Struct.ACDSN dsn = new Struct.ACDSN(0, 0, 0);

            String alias = config.TRACINGFILE;
            bool isFirst = true;     // 최초 실행여부

            List<ConstStruct.MetaInfo> metaInfoList = new List<ConstStruct.MetaInfo>();

            try
            {
                while(true)
                {
                    int resultActloader = trcFunc.InitActLoader(config.TRACINGFILE, config.TRACINGFILE_DEST);
                    Console.WriteLine("[ resultActloader ] :: " + resultActloader);
                    if (resultActloader < 0)
                    {
                        constFunc.WriteLog(constValues.CONST_LOGGER_TYPE_FATAL, "Failed to init load trace file.", constValues.CONST_LOGGER_POST, alias, config);
                        KillProcess(type, conn);
                    }

                    long searchIndex = trcFunc.SearchFileSCN(dsn.high, dsn.wrap, dsn.base_n, 0, false);
                    Console.WriteLine("[ searchIndex ] :: " + searchIndex);
                    if (searchIndex == -1)
                    {
                        constFunc.WriteLog(constValues.CONST_LOGGER_TYPE_FATAL, "Failed to search scn index in trace file.", constValues.CONST_LOGGER_POST, alias, config);
                        KillProcess(type, conn);
                    }

                    bool isSeek = trcFunc.SeekScn(ref dsn.high, ref dsn.wrap, ref dsn.base_n, 0, false);
                    Console.WriteLine("[ isSeek ] :: " + isSeek);
                    if (!isSeek)
                    {
                        constFunc.WriteLog(constValues.CONST_LOGGER_TYPE_DEBUG, "seek scn failed.", constValues.CONST_LOGGER_POST, alias, config);
                        KillProcess(type, conn);
                    }

                    StructMessagepack.TracingRecordHeaderTransaction headerTransaction = new StructMessagepack.TracingRecordHeaderTransaction();

                    // definition 정보
                    tracingDefinitionDataList = trcFunc.GetDefinitionInfoFromTraceFile(alias, config.TRACINGFILE_DEST);
                    Console.WriteLine("tracingDefinitionDataList count : " + tracingDefinitionDataList.Count);

                    while (true)
                    {
                        object record = new object();
                        object recoreMsgpack = new object();    // lob
                        Struct.TracingRecordBuffer buffer = new Struct.TracingRecordBuffer();
                        bool isTxEnd = false;   // 마지막 record 여부
                        string schemaName = "";
                        string tableName = "";

                        long r = trcFunc.GetNextRecordWithBufferImported(ref record, ref recoreMsgpack, ref buffer);
                        if(r > 0)
                        {
                            headerTransaction = MessagePack.MessagePackSerializer.Deserialize<StructMessagepack.TracingRecordHeaderTransaction>(buffer.data);

                            short trc_record_type = headerTransaction.header.trc_record_type;
                            schemaName = Encoding.UTF8.GetString(headerTransaction.header.schema);
                            tableName = Encoding.UTF8.GetString(headerTransaction.header.object_n);
                            Console.WriteLine("schemaName : " + schemaName);
                            Console.WriteLine("tableName : " + tableName);

                            if (isFirst)
                            {
                                isFirst = false;
                            }

                            bool isSkip = false;
                            bool isAdd = true;

                            if (trc_record_type != constValuesTracing.CONST_RECORD_TYPE_DEFINITION && trc_record_type != constValuesTracing.CONST_RECORD_TYPE_DML_INSERT)
                            {
                                // definition과 dml(insert)를 제외한 나머지는 skip
                                isSkip = true;
                            }

                            if(!isSkip)
                            {
                                if (trc_record_type != constValuesTracing.CONST_RECORD_TYPE_DEFINITION && trc_record_type != constValuesTracing.CONST_RECORD_TYPE_DML_INSERT)
                                {
                                    isAdd = false;
                                }

                                if(isAdd)
                                {
                                    if(trc_record_type == constValuesTracing.CONST_RECORD_TYPE_DEFINITION)
                                    {
                                        AddTracingRecordList(trc_record_type, record, ref tracingRecordDmlList, ref tracingDefinitionDataList);
                                        Console.WriteLine("tracingDefinitionDataList1 : " + tracingDefinitionDataList[0].table_def.table_name);
                                        Console.WriteLine("tracingDefinitionDataList2 : " + tracingDefinitionDataList[0].column_def[0].name);
                                        Console.WriteLine("tracingDefinitionDataList3 : " + tracingDefinitionDataList[0].column_def[0].datatype);

                                        // check db connection
                                        //bool isConn = processDatabase.CheckConnState();
                                        //Console.WriteLine("isConn : " + isConn);

                                        // check table exist
                                        int isExist = processDatabase.IsExistTable(schemaName, tableName, conn);
                                        if(isExist == 0)
                                        {
                                            Console.WriteLine("해당 테이블이 존재하지 않습니다.");
                                        }
                                    }
                                    else if (trc_record_type == constValuesTracing.CONST_RECORD_TYPE_DML_INSERT)
                                    {
                                        AddTracingRecordList(trc_record_type, record, ref tracingRecordDmlList, ref tracingDefinitionDataList);
                                        Console.WriteLine("tracingRecordDmlList1 > " + tracingRecordDmlList[0].tracingRecordList[0].dataList.Count);
                                    }
                                } else
                                {
                                    continue;
                                }

                                if (trc_record_type != constValuesTracing.CONST_RECORD_TYPE_DEFINITION && (headerTransaction.header.tx_indicator == 2 || headerTransaction.header.tx_indicator == 3) &&
                                                headerTransaction.header.record_continued == 0)
                                {
                                    isTxEnd = true;
                                }

                                // transaction의 마지막 레코드이거나 최대 2만건 읽은 경우 -> dml, definition 리스트를 모두 반환.. 이후에 쿼리 생성 하고 옵션에 따라 처리
                                if (isTxEnd && (trcFunc.traceCurPosition >= trcFunc.traceTotalLength))
                                {
                                    DataTable dtMeta = new DataTable();
                                    if (trc_record_type == constValuesTracing.CONST_RECORD_TYPE_DML_INSERT)
                                    {
                                        // # todo
                                        processDatabase.SetMetaInfoListByTraceDmlList(tracingRecordDmlList, ref metaInfoList);
                                        Console.WriteLine("metaInfoList :: " + metaInfoList[0].schema);
                                        dtMeta = processDatabase.GetTableMetaInfo("", schemaName, tableName);

                                        if (dtMeta == null)
                                        {
                                            Console.WriteLine("dtMeta null");
                                            KillProcess(type, conn);
                                        }
                                        Console.WriteLine("dtMeta :: " + dtMeta.Rows.Count);

                                        Console.WriteLine("file load end");
                                        break;
                                    }
                                }

                            }
                        }
                    }
                    break;
                }
            }
            catch (Exception e)
            {
                Console.WriteLine("process trc : " + e.ToString());
            }
        }

        #region AddTracingRecordList() : dml, ddl, definition 타입에 따라 list 에 add 함
        public static void AddTracingRecordList(short recordType, object traceRecord, ref List<Struct.TraceDml> tracingRecordDmlList, ref List<Struct.TracingRecordDataDefinition> traceDefinitionDataList)
        {
            ConstFunc constFunc = new ConstFunc();
            ConstValuesTracing constValuesTracing = new ConstValuesTracing();

            try
            {
                uint txIndex = 0;
                if (recordType == constValuesTracing.CONST_RECORD_TYPE_DML_INSERT)
                {
                    Struct.TracingRecordDml record = (Struct.TracingRecordDml)traceRecord;

                    AddTracingRecordDmlList(ref tracingRecordDmlList, record);
                    txIndex = record.header.tx_index + 1;
                    Console.WriteLine("tracingRecordDmlList cnt : " + tracingRecordDmlList.Count);
                }
                else if (recordType == constValuesTracing.CONST_RECORD_TYPE_DEFINITION)
                {
                    // record data 를 tracing record 구조체로 가져옴
                    Struct.TracingRecordDefinition record = (Struct.TracingRecordDefinition)traceRecord;
                    record.data.table_def.table_name = record.header.schema + "." + record.header.object_n;
                    for (int i = 0; i < traceDefinitionDataList.Count; i++)     // definition list 동일 테이블존재하면 삭제하고 새로 추가함
                    {
                        string[] arr = traceDefinitionDataList[i].table_def.table_name.Split('.');
                        string schema = "";
                        string table = "";
                        if (arr.Length == 2)
                        {
                            schema = arr[0];
                            table = arr[1];
                        }
                        if (schema.Equals(record.header.schema, StringComparison.OrdinalIgnoreCase) && table.Equals(record.header.object_n, StringComparison.OrdinalIgnoreCase))
                        {
                            traceDefinitionDataList.RemoveAt(i);
                            break;
                        }
                    }
                    traceDefinitionDataList.Add(record.data);

                    record = null;
                }
            }
            catch (Exception e)
            {
                //logMessage = "[System Exception] PostProgram AddTracingRecordList() exception : " + ex.Message.ToString();
                //constFunc.WriteLog(constValues.CONST_LOGGER_TYPE_FATAL, logMessage, constValues.CONST_LOGGER_POST, alias, config);
            }
        }
        #endregion

        #region AddTracingRecordDmlList() : tracingRecordDmlList 를 type, schema, table 에 따라 나누어 add 처리
        public static void AddTracingRecordDmlList(ref List<Struct.TraceDml> tracingRecordDmlList, Struct.TracingRecordDml tracingRecord)
        {
            ConstValuesTracing constValuesTracing = new ConstValuesTracing();
            try
            {
                string container = "";
                string schema = tracingRecord.header.schema;
                string table = tracingRecord.header.object_n;

                if (tracingRecord.header.trc_record_type == constValuesTracing.CONST_RECORD_TYPE_SEQUENCE_OPER)
                {
                    // sequence 는 따로 add 처리
                    Struct.TraceDml item = new Struct.TraceDml();
                    item.trc_record_type = tracingRecord.header.trc_record_type;
                    item.partition = tracingRecord.header.partition;
                    item.container = container;
                    item.schema = schema;
                    item.object_n = table;
                    item.updatemask = "";
                    item.tracingRecordList.Add(tracingRecord);
                    tracingRecordDmlList.Add(item);

                    item = null;
                }
                else
                {
                    string updatemask = "";
                    if (tracingRecord.header.trc_record_type == constValuesTracing.CONST_RECORD_TYPE_DML_INSERT)
                    {
                        // insert 는 값 존재여부로 작성
                        for (int i = 0; i < tracingRecord.dataList.Count; i++)
                        {
                            if (tracingRecord.dataList[i].bytes != null && tracingRecord.dataList[i].bytes.Length > 0)
                                updatemask += (tracingRecord.dataList[i].colno.ToString() + "1");
                            else
                                updatemask += (tracingRecord.dataList[i].colno.ToString() + "0");
                        }
                    }

                    // 해당하는 tracingRecordDmlList 의 schema, table 이 없으면 생성하여 추가
                    if (tracingRecordDmlList.Count < 1)
                    {
                        Struct.TraceDml item = new Struct.TraceDml();
                        item.trc_record_type = tracingRecord.header.trc_record_type;
                        item.partition = tracingRecord.header.partition;
                        item.container = container;
                        item.schema = schema;
                        item.object_n = table;
                        item.updatemask = updatemask;
                        item.tracingRecordList.Add(tracingRecord);
                        tracingRecordDmlList.Add(item);

                        item = null;
                    }
                    else
                    {
                        bool isAdd = true;

                        // grouping
                        for (int i = tracingRecordDmlList.Count - 1; i >= 0; i--)
                        {
                            // insert 일 경우는 전체 flag(값존재여부) 값 비교함
                            // 하나의 transaction 에 다른 dml 타입이 존재하면 dml 타입이 이어져 존재하는 케이스일 경우 일괄처리 지원 가능함
                            if (tracingRecordDmlList[i].container.Equals(container, StringComparison.OrdinalIgnoreCase) && tracingRecordDmlList[i].schema.Equals(schema, StringComparison.OrdinalIgnoreCase) &&
                                tracingRecordDmlList[i].object_n.Equals(table, StringComparison.OrdinalIgnoreCase))
                            {
                                // schema, table 이 같은 리스트의 마지막 항목 찾아서 dml 타입과 컬럼 구성이 같으면 일괄처리 대상으로 판단함
                                if (tracingRecordDmlList[i].trc_record_type == tracingRecord.header.trc_record_type)
                                {
                                    if (tracingRecordDmlList[i].updatemask.Equals(updatemask))
                                    {
                                        tracingRecordDmlList[i].tracingRecordList.Add(tracingRecord);
                                        isAdd = false;
                                        break;
                                    }
                                }
                                else
                                {
                                    break;
                                }
                            }
                        }

                        if (isAdd)
                        {
                            Struct.TraceDml item = new Struct.TraceDml();
                            item.trc_record_type = tracingRecord.header.trc_record_type;
                            item.partition = tracingRecord.header.partition;
                            item.container = container;
                            item.schema = schema;
                            item.object_n = table;
                            item.updatemask = updatemask;
                            item.tracingRecordList.Add(tracingRecord);
                            tracingRecordDmlList.Add(item);

                            item = null;
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                //logMessage = "[System Exception] PostProgram AddTracingRecordDmlList() exception : " + ex.Message.ToString();
                //constFunc.WriteLog(constValues.CONST_LOGGER_TYPE_FATAL, logMessage, constValues.CONST_LOGGER_POST, alias, config);
            }
        }
        #endregion

        static void KillProcess(String type, Object conn)
        {
            Console.WriteLine("프로세스 종료");
            // db disconnect
            ProcessDatabase processDatabase = new ProcessDatabase(type);

            if(conn != null)
            {
                processDatabase.DisconnectDatabase(conn);
            }

            Process process = Process.GetCurrentProcess();
            process.Refresh();
            process.Kill();
        }
    }
}
