using CareFusion.Lib.StorageSystem.Output;
using CareFusion.Lib.StorageSystem.Stock;
using Rowa.Observables;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Windows.Forms;

namespace CareFusion.ITSystemSimulator
{
    /// <summary>
    /// Class which contains all relevant information of an article in the stock.
    /// It also provides mutable access to imutable data.
    /// </summary>
    public class StockArticle
    {
        #region Members

        /// <summary>
        /// The referenced imutable article. 
        /// </summary>
        private IArticle _article;

        /// <summary>
        /// The mutable list of packs which belong to this article.
        /// </summary>
        private List<IPack> _packs = new List<IPack>();

        /// <summary>
        /// The count of packs.
        /// </summary>
        private int packCount;

        #endregion

        #region Properties

        public string Id { get { return _article.Id; } }

        public string Name { get { return _article.Name; } }

        public string DosageForm { get { return _article.DosageForm; } }

        public string PackagingUnit { get { return _article.PackagingUnit; } }

        public uint MaxSubItemQuantity { get { return _article.MaxSubItemQuantity; } }

        public int PackCount { get { return packCount; } }

        public IPack[] Packs { get { return _packs.ToArray(); } }

        public string VirtualId { get { return _article.VirtualId; } }

        #endregion

        /// <summary>
        /// Initializes a new instance of the <see cref="StockArticle"/> class.
        /// </summary>
        /// <param name="article">The underlying imutable article reference.</param>
        public StockArticle(IArticle article)
        {
            _ = article ?? throw new ArgumentException(nameof(article));
            _article = article;
            _packs.AddRange(_article.Packs);
            packCount = (int)_article.PackCount;
            if (packCount == 0)
            {
                packCount = article.Packs.Count();
            }
        }

        /// <summary>
        /// Updates the specified packs of the article.
        /// </summary>
        /// <param name="packs">The packs to update.</param>
        /// <param name="isFullUpdate">if set to <c>true</c> system received fullStock info message, so any packs that are not located on this updated list will be removed from article.</param>
        public void UpdatePacks(IPack[] packs, bool isFullUpdate)
        {
            if (packs == null)
            {
                return;
            }

            if (isFullUpdate)
            {
                var missingArticlePacksFromUpdate = _packs
                    .Where(c => !packs.Any(i => i.Id == c.Id))
                    .ToList();

                foreach (var missingPack in missingArticlePacksFromUpdate)
                {
                    _packs.Remove(missingPack);

                    packCount--;
                }
            }

            foreach (var pack in packs)
            {
                var stockPackIndex = _packs.FindIndex(p => p.Id == pack.Id);

                if (stockPackIndex >= 0)
                {
                    _packs.Insert(stockPackIndex, pack);
                    _packs.RemoveAt(stockPackIndex + 1);
                }
                else
                {
                    _packs.Add(pack);
                    packCount++;
                }
            }
        }

        /// <summary>
        /// Removes the specified packs from the article.
        /// </summary>
        /// <param name="packs">The packs to remove.</param>
        public void RemovePacks(IPack[] packs)
        {
            if ((packs == null) || (packs.Length == 0))
                return;
            packCount--;

            foreach (var pack in packs)
            {
                var stockPackIndex = _packs.FindIndex(p => p.Id == pack.Id);

                if (stockPackIndex >= 0)
                {
                    _packs.RemoveAt(stockPackIndex);
                }
            }
        }
    }

    /// <summary>
    /// Class which contains the data model of the storage system stock.
    /// </summary>
    public class StockModel
    {
        #region Members

        /// <summary>
        /// Holds the list of articles in stock.
        /// </summary>
        private List<StockArticle> _articles = new List<StockArticle>();

        /// <summary>
        /// Holds the prepared article data model.
        /// </summary>
        private DataTable _articleModel = new DataTable();

        /// <summary>
        /// Holds the prepared pack data model.
        /// </summary>
        private DataTable _packModel = new DataTable();

        /// <summary>
        /// Holds the currently active filter which is applied to the data model.
        /// </summary>
        private string _filter = string.Empty;

        /// <summary>
        /// Holds the currently selected article.
        /// </summary>
        private StockArticle _selectedArticle = null;

        /// <summary>
        /// Holds the article in datatable.
        /// </summary>
        private StockArticle _articleInDataTable = null;

        #endregion

        #region Properties

        /// <summary>
        /// Gets the currently active article data model.
        /// </summary>
        public DataTable Articles
        {
            get { return _articleModel; }
        }

        /// <summary>
        /// Gets the currently active pack data model.
        /// </summary>
        public DataTable Packs
        {
            get { return _packModel; }
        }

        /// <summary>
        /// Gets the overview of articles and their pack counts.
        /// </summary>
        public Dictionary<string, List<IPack>> ArticlePacks
        {
            get
            {
                var result = new Dictionary<string, List<IPack>>();
                _articles.ForEach(a => result.Add(a.Id, new List<IPack>(a.Packs)));
                return result;
            }
        }

        public Subject<AutoCompleteStringCollection> AutoCompleteSourceArticleId { get; } =
            new Subject<AutoCompleteStringCollection>();

        #endregion

        #region Methods

        /// <summary>
        /// Initializes a new instance of the <see cref="StockModel"/> class.
        /// </summary>
        public StockModel()
        {
            DataColumn column = _articleModel.Columns.Add();
            column.DataType = typeof(string);
            column.ColumnName = "ID";

            column = _articleModel.Columns.Add();
            column.DataType = typeof(string);
            column.ColumnName = "Name";

            column = _articleModel.Columns.Add();
            column.DataType = typeof(string);
            column.ColumnName = "DosageForm";

            column = _articleModel.Columns.Add();
            column.DataType = typeof(string);
            column.ColumnName = "PackagingUnit";

            column = _articleModel.Columns.Add();
            column.DataType = typeof(uint);
            column.ColumnName = "MaxSubItemQuantity";

            column = _articleModel.Columns.Add();
            column.DataType = typeof(int);
            column.ColumnName = "PackCount";

            column = _articleModel.Columns.Add();
            column.DataType = typeof(string);
            column.ColumnName = "Virtual ID";

            ////////////////////////////////////

            column = _packModel.Columns.Add();
            column.DataType = typeof(long);
            column.ColumnName = "ID";

            column = _packModel.Columns.Add();
            column.DataType = typeof(string);
            column.ColumnName = "Delivery";

            column = _packModel.Columns.Add();
            column.DataType = typeof(string);
            column.ColumnName = "Batch";

            column = _packModel.Columns.Add();
            column.DataType = typeof(string);
            column.ColumnName = "External ID";

            column = _packModel.Columns.Add();
            column.DataType = typeof(DateTime);
            column.ColumnName = "ExpiryDate";

            column = _packModel.Columns.Add();
            column.DataType = typeof(string);
            column.ColumnName = "ExpiryDateSource";

            column = _packModel.Columns.Add();
            column.DataType = typeof(DateTime);
            column.ColumnName = "StockInDate";

            column = _packModel.Columns.Add();
            column.DataType = typeof(bool);
            column.ColumnName = "Fridge";

            column = _packModel.Columns.Add();
            column.DataType = typeof(PackShape);
            column.ColumnName = "Shape";

            column = _packModel.Columns.Add();
            column.DataType = typeof(PackState);
            column.ColumnName = "State";

            column = _packModel.Columns.Add();
            column.DataType = typeof(uint);
            column.ColumnName = "SubItems";

            column = _packModel.Columns.Add();
            column.DataType = typeof(int);
            column.ColumnName = "Depth";

            column = _packModel.Columns.Add();
            column.DataType = typeof(int);
            column.ColumnName = "Width";

            column = _packModel.Columns.Add();
            column.DataType = typeof(int);
            column.ColumnName = "Height";

            column = _packModel.Columns.Add();
            column.DataType = typeof(string);
            column.ColumnName = "StockLocation";

            column = _packModel.Columns.Add();
            column.DataType = typeof(string);
            column.ColumnName = "MachineLocation";

            column = _packModel.Columns.Add();
            column.DataType = typeof(string);
            column.ColumnName = "Guid";
        }


        /// <summary>
        /// Clears all articles from the stock model.
        /// </summary>
        public void Clear()
        {
            _articles.Clear();
            _selectedArticle = null;
            UpdateModel();
        }

        /// <summary>
        /// Replaces the internal stock model with the specified article list.
        /// </summary>
        /// <param name="articleList">The article list to use.</param>
        public void Replace(List<IArticle> articleList)
        {
            _ = articleList ?? throw new ArgumentException(nameof(articleList));

            _articles.Clear();
            _articles.AddRange(articleList.Select(a => new StockArticle(a)));
            _selectedArticle = _articles.FirstOrDefault();
            UpdateModel();
        }

        /// <summary>
        /// Updates the internal stock model with the specified article list.
        /// </summary>
        /// <param name="articleList">The article list to use.</param>
        /// <param name="isFullUpdate">if set to <c>true</c> system received fullStock info message, so any packs that are not located on this updated list will be removed from article.</param>
        public void Update(IArticle[] articleList, bool isFullUpdate)
        {
            _ = articleList ?? throw new ArgumentException(nameof(articleList));

            foreach (var article in articleList)
            {
                var existingStockArticle = _articles.Find(a => a.Id == article.Id);

                if (existingStockArticle == null)
                {
                    _articles.Add(new StockArticle(article));
                }
                else
                {
                    existingStockArticle.UpdatePacks(article.Packs, isFullUpdate);
                }
            }

            UpdateModel();
        }

        /// <summary>
        /// Removes the specified list of articles from the stock model.
        /// </summary>
        /// <param name="articleList">List of articles to remove.</param>
        public void Remove(IArticle[] articleList)
        {
            foreach (var article in articleList)
            {
                var stockArticle = _articles.FirstOrDefault(a => a.Id == article.Id);
                if (stockArticle != null)
                {
                    stockArticle.RemovePacks(article.Packs);
                    if (stockArticle.PackCount == 0)
                        _articles.Remove(stockArticle);
                }
            }

            UpdateModel();
        }

        /// <summary>
        /// Removes the specified packs from the internal stock list.
        /// </summary>
        /// <param name="packs">The packs which were dispensed.</param>
        public void Remove(IDispensedPack[] packs)
        {
            if ((packs == null) || (packs.Length == 0))
                return;

            foreach (var dispensedPack in packs)
            {
                var stockArticle = _articles.FirstOrDefault(a => a.Id == dispensedPack.ArticleId);
                if (stockArticle != null)
                {
                    stockArticle.RemovePacks(new IPack[] { dispensedPack } );
                    if (stockArticle.PackCount == 0)
                        _articles.Remove(stockArticle);
                }
            }

            UpdateModel();
        }

        /// <summary>
        /// Clears the article selection.
        /// </summary>
        public void ClearArticleSelection()
        {
            _selectedArticle = null;
            UpdatePackModel();
        }

        /// <summary>
        /// Selects the article with the specified article code.
        /// </summary>
        /// <param name="articleCode">Code of the article to select.</param>
        public void SelectArticle(string articleCode)
        {
            _selectedArticle = _articles.FirstOrDefault(a => a.Id == articleCode);
            UpdatePackModel();
        }

        public bool HasAnyStock()
            => _articles.Count != 0;

        internal void SetFilter(string newFilter)
        {
            _filter = newFilter;
            UpdateModel();
        }

        /// <summary>
        /// Updates the internal data model.
        /// </summary>
        private void UpdateModel()
        {
            _articleModel.Rows.Clear();

            foreach (var article in _articles)
            {
                if (!ArticleMatchFilter(article))
                {
                    if ((_selectedArticle != null) &&
                        (_selectedArticle.Id == article.Id))
                    {
                        _selectedArticle = null;
                    }

                    continue;
                }

                _selectedArticle = _selectedArticle ?? article;
                DataRow row = _articleModel.NewRow();
                row[0] = article.Id;
                row[1] = article.Name != null ? article.Name : string.Empty;
                row[2] = article.DosageForm != null ? article.DosageForm : string.Empty;
                row[3] = article.PackagingUnit != null ? article.PackagingUnit : string.Empty;
                row[4] = article.MaxSubItemQuantity;
                row[5] = article.PackCount;
                row[6] = article.VirtualId;
                _articleModel.Rows.Add(row);
            }

            UpdatePackModel();
        }

        private bool ArticleMatchFilter(StockArticle article)
            => (article.Id.Contains(_filter)
                    || (!String.IsNullOrEmpty(article.VirtualId) && article.VirtualId.Contains(_filter)));

        /// <summary>
        /// Updates the pack data model only.
        /// </summary>
        private void UpdatePackModel()
        {
            if (_selectedArticle == _articleInDataTable)
            {
                return;
            }
            else
            {
                _articleInDataTable = _selectedArticle;
            }
            
            _packModel.Rows.Clear();

            if (_selectedArticle != null)
            {
                var packList = _selectedArticle.Packs;

                foreach (var pack in packList)
                {
                    var row = this._packModel.NewRow();

                    row["ID"] = pack.Id;
                    row["Delivery"] = pack.DeliveryNumber;
                    row["Batch"] = pack.BatchNumber;
                    row["External ID"] = pack.ExternalId;
                    row["ExpiryDate"] = pack.ExpiryDate;
                    row["ExpiryDateSource"] = pack.ExpiryDateSource;
                    row["StockInDate"] = pack.StockInDate;
                    row["Fridge"] = pack.IsInFridge;
                    row["Shape"] = pack.Shape;
                    row["State"] = pack.State;
                    row["SubItems"] = pack.SubItemQuantity;
                    row["Depth"] = pack.Depth;
                    row["Width"] = pack.Width;
                    row["Height"] = pack.Height;
                    row["StockLocation"] = pack.StockLocationId;
                    row["MachineLocation"] = pack.MachineLocation;
                    row["Guid"] = pack.Guid;

                    this._packModel.Rows.Add(row);
                }
            }

            var autoCompleteSource = new AutoCompleteStringCollection();
            autoCompleteSource.AddRange(ArticlePacks.Keys.ToArray());
            AutoCompleteSourceArticleId.Current = autoCompleteSource;
        }

        #endregion
    }
}
