using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using WebSocket.Interfaces;
using System.Linq;
using System.IO;
using System.IO.Compression;
using System.Text;

namespace WebSocket.Clients
{
    public class HUOBIClient : IWebClient
    {
        #region Members
        internal class HUOBIResponse
        {
            [JsonProperty(PropertyName = "ts")]
            public long Timestamp { get; set; }
            public HUOBIMarket Tick { get; set; }
            [JsonProperty(PropertyName = "ch")]
            public string Name { get; set; }
            public string Pair {
                get {
                    var array = this.Name.Split('.');
                    return array.Length > 1 ? array[1].ToUpper() : string.Empty;
                }
                set { }
            }
        }

        internal class HUOBIMarket
        {
            [JsonProperty(PropertyName = "open")]
            public decimal Open { get; set; }
            [JsonProperty(PropertyName = "close")]
            public decimal Close { get; set; }
            [JsonProperty(PropertyName = "low")]
            public decimal Low { get; set; }
            [JsonProperty(PropertyName = "high")]
            public decimal High { get; set; }
            [JsonProperty(PropertyName = "vol")]
            public decimal Volume { get; set; }
            //ts":1535719364915,
            //"tick":{"id":1535719320,"open":0.000029570000000000,"close":0.000029630000000000,
            //    "low":0.000029570000000000,"high":0.000029630000000000,
            //    "amount":16.450000000000000000,"vol":0.000487268300000000000000000000000000,"count":2}}
        }
        private IWebSocketForClient server;
        private Dictionary<string, Action<Command, string>> CommandAnswers = new Dictionary<string, Action<Command, string>>();
        #endregion

        /// <summary>
        /// ctor
        /// </summary>
        /// <param name="server"></param>
        public HUOBIClient(IWebSocketForClient server)
        {
            this.server = server;
            Register();
        }
        
        public void Connected()
        {
            //SendAuth();
            //wait connected message?
        }
        
        public void StartRequest(string[] pairs) {
            Send(new { sub = $"market.{pairs[0].ToLower()}.kline.1min", id = $"id{1}" });
            //Send(new {
            //    req = $"\"market.ethbtc.kline.1min\"",
            //    id = $"\"id10\""
            //});

            //server.Send($"{{\"sub\":\"market.{pairs[0].ToLower()}.kline.1min\", \"id\":\"id10\"}}");
            //{
            //  "req": "market.ethbtc.kline.1min",
            //  "id": "id10"
            //}
        }

        private void Register()
        {
            On("connected", (c, obj) => Connected());
            On("ping", (c, obj) => SendPong());
            //On("ohlcv1m", (c, obj) => Update(c.E, c, obj, typeof(Summary)));
        }

        public void Message(string message)
        {
            var parse = JsonConvert.DeserializeObject<Command>(message, GetSettings());
            if (parse.Oid != null && CommandAnswers.ContainsKey(parse.E + parse.Oid))
            {
                CommandAnswers[parse.E + parse.Oid](parse, message);
            }
            if (CommandAnswers.ContainsKey(parse.E))
            {
                CommandAnswers[parse.E](parse, message);
            }
            /*
             // response
                {
                "status": "ok",
                  "rep": "market.btccny.kline.1min",
                  "tick": [
                    {
                      "amount": 1.6206,
                      "count":  3,
                      "id":     1494465840,
                      "open":   9887.00,
                      "close":  9885.00,
                      "low":    9885.00,
                      "high":   9887.00,
                      "vol":    16021.632026
                    },
                    {
                      "amount": 2.2124,
                      "count":  6,
                      "id":     1494465900,
                      "open":   9885.00,
                      "close":  9880.00,
                      "low":    9880.00,
                      "high":   9885.00,
                      "vol":    21859.023500
                    }
                  ]
                }
             */
        }

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

        public void Data(byte[] data)
        {
            using (var compressedStream = new MemoryStream(data))
            using (var zipStream = new GZipStream(compressedStream, CompressionMode.Decompress))
            using (var resultStream = new MemoryStream())
            {
                zipStream.CopyTo(resultStream);
                var tickData = JsonConvert.DeserializeObject(Encoding.Default.GetString(resultStream.ToArray()), typeof(HUOBIResponse), GetSettings()) as HUOBIResponse;
                if (tickData.Tick != null)
                {
                    //this.server.updateData.Invoke(server.ExchangeId, "HUOBI", GetMarketOHLCVData(new Dictionary<string, object> { { tickData.Pair, tickData } }));
                    this.server.DataChanged("HUOBI", GetMarketOHLCVData(new Dictionary<string, object> { { tickData.Pair, tickData } }));
                }
            }
        }

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

        public void Closed(bool byUser, string reason)
        {
            //{"unsub": "market.btccny.trade.detail",  "id": "id4"}
        }

        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 =>
            {
                var item = x.Value as HUOBIResponse;
                return new MarketOHLCVData()
                {
                    PairSymbol = item.Pair,
                    HighPrice = item.Tick.High,
                    LowPrice = item.Tick.Low,
                    OpenPrice = item.Tick.Open,
                    ClosePrice = item.Tick.Close,
                    Volume = item.Tick.Volume,
                    TimeStamp = ConvertFromLong(item.Timestamp),
                } as IMarketOHLCVData;
            }).ToDictionary(x => x.PairSymbol);
        }

        #region Helpers
        private DateTime _1970Time = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
        private long ConvertToLong(DateTime time)
        {
            return (long)(time - _1970Time).TotalMilliseconds;
        }
        private DateTime ConvertFromLong(long ms)
        {
            return _1970Time.AddMilliseconds(ms);
        }

        private JsonSerializerSettings GetSettings()
        {
            var settings = new JsonSerializerSettings()
            {
                ContractResolver = new LowerCaseUnderscoreContractResolver()
                //Formatting = Formatting.Indented
            };
            settings.Converters.Add(new Newtonsoft.Json.Converters.UnixDateTimeConverter());
            return settings;
        }
        #endregion
    }
}
