using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Data;
using System.Linq;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Xml.Linq;
using System.ServiceModel;
using StockPortfolio_Web.com.flash_db.www;

namespace StockPortfolio_Web
{
    /// <summary>
    /// Summary description for StockInfoService
    /// </summary>
    [ServiceContract(Namespace = "http://stockinfoservice.componentone.com/")]
    [WebService(Namespace = "http://stockinfoservice.componentone.com/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    [ToolboxItem(false)]
    [System.Web.Script.Services.ScriptService]
    public class StockInfoService : System.Web.Services.WebService
    {
        #region ** fields

        // cached stock info
        private static TimedCache<CurrentStockInfo> _cacheCurrentStockInfo = new TimedCache<CurrentStockInfo>(new TimeSpan(2, 0, 0));
        private static TimedCache<HistoricalStockInfo> _cacheHistoricalStockInfo = new TimedCache<HistoricalStockInfo>();

        private Source _source = Source.Static;

        #endregion

        // ** current information

        /// <summary>
        /// Gets a <see cref="CurrentStockInfo"/> object with current stock information for a single company.
        /// </summary>
        /// <param name="symbol">Company stock symbol.</param>
        /// <returns></returns>
        [WebMethod]
        [OperationContract]
        public CurrentStockInfo GetCurrentStockInfo(string symbol)
        {
            // get from cache if possible
            CurrentStockInfo ci;
            if (_cacheCurrentStockInfo.TryGetValue(symbol, out ci))
            {
                return ci;
            }

            try
            {
                switch (_source)
                {
                    case Source.FlashDB:
                        // get from real service (StockInfoService.com.flash_db.www)
                        // http://www.flash-db.com/services/ws/companyInfo.wsdl
                        CompanyInfoService flashService = new CompanyInfoService();
                        CompanyInfoResult result = flashService.doCompanyInfo("anything", "anything", symbol.Trim());

                        // return our own company information object
                        ci = new CurrentStockInfo(symbol, result);
                        break;
                    case Source.GoogleFinance:
                    case Source.Static:
                    default:
                        ci = CurrentStockInfo.GetStockInfo(_source, symbol);
                        break;
                }
                _cacheCurrentStockInfo[symbol] = ci;
                return ci;
            }
            catch
            {
                return CurrentStockInfo.GetStockInfo(Source.Static, symbol);
            }
        }

        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);
        }
        /// <summary>
        /// Gets an array of <see cref="CurrentStockInfo"/> objects with current stock information for an array of companies.
        /// </summary>
        /// <param name="symbols">Array of company stock symbols.</param>
        /// <returns></returns>
        [WebMethod]
        [OperationContract]
        public CurrentStockInfo[] GetCurrentStockInfoArray(string[] symbols)
        {
            List<CurrentStockInfo> list = new List<CurrentStockInfo>();
            foreach (string symbol in symbols)
            {
                list.Add(GetCurrentStockInfo(symbol));
            }
            return list.ToArray();
        }

        // ** historical information

        Random _random = new Random();

        /// <summary>
        /// Gets an <see cref="HistoricalStockInfo"/> object with historical stock information for a single a company.
        /// </summary>
        /// <param name="symbol">Company stock symbol.</param>
        /// <returns></returns>
        [WebMethod]
        [OperationContract]
        public HistoricalStockInfo GetHistoricalStockInfo(string symbol)
        {
            // get from cache if possible
            HistoricalStockInfo hsi;
            if (_cacheHistoricalStockInfo.TryGetValue(symbol, out hsi))
            {
                return hsi;
            }

#if false
            // load actual historical info from database

            // open db connection
            string connStr = string.Format("Provider=Microsoft.Jet.OLEDB.4.0;Data Source={0}", this.Server.MapPath("App_Data\\stockData.mdb"));
            OleDbConnection conn = new OleDbConnection(connStr);
            conn.Open();

            // open db reader
            string sel = string.Format("SELECT [Date], [Close] From [Quotes] WHERE [Company] = '{0}'", symbol);
            OleDbCommand cmd = new OleDbCommand(sel, conn);
            OleDbDataReader dr = cmd.ExecuteReader(CommandBehavior.CloseConnection);

            // load data from db
            DataTable dt = new DataTable();
            dt.Columns.Add("Date", typeof(DateTime));
            dt.Columns.Add("Close", typeof(float));
            while (dr.Read())
            {
                DataRow row = dt.NewRow();
                row["Date"] = dr.GetDateTime(0);
                row["Close"] = dr.GetDecimal(1);
                dt.Rows.Add(row);
            }
#else
            // generate random data to keep the sample small
            // (don't want to download the 80meg stock data file)
            DataTable dt = new DataTable();
            dt.Columns.Add("Date", typeof(DateTime));
            dt.Columns.Add("Close", typeof(float));
            int count = 2500;
            float value = 10;
            DateTime d = DateTime.Today.AddDays(-count);
            for (int i = 0; i < count; i++)
            {
                dt.Rows.Add(d, value);
                value *= (float)(1 + (_random.NextDouble() - 0.49) / 100.0);
                d = d.AddDays(1);
            }
#endif
            // convert data into return value, cache it, and return the value
            hsi = new HistoricalStockInfo(symbol, dt);
            _cacheHistoricalStockInfo[symbol] = hsi;
            return hsi;
        }
        /// <summary>
        /// Gets an array of <see cref="HistoricalStockInfo"/> objects with historical stock information for an array of companies.
        /// </summary>
        /// <param name="symbols">Array of company stock symbols.</param>
        /// <returns></returns>
        [WebMethod]
        [OperationContract]
        public HistoricalStockInfo[] GetHistoricalStockInfoArray(string[] symbols)
        {
            List<HistoricalStockInfo> list = new List<HistoricalStockInfo>();
            foreach (string symbol in symbols)
            {
                list.Add(GetHistoricalStockInfo(symbol));
            }
            return list.ToArray();
        }
    }

    public enum Source
    {
        FlashDB,
        GoogleFinance,
        Static
    }

    /// <summary>
    /// Class that contains (nearly) current stock information.
    /// </summary>
    [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "2.0.50727.1378")]
    [System.SerializableAttribute()]
    //[System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.ComponentModel.DesignerCategoryAttribute("code")]
    [System.Xml.Serialization.SoapTypeAttribute(Namespace = "urn:CurrentStockInfo")]
    public partial class CurrentStockInfo
    {
        // ** fields
        float _open, _close, _high, _low, _volume, _pe, _change, _changePercent;
        DateTime _date;
        string _symbol, _name;
        bool _valid;

        // ** ctors
        public CurrentStockInfo() // required for serialization
        {
        }
        internal CurrentStockInfo(string symbol, CompanyInfoResult result)
        {
            _symbol = symbol;
            try
            {
                _name = result.company;
                _date = DateTime.Parse(string.Format("{0} {1}", result.tradedDate, result.tradedTime), CultureInfo.InvariantCulture);
                _close = ParseFloat(result.lastPrice);
                _open = ParseFloat(result.open);
                _high = ParseFloat(result.dayHigh);
                _low = ParseFloat(result.dayLow);
                _pe = ParseFloat(result.PE);
                _change = ParseFloat(result.change);
                _changePercent = ParseFloat(result.changePercent.Replace('%', ' '));
                _volume = ParseFloat(result.volume);
                _valid = true;
            }
            catch
            {
                _name = null;
                _valid = false;
            }
        }
        private float ParseFloat(string value)
        {
            return value.IndexOf("N/A", StringComparison.InvariantCultureIgnoreCase) > -1
                ? 0
                : float.Parse(value, NumberStyles.Any, CultureInfo.InvariantCulture);
        }

        // ** object model (must be read/write so its serializable)
        public float Open { get { return _open; } set { _open = value; } }
        public float Close { get { return _close; } set { _close = value; } }
        public float High { get { return _high; } set { _high = value; } }
        public float Low { get { return _low; } set { _low = value; } }
        public float Volume { get { return _volume; } set { _volume = value; } }
        public float PE { get { return _pe; } set { _pe = value; } }
        public bool Valid { get { return _valid; } set { _valid = value; } }
        public string Symbol { get { return _symbol; } set { _symbol = value; } }
        public string Name { get { return _name; } set { _name = value; } }
    }
    /// <summary>
    /// Class that contains historical stock information.
    /// </summary>
    [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "2.0.50727.1378")]
    [System.SerializableAttribute()]
    [System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.ComponentModel.DesignerCategoryAttribute("code")]
    [System.Xml.Serialization.SoapTypeAttribute(Namespace = "urn:HistoricalStockInfo")]
    public class HistoricalStockInfo
    {
        // ** fields
        string _symbol;
        DateTime _firstDate, _lastDate;
        float[] _values;
        bool _valid;

        // ** ctors
        public HistoricalStockInfo() // required for serialization
        {
        }
        internal HistoricalStockInfo(string symbol, DataTable dt)
        {
            // save the symbol
            _symbol = symbol;

            // sort values by date
            DataView dv = new DataView(dt);
            dv.Sort = "Date";
            if (dv.Count > 0)
            {
                // get column indices
                int valueIndex = dt.Columns["Close"].Ordinal;
                int dateIndex = dt.Columns["Date"].Ordinal;

                // get date range
                _firstDate = ((DateTime)dv[0][dateIndex]).Date;
                _lastDate = ((DateTime)dv[dv.Count - 1][dateIndex]).Date;

                // build list of values, one per day (filling up any blanks)
                List<float> values = new List<float>();
                DateTime date = _firstDate;
                float value = (float)dv[0][valueIndex];
                foreach (DataRowView drv in dv)
                {
                    // fill up empty dates (holidays/weekends)
                    DateTime thisDate = (DateTime)drv[dateIndex];
                    while (date < thisDate)
                    {
                        date = date.AddDays(1);
                        values.Add(value);
                    }

                    // add value to the list
                    value = (float)drv[valueIndex];
                    values.Add(value);

                    // move on to next day
                    date = date.AddDays(1);
                }

                // convert values to array
                _values = values.ToArray();
                _valid = true;
            }
        }

        // ** object model (must be read/write so its serializable)
        public string Symbol { get { return _symbol; } set { _symbol = value; } }
        public DateTime FirstDate { get { return _firstDate; } set { _firstDate = value; } }
        public DateTime LastDate { get { return _lastDate; } set { _lastDate = value; } }
        public float[] Values { get { return _values; } set { _values = value; } }
        public bool Valid { get { return _valid; } set { _valid = value; } }
    }
}
