﻿using Common;
using Common.Interfaces;
using Storage;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Xml.Linq;

namespace HistoryProvider
{
    internal struct TickerData
    {
        public string Ticker;
        public string ShortName;
        public string Name;
        public int LotSize;
        public byte Digits;
        public string Market;
        public string ID;
    }

    internal struct TimeframeData
    {
        public Timeframes Tf;
        public string TfKey;
        public string TfParam;
        public string Datf;
    }

    public class FinamHistoryProvider : IHistoryProvider
    {
        private List<TickerData> tickers;
        private List<TimeframeData> tfds;
        private string baseUrl;
        private IRequester requester = null;

        public FinamHistoryProvider() : this(null)
        {
        }

        public FinamHistoryProvider(IRequester req = null)
        {
            tfds = new List<TimeframeData>();
            tickers = new List<TickerData>();
            baseUrl = "";
            this.requester = req;
            if (this.requester == null) this.requester = new Requester();
        }

        public async Task Initialize()
        {
            tfds.Clear(); tickers.Clear();

            try 
            {
                XDocument xd = await LoadConfig();
                if (xd == null) return;

                // timeframes
                var tfNodes = xd.Root.Element("Timeframes").Elements("Timeframe");
                foreach (var tfNode in tfNodes)
                {
                    var tfAttr = tfNode.Attribute("Tf");
                    var tfKeyAttr = tfNode.Attribute("TfKey");
                    var tfParamAttr = tfNode.Attribute("TfParam");
                    var datfAttr = tfNode.Attribute("Datf");
                    if (tfAttr == null || tfKeyAttr == null || tfParamAttr == null || datfAttr == null)
                        continue;
                    int tf;
                    if (!Int32.TryParse(tfAttr.Value, out tf))
                        continue;

                    TimeframeData tfd = new TimeframeData()
                    {
                        Tf = (Timeframes)tf,
                        TfKey = tfKeyAttr.Value,
                        TfParam = tfParamAttr.Value,
                        Datf = datfAttr.Value
                    };
                    tfds.Add(tfd);
                }

                // tickers
                var tickerNodes = xd.Root.Element("Tickers").Elements("Ticker");
                foreach (var tickerNode in tickerNodes)
                {
                    var tickerAttr = tickerNode.Attribute("Ticker");
                    var shortNameAttr = tickerNode.Attribute("ShortName");
                    var nameAttr = tickerNode.Attribute("Name");
                    var lotSizeAttr = tickerNode.Attribute("LotSize");
                    var digitsAttr = tickerNode.Attribute("Digits");
                    var marketAttr = tickerNode.Attribute("Market");
                    var idAttr = tickerNode.Attribute("ID");
                    if (tickerAttr == null || shortNameAttr == null || marketAttr == null || idAttr == null
                        || nameAttr == null || lotSizeAttr == null || digitsAttr == null)
                        continue;

                    int lotSize = 0;
                    if (!Int32.TryParse(lotSizeAttr.Value, out lotSize)) lotSize = 1;
                    byte digits = 0;
                    if (!Byte.TryParse(digitsAttr.Value, out digits)) digits = 0;
                    
                    TickerData td = new TickerData()
                    {
                        Ticker = tickerAttr.Value,
                        ShortName = shortNameAttr.Value,
                        Name = nameAttr.Value,
                        LotSize = lotSize,
                        Digits = digits,
                        Market = marketAttr.Value,
                        ID = idAttr.Value
                    };
                    tickers.Add(td);
                }

                baseUrl = xd.Root.Element("Url").Attribute("Base").Value;
            }
            catch(Exception ex)
            {
            }
        }

        private async Task<XDocument> LoadConfig()
        {
            var storage = IoC.Resolve<IStorage>();
            byte[] data = await storage.GetResourceFile("finam.xml");
            return XDocument.Load(new MemoryStream(data));
        }

        public IEnumerable<HistoryProviderTimeframe> GetTimeframes()
        {
            return (from tfd in tfds select new HistoryProviderTimeframe(tfd.Tf)).ToList();
        }

        public IEnumerable<HistoryProviderInstrum> GetInstrums()
        {
            return (from t in tickers 
                    select new HistoryProviderInstrum(t.Ticker, t.ShortName, t.Name, t.LotSize, t.Digits)).ToList();
        }

        public async Task<IEnumerable<Bar>> GetDataAsync(string ticker, Timeframes tf, DateTime date1, DateTime date2)
        {
            if (requester == null) return null;
            if (tickers.All(td => td.Ticker != ticker)) return null;
            if (tfds.All(t => t.Tf != tf)) return null;

            string url = ReplParams(baseUrl, ticker, tf, date1, date2);
            byte[] data = await requester.RequestAsync(url);

            ParserSettings ps = new ParserSettings();
            ps.SkipLinesCount = 1;
            if (tf == Timeframes.Tick)
            {
                ps.OpenPriceIndex = ps.HighPriceIndex = ps.LowPriceIndex = ps.ClosePriceIndex = ps.VolumeIndex = -1;
                ps.FieldCount = 6;
            }
            else
            {
                ps.TickPriceIndex = ps.TickVolumeIndex = -1;
                ps.FieldCount = 9;
            }
            Parser parser = new Parser(ps);
            
            return await parser.ParseAsync(data);
        }

        /// <summary>
        /// Substitution of parameter values in a string
        /// </summary>
        /// <param name="isStr"></param>
        /// <param name="ticker"></param>
        /// <param name="tf"></param>
        /// <param name="date1"></param>
        /// <param name="date2"></param>
        /// <returns></returns>
        private string ReplParams(string inStr,
            string ticker, Timeframes tf, DateTime date1, DateTime date2)
        {
            string outStr = inStr;
            TickerData tickerData = tickers.FirstOrDefault(t => t.Ticker == ticker);
            TimeframeData tfData = tfds.FirstOrDefault(t => t.Tf == tf);

            outStr = outStr.Replace("{TICKER}", ticker);
            outStr = outStr.Replace("{ID}", tickerData.ID);
            outStr = outStr.Replace("{Market}", tickerData.Market);
            outStr = outStr.Replace("{TfParam}", tfData.TfParam);
            outStr = outStr.Replace("{TFKey}", tfData.TfKey);
            outStr = outStr.Replace("{DATF}", tfData.Datf);

            outStr = outStr.Replace("{YYF}", date1.Date.Year.ToString().Substring(2, 2));
            outStr = outStr.Replace("{MMF}", date1.Date.Month.ToString("0#"));
            outStr = outStr.Replace("{DDF}", date1.Date.Day.ToString("0#"));
            outStr = outStr.Replace("{YYT}", date2.Date.Year.ToString().Substring(2, 2));
            outStr = outStr.Replace("{MMT}", date2.Date.Month.ToString("0#"));
            outStr = outStr.Replace("{DDT}", date2.Date.Day.ToString("0#"));

            outStr = outStr.Replace("{DF}", date1.Date.Day.ToString());
            outStr = outStr.Replace("{MF}", date1.Date.Month.ToString());
            outStr = outStr.Replace("{MF-1}", (date1.Date.Month - 1).ToString());
            outStr = outStr.Replace("{DF-1}", (date1.Date.Day - 1).ToString());
            outStr = outStr.Replace("{YYYYF}", date1.Date.Year.ToString());

            outStr = outStr.Replace("{DT}", date2.Date.Day.ToString());
            outStr = outStr.Replace("{MT}", date2.Date.Month.ToString());
            outStr = outStr.Replace("{MT-1}", (date2.Date.Month - 1).ToString());
            outStr = outStr.Replace("{DT-1}", (date2.Date.Day - 1).ToString());
            outStr = outStr.Replace("{YYYYT}", date2.Date.Year.ToString());

            return outStr;
        }
    }
}
