using System;
using System.Collections.Generic;
using System.Collections;
using System.Text;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
using pms_modal;
using CommonUtilities;
using System.Linq;

namespace pmsDataGridView
{
    public class Product : Item
    {
        public enum SealType { OneSideMetal, BothSideMetal, OneSidePlastic, BothSidePlastic, BothSideMetalandPlastic }
        public enum BrandType { Normal, NMB, NSK, KOYO, XU, WBKS, MIX }
        public enum GridColumnType { ID, BEARING, CODE, BRAND, QUANTITY, SEAL, COST, PRICE, ORDER, INDEX }
        #region Constructor and Destructor
        
        public Product() 
        {
            //hard code the qty breaks
            //DefualtPriceListQty = new int[] { 1, 4, 10, 20, 40, 100, 300 };
            PriceListQty = new List<int>();
            PriceListPrice = new List<double>();
            DefualtPriceListQty = new List<int>();
            if (InitConfig.Business == InitConfig.BusinessType.BEARING)
            {
                DefualtPriceListQty.Clear();
                DefualtPriceListQty.Add(1);
                DefualtPriceListQty.Add(4);
                DefualtPriceListQty.Add(10);
                DefualtPriceListQty.Add(20);
                DefualtPriceListQty.Add(40);
                DefualtPriceListQty.Add(100);
                DefualtPriceListQty.Add(300);
            }
        }
        public Product(double inner, double outer, double thickness, SealType seal, bool flanged, BrandType brand,
        int Quantity, double PrimeCost, double Price, string Comment) 
            : this()
        {
            //define the name based on the bearing dimension
            string name;                       
            name = inner.ToString() + "x" + outer.ToString() + "x" + thickness.ToString();
            if (flanged == true) name = "F" + name;
            this.pInner = inner;
            this.pOuter = outer;
            this.pThickness = thickness;
            this.pSeal = seal;
            this.pFlangled = flanged;
            this.pBrand = brand;
            this.name = name;
            this.quantity = Quantity;
            this.prime_cost = PrimeCost;
            this.pPrice = Price;
            this.comment = Comment;

            make_ID();
                     
        }

        public void make_ID()
        {
            //Work out the ID
            string flangedbit, brandbit, sealbit;
            //int t = thickness; //truncate the decimal of the thickness
            if (pFlangled == true) flangedbit = "1"; else flangedbit = "0";
            brandbit = pBrand.GetHashCode().ToString();
            sealbit = pSeal.GetHashCode().ToString();
            this.ID = pInner.ToString("00") + pOuter.ToString("00") + pThickness.ToString("00") + flangedbit + brandbit + sealbit;
        }
        public Product(Product b)
        {
            this.copy(b);
        }
        ~Product() { }

        #endregion


        
        #region Properties

        [XmlElement("InnerDiameter_mm")]
        public double Inner 
        {
            get {return pInner;}
            set { pInner = value; }
        }

        [XmlElement("OuterDiameter_mm")]
        public double Outer
        {
            get { return pOuter; }
            set { pOuter = value; }
        }

        [XmlElement("Thickness_mm")]
        public double Thickness 
        {
            get { return pThickness; }
            set { pThickness = value; }
        }

        [XmlElement("Seal_Type")]
        public SealType Seal
        {
            get { return pSeal; }
            set { pSeal = value; }
        }

        [XmlElement("Brand")]
        public BrandType Brand
        {
            get { return pBrand; }
            set { pBrand = value; }
        }

        [XmlElement("Flanged")]
        public bool Flanged
        {
            get { return pFlangled; }
            set { pFlangled = value; }
        }

        [XmlElement("Price")]
        public double Price
        {
            get { return pPrice; }
            set { pPrice = value; }
        }

        public int Order
        {
            get { return pOrder; }
            set { pOrder = value; }
        }

        #endregion

        //Methods
        public void ExecuteOrder()
        {
            quantity -= Order;
        }

        public void fill_to_row_i(System.Windows.Forms.DataGridView ProductCatalogGrid, int i)
        {
         //This function fills bearing on a row, either replace an old bearing or to add a new bearing. 
         //need to add a new row before calling this function if adding a new bearing
            //if the existing rows are less than the size of the list, then add more rows
            if (i == ProductCatalogGrid.Rows.Count)
            {
                ProductCatalogGrid.Rows.Add();
            }
            //productlist.Bearings[last_sale_index].Index = last_sale_index; //reset all the indices
            ProductCatalogGrid.Rows[i].Cells[(int)GridColumnType.ID].Value = this.ID;
            ProductCatalogGrid.Rows[i].Cells[(int)GridColumnType.BEARING].Value = this.name;
            ProductCatalogGrid.Rows[i].Cells[(int)GridColumnType.CODE].Value = this.Code;
            ProductCatalogGrid.Rows[i].Cells[(int)GridColumnType.BRAND].Value = this.Brand;
            ProductCatalogGrid.Rows[i].Cells[(int)GridColumnType.QUANTITY].Value = this.quantity.ToString("000");
            ProductCatalogGrid.Rows[i].Cells[(int)GridColumnType.SEAL].Value = this.Seal.ToString();
            ProductCatalogGrid.Rows[i].Cells[(int)GridColumnType.COST].Value = this.prime_cost.ToString("0.00");
            ProductCatalogGrid.Rows[i].Cells[(int)GridColumnType.PRICE].Value = this.Price.ToString("0.00");
            if (this.Order == 0)
            {
                ProductCatalogGrid.Rows[i].Cells[(int)GridColumnType.ORDER].Value = null;
            }
            else
            {
                ProductCatalogGrid.Rows[i].Cells[(int)GridColumnType.ORDER].Value = this.Order;
            }
            ProductCatalogGrid.Rows[i].Cells[(int)GridColumnType.INDEX].Value = this.Index.ToString();
        }
        public void fill_to_last_row(System.Windows.Forms.DataGridView ProductCatalogGrid)
        {
            int i = ProductCatalogGrid.Rows.Count - 1;
            fill_to_row_i(ProductCatalogGrid, i);
        }

        public void update_order_from_grid(System.Windows.Forms.DataGridView ProductCatalogGrid, int selected_row_index)
        {
            object order_amount = ProductCatalogGrid.Rows[selected_row_index].Cells[(int)GridColumnType.ORDER].Value;
            if (order_amount != null && Convert.ToInt32(order_amount) != 0)
            {
                //change the order field of the selected product
                this.Order = Convert.ToInt32(order_amount);
            }
            else
            {
                this.Order = 0;
            }
        }
        public void copy(Product sample)
        {
            this.ID = sample.ID;
            this.name = sample.name;
            this.Inner = sample.Inner;
            this.Outer = sample.Outer;
            this.Thickness = sample.Thickness;
            this.Order = sample.Order;
            this.Code = sample.Code;
            this.Brand = sample.Brand;
            this.quantity = sample.quantity;
            this.Seal = sample.Seal;
            this.comment = sample.comment;
            this.Index = sample.Index;
            this.Flanged = sample.Flanged;
            this.Price = sample.Price;
            this.prime_cost = sample.prime_cost;
            this.Desription = sample.Desription;
            this.MasterListIndex = sample.MasterListIndex;
            this.cluster = sample.cluster;
            this.ClusterTableIndex = sample.ClusterTableIndex;
            this.toPackList = sample.toPackList;
            this.ourLink = sample.ourLink;
            this.supplerLink = sample.supplerLink;
            //sample.DefualtPriceListQty.CopyTo(this.DefualtPriceListQty.a);            
            //don't put this to xml
            //this.DefualtPriceListQty = sample.DefualtPriceListQty.ToList();
        }
        //Variables
        private double pInner = 0;
        private double pOuter = 0;
        private double pThickness = 0;
        private bool pFlangled;
        private double pPrice=0.0;
        private SealType pSeal;
        private BrandType pBrand;
        public string Code;
        private int pOrder = 0; // positive stands for sell, -ve for buy in
        public string ID = "";
        public int Index = -1;
        public int MasterListIndex = -1;
        public int ClusterTableIndex = -1; //newly added property
        public bool cluster = false;
        public string toPackList = null;
        //index is the index of the item in its list, there can be many lists
        //master index is the index of the item in the main list i.e. productlist
        public string Desription; //product description
        public string ourLink = null;
        public string supplerLink = null;
        //an int array to store te qty of a price list
        //public int[] PriceListQty;
        //public double[] PriceListPrice;
        public List<int> PriceListQty;
        public List<double> PriceListPrice;
        [XmlIgnoreAttribute]
        public List<int> DefualtPriceListQty;
    }

    public class ProductList
    {
        #region Constructor and Distructor
        public ProductList()
        {
            productlist = new ArrayList();
            clustertables = new ClusterTableList();
            clustertables.load("cluster_tables.xml");
        }
        ~ProductList() { }
        #endregion

        #region Methods

        public void add(Product b)
        {
            productlist.Add(b); //add to list
            //can't remember why the index has to be initialized when add bearing to the list
            //b.Index = Count - 1; //assign an index
            //but now change to make the new bearing's index the last index in the list, hope doesn't break anything
            b.Index = productlist.Count-1;            
            TotalNumberOfBearings += b.quantity; //update the total quantity
        }

        public void update_total_bearing_quantity()
        {
            TotalNumberOfBearings = 0;
            foreach (Product b in productlist)
            {
                TotalNumberOfBearings += b.quantity;
            }
        }
        public Product get_bearing(int location) //0 based index  
        {
            if (location < productlist.Count)
            {
                return (Product)productlist[location];
            }
            else return null;
        }

        public int Count
        {
            get { return productlist.Count; }
        }

        public void remove(int location) //0 based index  
        {
            if (location < productlist.Count)
            {
                productlist.RemoveAt(location);
                
                for (int i = location; i < Count; i++) //decrement indices of bearings after 'location' 
                {
                    this[i].Index--;
                }
                 
            }
            else return;
        }

        public void copy(ProductList sample)
        {
            this.productlist = sample.productlist;
        }

        public void update_table_from_txt_file()
        {
            clustertables.update_tables_from_txt_file(this);
        }
        public void update_cluster_product_index()
        {
            for (int i = 0; i < productlist.Count; i++)
            {//search for all clustered products, ignore non-clusters
                if (this[i].cluster == true)
                {
                    //search for the matching name in cluster table
                    for (int j = 0; j < clustertables.Count; j++)
                    {
                        if (this[i].name == clustertables[j].ClusterName)
                        {
                            this[i].ClusterTableIndex = j;
                            break;
                        }
                    }
                }
            }
        }
        public void save_ClusterTable()
        {
            clustertables.save_as("cluster_tables.xml");
        }
        public void update_product_quantity()
        {//mainly update the quantity of clustered products
            for (int i = 0; i < productlist.Count; i++)
            {//search for all clustered products, ignore non-clusters
             //the subitem of the min qty is the qty of the clustered product
                //here assumes cluster can not contain cluster, the implementation is too difficult, not worth the effort
                if (this[i].cluster == true)
                {
                    ArrayList subitem_qty = new ArrayList();
                    subitem_qty.Clear(); //just in case;
                    int ind = this[i].ClusterTableIndex;
                    for (int j = 0; j < clustertables[ind].Count; j++)
                    {//for each prodcut ref in ONE table
                        //check whether index and name are matched  
                        if (authenticate(i, j) == true)
                        {
                            Product subitem = this[clustertables[ind][j].index];
                            subitem_qty.Add(subitem.quantity);
                        }
                    }
                    subitem_qty.Sort();//sort ascend, smallest value first   
                    //grab the first value to represent the qty of the clustered product
                    this[i].quantity = (int)subitem_qty[0];
                }
            }
        }
        public bool authenticate(int bearinglist_index, int ProductRef_index)
        {
            int proRef_index = clustertables[this[bearinglist_index].ClusterTableIndex][ProductRef_index].index;
            string proRef_ID = clustertables[this[bearinglist_index].ClusterTableIndex][ProductRef_index].ID;
            
            if (this[proRef_index].ID == proRef_ID)
            {
                return true;
            }
            return false;
        }

        public ProductList get_subitems(Product clustered_item)
        {
            ProductList subitems = new ProductList();
            int master_index = clustered_item.MasterListIndex;
            if (this[master_index].cluster == false)
            {
                return null;//may i just add the same item to the list??
            }
            else
            {
                int i = master_index;
                int c_ind = this[master_index].ClusterTableIndex;
                for (int j = 0; j < clustertables[c_ind].Count; j++)
                {//for each prodcut ref in ONE table
                    //check whether index and name are matched  
                    if (authenticate(i, j) == true)
                    {
                        Product subitem = new Product();
                        subitem.copy(this[clustertables[c_ind][j].index]);
                        //modify the quantity according to the cluster table and the quantity of the clustered item
                        subitem.quantity = clustertables[c_ind][j].quantity * clustered_item.quantity;
                        subitems.add(subitem);
                    }
                }

            }
            return subitems;
        }
        public ProductList get_subitems(Product clustered_item, bool hold_qty)
        {
            ProductList subitems = new ProductList();
            int master_index = clustered_item.MasterListIndex;
            if (this[master_index].cluster == false)
            {
                return null;//may i just add the same item to the list??
            }
            else
            {
                int i = master_index;
                int c_ind = this[master_index].ClusterTableIndex;
                for (int j = 0; j < clustertables[c_ind].Count; j++)
                {//for each prodcut ref in ONE table
                    //check whether index and name are matched  
                    if (authenticate(i, j) == true)
                    {
                        Product subitem = new Product();
                        subitem.copy(this[clustertables[c_ind][j].index]);
                        if (hold_qty == false)
                        {
                            //modify the quantity according to the cluster table and the quantity of the clustered item
                            subitem.quantity = clustertables[c_ind][j].quantity * clustered_item.quantity;
                        }
                        subitems.add(subitem);
                    }
                }

            }
            return subitems;
        }

        public void returnOrderedItems(ProductList orderedItems)
        {//pretty much the reverse process of subtract_qty
            ProductList toreturnlist = new ProductList();
            toreturnlist = ToPackList(orderedItems);
            for (int i = 0; i < toreturnlist.Count; i++)
            {
                this[toreturnlist[i].MasterListIndex].quantity += toreturnlist[i].quantity;
            }

            for (int i = 0; i < orderedItems.Count; i++)
            {
                if (orderedItems[i].cluster)
                {
                    //decrease the quantity of the clustered item 
                    this[orderedItems[i].MasterListIndex].quantity += orderedItems[i].quantity;
                }
            }

        }
        /*
        public void subtract_qty(int product_index, int qty)
        {
            //doesn't matter if the product is clustered or not, it's qty has to decrement
            this[product_index].quantity -= qty;
            if (this[product_index].cluster) //it's a clustered product
            {
                int ind = this[product_index].ClusterTableIndex;
                for (int j = 0; j < clustertables[ind].Count; j++)
                {//for each prodcut ref in ONE table
                    //check whether index and name are matched  
                    if (authenticate(product_index, j) == true)
                    {
                        Bearing subitem = this[clustertables[ind][j].index];
                        int subitem_qty = clustertables[this[product_index].ClusterTableIndex][j].quantity * qty;
                        subitem.quantity -= subitem_qty;
                    }
                }
            }
        }
         * */
        public void subtract_qty(ProductList orderbearings)
        {
            ProductList topacklist = new ProductList();
            topacklist = ToPackList(orderbearings);
            for (int i = 0; i < topacklist.Count; i++)
            {
                this[topacklist[i].MasterListIndex].quantity -= topacklist[i].quantity;
            }

            for (int i = 0; i < orderbearings.Count; i++)
            {
                if (orderbearings[i].cluster)
                {
                    //decrease the quantity of the clustered item 
                    this[orderbearings[i].MasterListIndex].quantity -= orderbearings[i].quantity;
                }
            }
        }

        public ProductList ToPackList(ProductList OrderItems)
        {                 
            ProductList itemsToBePacked = new ProductList(); //this list shouldn't contain any cluster products.
            int cd_count = 0;
            for (int i = 0; i < OrderItems.Count; i++)
            {
                if (OrderItems[i].cluster == true)
                {
                    ProductList subitems = get_subitems(OrderItems[i]);
                    for (int j = 0; j < subitems.Count; j++)
                    {
                        if (subitems[j].ID == "600103001") //if it's the CD 
                        {
                            if (cd_count == 0)
                            {
                                itemsToBePacked.AddNoRepeat(subitems[j]);
                                cd_count++;
                            }
                        }
                        else
                        {
                            itemsToBePacked.AddNoRepeat(subitems[j]);
                        }
                    }
                }
                else //not clustered
                {
                    itemsToBePacked.AddNoRepeat(OrderItems[i]);
                }
            }
            return itemsToBePacked;
        }

        public string ToPackListText(ProductList OrderItems)
        {
            StringBuilder sb = new StringBuilder();
            ProductList bl = new ProductList();
            bl.copy(this.ToPackList(OrderItems));
            for (int i = 0; i < bl.Count; i++)
            {
                //sb.Append(bl[i].name + "\t\t" + bl[i].quantity.ToString() + "\t\t(" + bl[i].comment + ")" + Environment.NewLine);
                sb.Append(bl[i].name + "\t\t" + bl[i].quantity.ToString() + Environment.NewLine);
            }
            bl.update_total_bearing_quantity();
            sb.Append("Total: " + bl.TotalNumberOfBearings.ToString() + Environment.NewLine);
            return sb.ToString();
        }
        public string ToPackListText_v2(ProductList OrderItems)
        {
            //this one directly print the topacklist proterty of each product.
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < OrderItems.Count; i++)
            {
                sb.Append(OrderItems[i].name + Environment.NewLine); //show the product's name first
                sb.Append(OrderItems[i].toPackList + Environment.NewLine); 
            }
            return sb.ToString();
        }
        public string ToPackListText(Product Item)
        {
            StringBuilder sb = new StringBuilder();
            ProductList bl = new ProductList();
            ProductList items = new ProductList(); //to hold the Item
            Product item = new Product();
            item.copy(Item); //don't alter Item
            item.quantity = 1; //assume the quantity to be 1
            items.add(item);
            bl.copy(this.ToPackList(items));
            for (int i = 0; i < bl.Count; i++)
            {
                sb.Append(bl[i].name + "\t\t" + bl[i].quantity.ToString() + Environment.NewLine);
            }
            return sb.ToString();
        }

        public void AddNoRepeat(Product b)
        {//add b to bl, if b exists in bl, add the qty
            //if bl is empty, bl.count = 0, then it skips the for loop
            for (int i = 0; i < this.Count; i++)
            {
                if (this[i].ID == b.ID)
                {
                    this[i].quantity += b.quantity;
                    return;
                }
            }
            //reaches here means b is not in bl, then just add it in
            Product nwb = new Product();
            //dont' want to change b so make a copy and put it in bl
            nwb.copy(b);
            this.add(nwb);
        }
        public void fill_grid(System.Windows.Forms.DataGridView ProductCatalogGrid)
        {
            for (int i = 0; i < Count; i++)
            {
                Product b = this[i];
                //ProductCatalogGrid.Rows.Add();
                b.fill_to_row_i(ProductCatalogGrid, i);
                //for bearing business only
                //if product price list is empty, add the default qty list
                if (b.PriceListQty.Count == 0)
                {
                    b.PriceListQty = b.DefualtPriceListQty.ToList();
                }
            }
        }

        public void refresh_grid(System.Windows.Forms.DataGridView ProductCatalogGrid)
        {
            for (int i = 0; i < Count; i++)
            {
                Product b = this[i];
                b.fill_to_row_i(ProductCatalogGrid, i);
            }
        }
        public void ShowSearchResults(System.Windows.Forms.DataGridView ProductCatalogGrid, string SearchKeyWord)
        {
            //clear all the products list 
            ProductCatalogGrid.Rows.Clear();                        
            int j = 0;
            for (int i = 0; i < Count; i++)
            {
                Product b = this[i];
                string content;
                //check if string in each field contains substring
                //if substring str2 cannot be found, str1.IndexOf(str2) returns -1
                //add content to be search
                content = b.name + b.ID;
                if (CommonUtil.KeyWordsFound(SearchKeyWord, content))
                {//the key word is found, add it to the grid
                    ProductCatalogGrid.Rows.Add();
                    b.fill_to_row_i(ProductCatalogGrid, j);
                    //fill_a_product_to_row_i(b, j);
                    j++;
                }
            }
    }
        public int contains(Product p)
        {
            //return -1 means not contain
            //return > >-1 means contains, and the return value is the index of the item in the list
            for (int i = 0; i < Count; i++)
            {
                //double confirm
                if (this[i].ID == p.ID && this[i].name == p.name)
                {                    
                    return i;
                }               
            }
            return -1;
        }
        #endregion

        #region XML read and write methods

        //Indexer
        public Product this[int location]
        {
            get { return (Product)productlist[location]; }
            set { productlist[location] = value; }
        }

        [XmlElement("Item")]
        //this is a property, when use: item.items[0], returns an item
        public Product[] Bearings
        {
            get
            {
                Product[] bearings = new Product[productlist.Count];
                productlist.CopyTo(bearings);                
                return bearings;
            }

            set
            {
                if (value == null) return;
                Product[] bearings = (Product[])value;
                productlist.Clear();
                foreach (Product b in bearings)
                {
                    productlist.Add(b);
                    //this Add is the built-in method of arraylist, not my own method in the ProductList class
                }
            }
        }

        public virtual void save_as(string filename)
        {
            string FilePath = commonDBdirectory + filename;
            // Serialization
            XmlSerializer ser = new XmlSerializer(typeof(ProductList));
            TextWriter xml_write = new StreamWriter(FilePath);

            ser.Serialize(xml_write, this);
            xml_write.Close();

            //save the cluster table as well
            save_ClusterTable();
        }

        public virtual void load(string filename)
        {            
            string FilePath = commonDBdirectory + filename;
            ProductList temp = new ProductList();
            // Deserialization             
            XmlSerializer ser = new XmlSerializer(typeof(ProductList));

            if (FilePath != null && File.Exists(FilePath))
            {
                TextReader xml_read = new StreamReader(FilePath);

                temp = (ProductList)ser.Deserialize(xml_read);

                copy(temp);
                update_total_bearing_quantity();
                xml_read.Close();
            }
            else //dont find the xml file, so creat an empty bearing list anyway
            {
                copy(temp);
            }
        }

        #endregion

        #region Private variables

        
        private ArrayList productlist;
        public string commonDBdirectory = "../../ProgramData/";
        //public string FilePath = @"../../ProgramData/ProductData.xml";
        public string DatabaseName;
        public int TotalNumberOfBearings = 0;
        [XmlIgnore]
        public ClusterTableList clustertables;
        #endregion  

    }




}
