//using Cex.Models;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using System;
using System.Collections.Generic;
using System.Text;
using WebSocket.Interfaces;
using System.Linq;
using System.Threading.Tasks;
using System.Globalization;
using System.Text.RegularExpressions;
using Newtonsoft.Json.Serialization;
using Newtonsoft.Json.Linq;
using System.IO;

namespace WebSocket.Clients
{
    public class LowerCaseUnderscoreContractResolver : DefaultContractResolver
    {
        private Regex regex = new Regex("(?!(^[A-Z]))([A-Z])");

        protected override string ResolvePropertyName(string propertyName)
        {
            return regex.Replace(propertyName, "_$2"); //.ToLower();
        }
    }

    public class MicrosoftSecondsDateTimeConverter : DateTimeConverterBase
    {
        private static readonly DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            if (reader.TokenType == JsonToken.Null)
                return null;

            var token = JToken.Load(reader);
            if (token.Type == JTokenType.Integer)
            {
                var dt = epoch.AddMilliseconds((long)token);
                return dt;
            }

            // Not a Microsoft date.
            return new JsonSerializer().Deserialize(token.CreateReader(), objectType);
        }

        public override bool CanWrite { get { return false; } }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }
    }

    public class BinanceStreamModel
    {
        [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
        public string e { get; set; }
        [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
        public string E { get; set; }
        [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
        public string s { get; set; }
        [JsonProperty("k", NullValueHandling = NullValueHandling.Ignore)]
        public BinanceTickerData Data { get; set; }
        [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
        public string Ok { get; set; }
        [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
        public string Error { get; set; }
    }

    public class BinanceTickerData
    {
        [JsonProperty("t", NullValueHandling = NullValueHandling.Ignore)]
        [JsonConverter(typeof(MicrosoftSecondsDateTimeConverter))]
        public DateTime StartTime { get; set; } // : 123400000, // Kline start time
        [JsonProperty("T", NullValueHandling = NullValueHandling.Ignore)]
        [JsonConverter(typeof(MicrosoftSecondsDateTimeConverter))]
        public DateTime CloseTime { get; set; } //"T": 123460000, // Kline close time
        [JsonProperty("s", NullValueHandling = NullValueHandling.Ignore)]
        public string Symbol { get; set; } // "s": "BNBBTC",  // Symbol
        [JsonProperty("o", NullValueHandling = NullValueHandling.Ignore)]
        public decimal OpenPrice { get; set; } //  "o": "0.0010",  // Open price
        [JsonProperty("c", NullValueHandling = NullValueHandling.Ignore)]
        public decimal ClosePrice { get; set; } //  "c": "0.0020",  // Close price
        [JsonProperty("h", NullValueHandling = NullValueHandling.Ignore)]
        public decimal HighPrice { get; set; } //  "h": "0.0025",  // High price
        [JsonProperty("l", NullValueHandling = NullValueHandling.Ignore)]
        public decimal LowPrice { get; set; } //  "l": "0.0015",  // Low price
        [JsonProperty("v", NullValueHandling = NullValueHandling.Ignore)]
        public decimal Volume { get; set; } //  "v": "1000",    // Base asset volume

        //  "i": "1m",      // Interval
        //  "f": 100,       // First trade ID
        //  "L": 200,       // Last trade ID
        //  "n": 100,       // Number of trades
        //  "x": false,     // Is this kline closed?
        //  "q": "1.0000",  // Quote asset volume
        //  "V": "500",     // Taker buy base asset volume
        //  "Q": "0.500",   // Taker buy quote asset volume
        //  "B": "123456"   // Ignore
        //  }
    }

    public class BinanceModel: Command
    {
        public string stream { get; set; }
        public BinanceStreamModel data { get; set; }

        //"e": "kline",     // Event type
        //"E": 123456789,   // Event time
        //"s": "BNBBTC",    // Symbol
        //"k": {
        //  "t": 123400000, // Kline start time
        //  "T": 123460000, // Kline close time
        //  "s": "BNBBTC",  // Symbol
        //  "i": "1m",      // Interval
        //  "f": 100,       // First trade ID
        //  "L": 200,       // Last trade ID
        //  "o": "0.0010",  // Open price
        //  "c": "0.0020",  // Close price
        //  "h": "0.0025",  // High price
        //  "l": "0.0015",  // Low price
        //  "v": "1000",    // Base asset volume
        //  "n": 100,       // Number of trades
        //  "x": false,     // Is this kline closed?
        //  "q": "1.0000",  // Quote asset volume
        //  "V": "500",     // Taker buy base asset volume
        //  "Q": "0.500",   // Taker buy quote asset volume
        //  "B": "123456"   // Ignore
        //  }
    }

    public class BinanceClient : IWebClient
    {
        #region Members
        //private Dictionary<string, object> data = new Dictionary<string, object>();
        private Dictionary<string, Action<Command, string>> CommandAnswers = new Dictionary<string, Action<Command, string>>();
        private IWebSocketForClient server;
        #endregion

        /// <summary>
        /// ctor
        /// </summary>
        /// <param name="server"></param>
        public BinanceClient(IWebSocketForClient server)
        {
            this.server = server;
            Register();
        }

        public void Connected()
        {
            //SendAuth();
            //wait connected message?
        }
        
        private void Update(string name, Command command, string message, Type dataType) {
            var data = new Dictionary<string, object>();
            data[name]= JsonConvert.DeserializeObject(message, dataType, GetSettings());
            this.server.DataChanged("Binance", GetMarketOHLCVData(data));
            //this.server.updateData.Invoke(this.server.ExchangeId, "Binance", GetMarketOHLCVData(data));
        }

        private void Register()
        {
            On("kline", (c, obj) => Update(((BinanceModel)c).stream, c, obj, typeof(BinanceModel)));
            //On("connected", (c, obj) => SendAuth());
            //On("auth", (c, obj) => { if (c.Ok != null) { SetSubscribes(); } });
            On("ping", (c, obj) => SendPong());
        }

        public void Message(string message)
        {
            var parse = JsonConvert.DeserializeObject<BinanceModel>(message, GetSettings());
            //var arg = ((BinanceModel)parse);
            if (parse.data != null && CommandAnswers.ContainsKey(parse.data.e))
            {
                CommandAnswers[parse.data.e](parse, message);
            }
        }

        public void Tick()
        {
            //'ticker' or 'get-balance'
        }

        public void Data(byte[] data)
        {
        }

        public void Data(Dictionary<string, object> data)
        {
        }

        public void Closed(bool byUser, string reason)
        {
            //subscribed = false;
        }

        public void Error(Exception ex)
        {
        }

        public void Error(string reason)
        {
        }

        public void SendFailed(string data, Exception ex)
        {
        }

        private void Send(object obj)
        {
            server.Send(JsonConvert.SerializeObject(obj, GetSettings()));
        }

        private void SendPong()
        {
            Send(new Command() { E = "pong" });
        }

        private void On(string name, Action<Command, string> command)
        {
            CommandAnswers[name] = command;
        }

        private void Off(string name)
        {
            if (CommandAnswers.ContainsKey(name))
            {
                CommandAnswers.Remove(name);
            }
        }

        public Dictionary<string, IMarketOHLCVData> GetMarketOHLCVData(Dictionary<string, object> data)
        {
            return data.Select(x => new MarketOHLCVData()
            {
                PairSymbol = ((BinanceModel)x.Value).data.s,
                HighPrice = ((BinanceModel)x.Value).data.Data.HighPrice,
                LowPrice = ((BinanceModel)x.Value).data.Data.LowPrice,
                OpenPrice = ((BinanceModel)x.Value).data.Data.OpenPrice,
                ClosePrice = ((BinanceModel)x.Value).data.Data.ClosePrice,
                Volume = ((BinanceModel)x.Value).data.Data.Volume,
                TimeStamp = ((BinanceModel)x.Value).data.Data.CloseTime
            } as IMarketOHLCVData).ToDictionary(x => x.PairSymbol);
        }

        private JsonSerializerSettings GetSettings()
        {
            var settings = new JsonSerializerSettings()
            {
                ContractResolver = new LowerCaseUnderscoreContractResolver()
                //Formatting = Formatting.Indented
            };
            //settings.Converters.Add(new MicrosoftSecondsDateTimeConverter()); 
            return settings;
        }
    }
}
