using MongoDB.Bson;
using MongoDB.Driver;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace LolDataRequestLib
{
    /// <summary>
    /// 실시간으로 업데이트를 해야하는 데이터들의 추상팩토리클래스.
    /// </summary>
    internal abstract class ARequestData
    {

        /// <summary>
        /// DB Access 클라이언트.
        /// </summary>
        protected MongoClient mDBClient = new MongoClient(DBDefine.MONGODB주소);

        /// <summary>
        /// DB데이터베이스.
        /// </summary>
        protected IMongoDatabase eventDataBase = null;

        /// <summary>
        /// 가장 최근에 업데이트했던 데이터의 인덱스.
        /// 같은 데이터를 계속 업데이트 하는 것을 막는다.
        /// </summary>
        protected int mLastDataSequanceIndex = 0;


        /// <summary>
        /// 데이터를 가져올 DB 컬렉션(테이블) 이름
        /// </summary>
        protected string mCollectionName = "";

        /// <summary>
        /// db에서 가져와서 manager에 업데이트하기위해 가공된 BsonValue
        /// </summary>
        protected BsonValue mUpdatedBsonValue = null;

        /// <summary>
        /// 인스턴스를 생산하기위해 요청된 데이터타입.
        /// </summary>
        protected DBDefine.RequestDataType mRequestType = DBDefine.RequestDataType.BAN_AND_PICK;

        /// <summary>
        /// 팩토리 Create메서드
        /// </summary>
        /// <param name="recvRequestType"></param>
        /// <returns></returns>
        internal static ARequestData createRequestFactory(DBDefine.RequestDataType recvRequestType)
        {

            ARequestData bufInstance = null;

            try
            {
                
                switch (recvRequestType)
                {
                    case DBDefine.RequestDataType.BAN_AND_PICK:
                        bufInstance = new BanPickRequest();
                        bufInstance.mCollectionName = "champ_select";
                        break;
                    case DBDefine.RequestDataType.GAME_STATUS:
                        bufInstance = new GameStatusRequest();
                        bufInstance.mCollectionName = "stats_update";
                        break;
                    case DBDefine.RequestDataType.OBJECT_EVENT:
                        bufInstance = new ObjectDataRequest();
                        bufInstance.mCollectionName = "epic_monster_kill";
                        break;
                    case DBDefine.RequestDataType.STRUCT_EVENT:
                        bufInstance = new StructDataRequest();
                        bufInstance.mCollectionName = "building_destroyed";
                        break;
                    case DBDefine.RequestDataType.DRAGON_RESPAWN:
                        bufInstance = new DragonRequest();
                        bufInstance.mCollectionName = "queued_dragon_info";
                        break;
                    case DBDefine.RequestDataType.ATAKHAN_RESPAWN:
                        bufInstance = new AtakhanRequest();
                        bufInstance.mCollectionName = "queued_epic_monster_info";
                        break;
                    case DBDefine.RequestDataType.STRUCT_GOLD_EVENT:
                        bufInstance = new StructGoldDataRequest();
                        bufInstance.mCollectionName = "building_gold_grant";
                        break;
                }

                //조회를 위한 RequestType등록.
                bufInstance.mRequestType = recvRequestType;

                //데이터베이스(Schema)선택
                bufInstance.eventDataBase = bufInstance.mDBClient.GetDatabase("datalol");

                //ThreadPool에 업데이트워크를 등록
                ThreadPool.QueueUserWorkItem(o => { bufInstance.UpdateWorker(); });

            }
            catch (Exception ex)
            {

                Console.WriteLine(ex.ToString());
            }

            return bufInstance;
        }

        /// <summary>
        /// 인스턴스의 DB주소를 변경
        /// 20210614 현재 사용하지 않는다.
        /// Mongodb의 커넥션 인스턴스가 Mariadb와 달라서 비슷하게 접근했다가 Connection이 Disconnect되는 현상이 계속 발생했다.
        /// </summary>
        internal void resetDBAddress()
        {
            mDBClient = new MongoClient(DBDefine.MONGODB주소);
        }

        /// <summary>
        /// 업데이트 인덱스를 초기화한다.
        /// 20210608 첫용이 업데이트 되지 않는 버그를 수정하면서 추가.
        /// </summary>
        internal void initIndex()
        {
            this.mLastDataSequanceIndex = 0;
        }

        /// <summary>
        /// 인스턴스내에 데이터를 업데이트 하는 워커메서드.
        /// 해당메서드를 스레드풀에 넣고 반복문을 통해 계속 업데이트한다.
        /// </summary>
        internal void UpdateWorker()
        {

            while (DataManager.getInstance().IsupdateWorkersWork)
            {
                try
                {
                    //DB에서 데이터를 가져온다.
                    this.requestDataMongoDB();


                    //조회되는 데이터가 없을경우 테이블을 비운다.
                    if (mUpdatedBsonValue == null)
                    {
                        this.exchangeTable();
                    }
                    //조회되는 데이터가 기존 데이터와 INDEX가 같지 않을경우 데이터를 업데이트한다.
                    else if (mUpdatedBsonValue["sequenceIndex"].ToInt32() != this.mLastDataSequanceIndex)
                    {
                        this.exchangeTable();
                        this.mLastDataSequanceIndex = mUpdatedBsonValue["sequenceIndex"].ToInt32();
                    }

                    //조회 후 인터벌 시간동안 슬립.
                    Thread.Sleep(DataManager.getInstance().리퀘스트인터벌밀리초);

                }
                catch (Exception ex)
                {
                    //DataManager.getInstance().mCallback.errorReceivedByWorker(요청데이터, ex.ToString());
                    //break;
#if(DEBUG)
                    {
                        Console.WriteLine(ex.ToString());
                    }
#endif
                }

            }

        }

        /// <summary>
        /// DB에서 데이터를 요청하는 메서드.
        /// </summary>
        protected virtual void requestDataMongoDB()
        {
            try
            {
                //var filter = Builders<BsonDocument>.Filter.Eq("RequestGameID", DataManager.getInstance().mPlatformGameID);

                var projection = Builders<BsonDocument>.Projection
                    .Exclude("_id")
                    .Include("eventDocument");

                List<BsonDocument> documents = eventDataBase.GetCollection<BsonDocument>(this.mCollectionName)
                    .Find(new BsonDocument())//.Find(filter)
                    .SortByDescending(x => x["sequenceIndex"])
                    .Project(projection)
                    .Limit(1)
                    .ToList();


                if (documents.Count != 0)
                {
                    mUpdatedBsonValue = documents.Last()["eventDocument"];
                }
                else
                {
                    mUpdatedBsonValue = null;
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }

        void exchangeTable()
        {
            try
            {
                Dictionary<int, BsonValue> bufHash = null;

                switch (this.mRequestType)
                {
                    case DBDefine.RequestDataType.BAN_AND_PICK:
                        DataManager.getInstance().밴픽데이터 = this.mUpdatedBsonValue;
                        break;
                    case DBDefine.RequestDataType.GAME_STATUS:
                        DataManager.getInstance().경기데이터 = this.mUpdatedBsonValue;
                        //DataManager.getInstance().경기시간 = this.mUpdatedBsonValue["gameTime"].ToInt32() / 1000;
                        //Console.WriteLine("gametime : " + this.mUpdatedBsonValue["gameTime"].ToInt32() / 1000);
                        break;
                    case DBDefine.RequestDataType.OBJECT_EVENT:
                        DataManager.getInstance().오브젝트데이터 = this.mUpdatedBsonValue;
                        break;
                    case DBDefine.RequestDataType.STRUCT_EVENT:
                        DataManager.getInstance().건물데이터 = this.mUpdatedBsonValue;
                        break;
                    case DBDefine.RequestDataType.DRAGON_RESPAWN:
                        DataManager.getInstance().드래곤리스폰 = this.mUpdatedBsonValue;
                        break;
                    case DBDefine.RequestDataType.STRUCT_GOLD_EVENT:
                        DataManager.getInstance().타워골드데이터 = this.mUpdatedBsonValue;
                        break;
                        //case DBDefine.RequestDataType.ATAKHAN_RESPAWN:
                        //    DataManager.getInstance().아타칸리스폰 = this.mUpdatedBsonValue;
                        //    break;
                }
            }
            catch(Exception ex) { }
        }

    }
}
