using System;
using System.IO;
using System.Reflection;
using System.Collections.Generic;
using System.Text;
using System.IO.IsolatedStorage;
using System.Diagnostics;
using System.ServiceModel;
using C1.Silverlight.Data;
using StockPortfolio.localhost;
using System.Collections.Specialized;

namespace StockPortfolio
{
    /// <summary>
    /// Business object that represents a set of stock portfolios.
    /// </summary>
    public class PortfolioSet : DataSet
    {
        private static PortfolioSet _instance = null;
        public static PortfolioSet Portfolio
        {
            get
            {
                if (_instance == null)
                {
                    _instance = new PortfolioSet();
                }
                return _instance;
            }
        }

        //------------------------------------------------------------------------------
        #region ** fields

        DateTime                _lastUpdate;
        int                     _activePortfolio;
        StockInfoServiceClient  _stockInfoService;
        int                     _serviceCount;

        static Dictionary<string, string> _symbols;
        static List<string> _news;

        #endregion

        //------------------------------------------------------------------------------
        #region ** ctor

        private PortfolioSet()
        {
            //Tables.Add(new DataTable("Transactions"));
            //Tables.Add(new DataTable("Portfolios"));
            //Tables.Add(new DataTable("Summary"));
        }

        public void Initialize()
        {
            // create service
            BasicHttpBinding binding = new BasicHttpBinding();
            binding.MaxReceivedMessageSize = 2147483647; // int.MaxValue
            binding.MaxBufferSize = 2147483647; // int.MaxValue
            string uri = Main.GetAbsoluteUri("StockInfoService.svc");
            EndpointAddress address = new EndpointAddress(uri);
            _stockInfoService = new StockInfoServiceClient(binding, address);

            // load portfolio
            Load();

            // create summary table
            try
            {
                UpdateSummary();
            }
            catch (Exception x)
            {
                // shouldn't happen, but just to be safe...
                Debug.WriteLine("Caught exception updating summary: " + x.Message);
            }

            // set active portfolio
            foreach (DataRow pf in Portfolios.Rows)
            {
                if ((bool)pf["Active"])
                {
                    ActivePortfolio = (int)pf["id"];
                    break;
                }
            }

            // update info for portfolio we just loaded
            try
            {
                UpdateTransactionValues();
            }
            catch (Exception x)
            {
                // shouldn't happen, but just to be safe...
                Debug.WriteLine("Caught exception updating transaction values: " + x.Message);
            }

            // update summary when Transactions table changes
            Transactions.Rows.CollectionChanged += new NotifyCollectionChangedEventHandler(Rows_CollectionChanged); 
        }

        #endregion

        //------------------------------------------------------------------------------
        #region ** object model

        /// <summary>
        /// Gets or sets the currently active portfolio.
        /// </summary>
        public int ActivePortfolio
        {
            get { return _activePortfolio; }
            set
            {
                if (_activePortfolio != value && Portfolios != null)
                {
                    // save new value
                    _activePortfolio = value;

                    // update table
                    foreach (DataRow pf in Portfolios.Rows)
                    {
                        pf["Active"] = (int)pf["id"] == value;
                    }

                    // update filter
                    string filter = string.Format("Portfolio = {0}", value);
                    Transactions.DefaultView.RowFilter = filter;
                    Summary.DefaultView.RowFilter = filter;

                    // notify listeners
                    OnActivePortfolioChanged(EventArgs.Empty);
                }
            }
        }
        /// <summary>
        /// Gets a table with the available portfolios.
        /// </summary>
        public DataTable Portfolios
        {
            get { return Tables["Portfolios"]; }
        }
        /// <summary>
        /// Gets a table with all transactions for all portfolios.
        /// </summary>
        public DataTable Transactions
        {
            get { return this.Tables["Transactions"]; }
        }
        /// <summary>
        /// Gets a table with a transaction summary (each symbol appears only once).
        /// </summary>
        public DataTable Summary
        {
            get { return this.Tables["Summary"]; }
        }

        public override void Reset()
        {
            //Portfolios.Rows.Clear();
            //Transactions.Rows.Clear();
            //Summary.Rows.Clear();
        }
        /// <summary>
        /// Loads the current portfolio from isolated storage.
        /// </summary>
        public virtual void Load()
        {
            // load existing portfolio
            string fileName = GetFileName();
            try
            {
                using (IsolatedStorageFile isoStore = IsolatedStorageFile.GetUserStoreForApplication())
                {
                    if (isoStore.GetFileNames(fileName).Length > 0)
                    {
                        using (IsolatedStorageFileStream isoStream = new IsolatedStorageFileStream(fileName, FileMode.Open, isoStore))
                        {
                            ReadXml(isoStream);
                        }
                    }
                }
            }
            catch (Exception x)
            {
                // shouldn't happen, but just to be safe...
                Debug.WriteLine("Caught exception loading pf from isolated storage: " + x.Message);
            }

            // create default portfolio by loading resource
            if (Tables.Count == 0)
            {
                string resName = "StockPortfolio.Resources.Portfolio.xml";
                using (Stream s = Assembly.GetExecutingAssembly().GetManifestResourceStream(resName))
                {
                    // load dataset
                    ReadXml(s);
                }
            }
        }
        /// <summary>
        /// Saves the current portfolio into isolated storage.
        /// </summary>
        public virtual void Save()
        {
            string fileName = GetFileName();
            using (IsolatedStorageFile isoStore = IsolatedStorageFile.GetUserStoreForApplication())
            {
                using (IsolatedStorageFileStream isoStream = new IsolatedStorageFileStream(fileName, FileMode.Create, isoStore))
                {
                    WriteXml(isoStream);
                }
            }
        }
        /// <summary>
        /// Event that fires when the <see cref="ActivePortfolio"/> changes.
        /// </summary>
        public event EventHandler ActivePortfolioChanged;
        /// <summary>
        /// Raises the <see cref="ActivePortfolioChanged"/> event.
        /// </summary>
        /// <param name="e"></param>
        protected virtual void OnActivePortfolioChanged(EventArgs e)
        {
            if (ActivePortfolioChanged != null)
                ActivePortfolioChanged(this, e);
        }
        /// <summary>
        /// Event that fires after stock information is updated from the service.
        /// </summary>
        public event EventHandler CurrentStockInfoUpdated;
        /// <summary>
        /// Raises the <see cref="CurrentStockInfoUpdated"/> event.
        /// </summary>
        /// <param name="e"></param>
        protected virtual void OnCurrentStockInfoUpdated(EventArgs e)
        {
            if (CurrentStockInfoUpdated != null)
                CurrentStockInfoUpdated(this, e);
        }
        /// <summary>
        /// Event that fires after the portfolio summary has been updated.
        /// </summary>
        public event EventHandler SummaryUpdated;
        /// <summary>
        /// Raises the <see cref="SummaryUpdated"/> event.
        /// </summary>
        /// <param name="e"></param>
        protected virtual void OnSummaryUpdated(EventArgs e)
        {
            if (SummaryUpdated != null)
                SummaryUpdated(this, e);
        }
        /// <summary>
        /// Gets the time when the portfolio information was last updated.
        /// </summary>
        public DateTime LastUpdate
        {
            get { return _lastUpdate; }
        }
        /// <summary>
        /// Gets a dictionary with company symbols and names.
        /// </summary>
        public static Dictionary<string, string> Symbols
        {
            get
            {
                // load company symbols/names
                if (_symbols == null)
                {
                    _symbols = new Dictionary<string, string>();
                    string resName = "StockPortfolio.Resources.Symbols.txt";
                    using (StreamReader s = new StreamReader(Assembly.GetExecutingAssembly().GetManifestResourceStream(resName)))
                    {
                        while (!s.EndOfStream)
                        {
                            string[] sn = s.ReadLine().Split('\t');
                            if (sn.Length == 2)
                                _symbols[sn[0]] = sn[1];
                        }
                    }
                }

                // return symbol dictionary
                return _symbols;
            }
        }
        /// <summary>
        /// Gets a static list of news (title|text).
        /// </summary>
        public static List<string> News
        {
            get
            {
                // load economic news
                if (_news == null)
                {
                    _news = new List<string>();
                    string resName = "StockPortfolio.Resources.News.txt";
                    using (StreamReader s = new StreamReader(Assembly.GetExecutingAssembly().GetManifestResourceStream(resName)))
                    {
                        while (!s.EndOfStream)
                        {
                            string news = s.ReadLine().Trim();
                            if (news.Length > 0)
                                _news.Add(news);
                        }
                    }
                }

                // return news array (title|body)
                return _news;
            }
        }
        /// <summary>
        /// Notify listeners that web service has started processing.
        /// </summary>
        public void BeginService()
        {
            _serviceCount++;
            if (_serviceCount == 1)
            {
                OnServiceStarted(EventArgs.Empty);
            }
        }
        /// <summary>
        /// Notify listeners that web service is done processing.
        /// </summary>
        public void EndService()
        {
            if (_serviceCount > 0)
            {
                _serviceCount--;
                if (_serviceCount == 0)
                {
                    OnServiceEnded(EventArgs.Empty);
                }
            }
        }
        /// <summary>
        /// Fires when remote services start executing.
        /// </summary>
        public event EventHandler ServiceStarted;
        /// <summary>
        /// Raises the <see cref="ServiceStarted"/> event.
        /// </summary>
        /// <param name="e"></param>
        protected virtual void OnServiceStarted(EventArgs e)
        {
            if (ServiceStarted != null)
                ServiceStarted(this, e);
        }
        /// <summary>
        /// Fires when remote services finish executing.
        /// </summary>
        public event EventHandler ServiceEnded;
        /// <summary>
        /// Raises the <see cref="ServiceEnded"/> event.
        /// </summary>
        /// <param name="e"></param>
        protected virtual void OnServiceEnded(EventArgs e)
        {
            if (ServiceEnded != null)
                ServiceEnded(this, e);
        }

        #endregion

        //------------------------------------------------------------------------------
        #region ** private

        // file name used to store the portfolio
        private static string GetFileName()
        {
            return "C1StockPorfolioData.xml";
        }

        // update summary when Transactions table changes
        void Rows_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            if (e.Action == NotifyCollectionChangedAction.Add)
            {
                UpdateTransactionValues();
            }
            UpdateSummary();
        }

        // update summary table
        private void UpdateSummary()
        {
            // get summary table
            DataTable dt = Tables["Summary"];

            // doesn't exist, create it now
            if (dt == null)
            {
                dt = Tables.Add("Summary");
                foreach (DataColumn col in Transactions.Columns)
                {
                    dt.Columns.Add(col.ColumnName, col.DataType);
                }
                dt.Columns["Gain%"].Expression = Transactions.Columns["Gain%"].Expression;

                DataColumn pk = dt.Columns["ID"];
                dt.PrimaryKey = pk;
                pk.AutoIncrement = true;
            }

            // fill table with summary information
            dt.BeginLoadData();
            dt.Rows.Clear();
            foreach (DataRow pf in Portfolios.Rows)
            {
                UpdateSummary((int)pf["id"]);
            }
            dt.EndLoadData();

            // fire event
            OnSummaryUpdated(EventArgs.Empty);
        }

        // update summary table for a given portfolio
        private void UpdateSummary(int portfolio)
        {
            // calculate totals for this portfolio
            double pfCost = 0;
            double pfValue = 0;
            double pfGain = 0;

            // build a list of unique symbols for this portfolio
            List<string> symbols = new List<string>();
            foreach (DataRow tr in Transactions.Rows)
            {
                if (tr.RowState != DataRowState.Deleted &&
                    tr.RowState != DataRowState.Detached && 
                    (int)tr["Portfolio"] == portfolio)
                {
                    // add symbol to list
                    string symbol = (string)tr["Symbol"];
                    if (!symbols.Contains(symbol))
                    {
                        symbols.Add(symbol);
                    }

                    // accumulate totals
                    if (tr["Gain"] is double)
                    {
                        pfCost  += (double)tr["Cost Basis"];
                        pfValue += (double)tr["Market Value"];
                        pfGain += (double)tr["Gain"];
                    }
                }
            }

            // store portfolio info
            DataRow pf = Portfolios.Rows.Find(portfolio);
            pf["Cost Basis"] = pfCost;
            pf["Market Value"] = pfValue;
            pf["Gain"] = pfGain;

            // summarize transactions for each symbol
            foreach (string symbol in symbols)
            {
                UpdateSummary(portfolio, symbol);
            }
        }
#if true
        // update summary using the DataTable (no DataView)
        private void UpdateSummary(int portfolio, string symbol)
        {
            // create new row
            int count = 0;
            DataRow dr = Summary.NewRow();
            dr["Shares"] = 0;
            dr["Cost Basis"] = (double)0;
            dr["Market Value"] = (double)0;
            dr["Gain"] = (double)0;
            dr["Date"] = new DateTime();
            dr["Price"] = (double)0;
            dr["Commission"] = (double)0;
            dr["Notes"] = (double)0;

            // scan transactions
            foreach (DataRow tr in Transactions.Rows)
            {
                // select portfolio and symbol
                if (tr.RowState != DataRowState.Deleted &&
                    tr.RowState != DataRowState.Detached &&
                    (int)tr["Portfolio"] == portfolio && (string)tr["Symbol"] == symbol)
                {
                    // count how many rows we're accumulating
                    count++;

                    // first row? copy constant values
                    if (count == 1)
                    {
                        foreach (string field in "Portfolio,Symbol,Last Price,Open,High,Low".Split(','))
                        {
                            dr[field] = tr[field];
                        }
                    }

                    // accumulate others
                    if (tr["Shares"] is int)
                        dr["Shares"] = (int)dr["Shares"] + (int)tr["Shares"];
                    foreach (string field in "Cost Basis,Market Value,Gain".Split(','))
                    {
                        if (tr[field] is double)
                        {
                            dr[field] = (double)dr[field] + (double)tr[field];
                        }
                    }
                }
            }

            // add this new row to the table
            if (count > 0)
            {
                Summary.Rows.Add(dr);
            }
        }
#else 
        // update summary using a DataView (a little more elegant, a little slower)
        private void UpdateSummary(int portfolio, string symbol)
        {
            DataView dv = new DataView(Transactions);
            dv.RowFilter = string.Format("Portfolio={0} AND symbol='{1}'", portfolio, symbol);
            if (dv.Count > 0)
            {
                // create new row
                DataRow dr = Summary.NewRow();

                // initialize new row
                dr["Shares"] = 0;
                dr["Cost Basis"] = 0;
                dr["Market Value"] = 0;
                dr["Gain"] = 0;
                DataRow tr = dv[0].Row;
                foreach (string field in "Portfolio,Symbol,Last Price,Open,High,Low".Split(','))
                {
                    dr[field] = tr[field];
                }

                // summarize all transactions
                foreach (DataRowView drv in dv)
                {
                    // accumulate others
                    if (drv["Shares"] is int)
                        dr["Shares"] = dr.GetInt("Shares"] + (int)drv["Shares"];
                    foreach (string field in "Cost Basis,Market Value,Gain".Split(','))
                    {
                        if (drv[field] is double)
                        {
                            dr[field] = (double)dr[field] + (double)drv[field];
                        }
                    }
                }

                // add this new row to the table
                Summary.Rows.Add(dr);
            }
        }
#endif
        // use web service to update transaction values
        private void UpdateTransactionValues()
        {
            // build list of unique symbols
            List<string> symbols = new List<string>();
            foreach (DataRow dr in Transactions.Rows)
            {
                string symbol = (string)dr["Symbol"];
                if (!symbols.Contains(symbol))
                    symbols.Add(symbol);
            }

            // request symbol info from service
            if (symbols.Count > 0)
            {
                BeginService();
                _stockInfoService.GetCurrentStockInfoArrayCompleted += new EventHandler<GetCurrentStockInfoArrayCompletedEventArgs>(_stockInfoService_GetCurrentStockInfoArrayCompleted);
                _stockInfoService.GetCurrentStockInfoArrayAsync(symbols.ToArray());
            }
        }

        // handle values returned asynchronously by service
        void _stockInfoService_GetCurrentStockInfoArrayCompleted(object sender, GetCurrentStockInfoArrayCompletedEventArgs e)
        {
            if (e.Error == null)
            {
                // finish asynch call
                _lastUpdate = DateTime.Now;
                CurrentStockInfo[] result = e.Result;
                EndService();

                Transactions.Rows.BeginUpdate();
                foreach (CurrentStockInfo ci in result)
                {
                    if (ci._valid)
                    {
                        DataRow[] rows = Transactions.Select(string.Format("Symbol = '{0}'", ci._symbol));
                        foreach (DataRow dr in rows)
                        {
                            dr["Last Price"] = ci._close;
                            dr["High"] = ci._high;
                            dr["Low"] = ci._low;
                            dr["Open"] = ci._open;
                            dr["Date"] = ci._date;
                            dr["Price"] = ci._pe;
                            dr["Symbol"] = ci._symbol;
                        }
                    }
                    else
                    {
                        System.Diagnostics.Debug.WriteLine("  " + ci._symbol + " **** failed!!!");
                    }
                }
                Transactions.Rows.EndUpdate();

                // update portfolio values
                UpdateSummary();

                // notify listeners
                OnCurrentStockInfoUpdated(EventArgs.Empty);
            }
        }
        #endregion
    }
}
