using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ClosedXML.Excel
{
    internal class XLTable : XLRange, IXLTable
    {
        #region Private fields

        private string _name;
        internal bool _showTotalsRow;
        internal HashSet<String> _uniqueNames;

        #endregion

        #region Constructor

        public XLTable(XLRange range, Boolean addToTables, Boolean setAutofilter = true)
            : base(new XLRangeParameters(range.RangeAddress, range.Style ))
        {
            InitializeValues(setAutofilter);

            Int32 id = 1;
            while (true)
            {
                string tableName = String.Format("Table{0}", id);
                if (!Worksheet.Tables.Any(t => t.Name == tableName))
                {
                    Name = tableName;
                    AddToTables(range, addToTables);
                    break;
                }
                id++;
            }
        }

        public XLTable(XLRange range, String name, Boolean addToTables, Boolean setAutofilter = true)
            : base(new XLRangeParameters(range.RangeAddress, range.Style))
        {
            InitializeValues(setAutofilter);

            Name = name;
            AddToTables(range, addToTables);
        }

        #endregion

        private IXLRangeAddress _lastRangeAddress;
        private Dictionary<String, IXLTableField> _fieldNames = null;
        public Dictionary<String, IXLTableField> FieldNames
        {
            get
            {
                if (_fieldNames != null && _lastRangeAddress != null && _lastRangeAddress.Equals(RangeAddress)) return _fieldNames;

                _fieldNames = new Dictionary<String, IXLTableField>();
                _lastRangeAddress = RangeAddress;

                if (ShowHeaderRow)
                {
                    var headersRow = HeadersRow();
                    Int32 cellPos = 0;
                    foreach (var cell in headersRow.Cells())
                    {
                        var name = cell.GetString();
                        if (XLHelper.IsNullOrWhiteSpace(name)) 
                        {
                            name = "Column" + (cellPos + 1);
                            cell.SetValue(name);
                        }
                        if (_fieldNames.ContainsKey(name))
                            throw new ArgumentException("The header row contains more than one field name '" + name + "'.");

                        _fieldNames.Add(name, new XLTableField(this) {Index = cellPos++, Name = name});
                    }
                }
                else
                {
                    if (_fieldNames == null) _fieldNames = new Dictionary<String, IXLTableField>();

                    Int32 colCount = ColumnCount();
                    for (Int32 i = 1; i <= colCount; i++)
                    {
                        if (!_fieldNames.Values.Any(f => f.Index == i - 1))
                        {
                            var name = "Column" + i;

                            _fieldNames.Add(name, new XLTableField(this) {Index = i - 1, Name = name});
                        }
                    }
                }
                return _fieldNames;
            }
        }

        internal void AddFields(IEnumerable<String> fieldNames)
        {
            _fieldNames = new Dictionary<String, IXLTableField>();

            Int32 cellPos = 0;
            foreach(var name in fieldNames)
            {
                _fieldNames.Add(name, new XLTableField(this) { Index = cellPos++, Name = name });
            }
        }


        public String RelId { get; set; }

        public IXLTableRange DataRange
        {
            get
            {
                XLRange range;
                //var ws = Worksheet;
                //var tracking = ws.EventTrackingEnabled;
                //ws.EventTrackingEnabled = false;

                if (_showHeaderRow)
                {
                    range = _showTotalsRow
                                ? Range(2, 1,RowCount() - 1,ColumnCount())
                                : Range(2, 1, RowCount(), ColumnCount());
                }
                else
                {
                    range = _showTotalsRow
                                ? Range(1, 1, RowCount() - 1, ColumnCount())
                                : Range(1, 1, RowCount(), ColumnCount());
                }
                //ws.EventTrackingEnabled = tracking;
                return new XLTableRange(range, this);
            }
        }

        private XLAutoFilter _autoFilter;
        public XLAutoFilter AutoFilter
        {
            get
            {
                using (var asRange = ShowTotalsRow ? Range(1, 1, RowCount() - 1, ColumnCount()) : AsRange())
                {
                    if (_autoFilter == null)
                        _autoFilter = new XLAutoFilter();

                    _autoFilter.Range = asRange;
                }
                return _autoFilter;
            }
        }

        public new IXLBaseAutoFilter SetAutoFilter()
        {
            return AutoFilter;
        }

        #region IXLTable Members

        public Boolean EmphasizeFirstColumn { get; set; }
        public Boolean EmphasizeLastColumn { get; set; }
        public Boolean ShowRowStripes { get; set; }
        public Boolean ShowColumnStripes { get; set; }

        private Boolean _showAutoFilter;
        public Boolean ShowAutoFilter { 
            get { return _showHeaderRow && _showAutoFilter; } 
            set { _showAutoFilter = value; }
            }
        public XLTableTheme Theme { get; set; }

        public String Name
        {
            get { return _name; }
            set
            {
                if (Worksheet.Tables.Any(t => t.Name == value))
                {
                    throw new ArgumentException(String.Format("This worksheet already contains a table named '{0}'",
                                                              value));
                }

                _name = value;
            }
        }

        public Boolean ShowTotalsRow
        {
            get { return _showTotalsRow; }
            set
            {
                if (value && !_showTotalsRow)
                    InsertRowsBelow(1);
                else if (!value && _showTotalsRow)
                    TotalsRow().Delete();

                _showTotalsRow = value;

                if (_showTotalsRow)
                {
                    AutoFilter.Range = Worksheet.Range(
                        RangeAddress.FirstAddress.RowNumber, RangeAddress.FirstAddress.ColumnNumber,
                        RangeAddress.LastAddress.RowNumber - 1, RangeAddress.LastAddress.ColumnNumber);
                }
                else
                    AutoFilter.Range = Worksheet.Range(RangeAddress);
            }
        }

        public IXLRangeRow HeadersRow()
        {
            if (!ShowHeaderRow) return null;

            var m = FieldNames;
            return FirstRow();
        }

        public IXLRangeRow TotalsRow()
        {
            return ShowTotalsRow ? LastRow() : null;
        }

        public IXLTableField Field(String fieldName)
        {
            return Field(GetFieldIndex(fieldName));
        }

        public IXLTableField Field(Int32 fieldIndex)
        {
            return FieldNames.Values.First(f => f.Index == fieldIndex);
        }

        public IEnumerable<IXLTableField> Fields
        {
            get
            {
                Int32 columnCount = ColumnCount();
                for (int co = 0; co < columnCount; co++)
                    yield return Field(co);
            }
        }

        public IXLTable SetEmphasizeFirstColumn()
        {
            EmphasizeFirstColumn = true;
            return this;
        }

        public IXLTable SetEmphasizeFirstColumn(Boolean value)
        {
            EmphasizeFirstColumn = value;
            return this;
        }

        public IXLTable SetEmphasizeLastColumn()
        {
            EmphasizeLastColumn = true;
            return this;
        }

        public IXLTable SetEmphasizeLastColumn(Boolean value)
        {
            EmphasizeLastColumn = value;
            return this;
        }

        public IXLTable SetShowRowStripes()
        {
            ShowRowStripes = true;
            return this;
        }

        public IXLTable SetShowRowStripes(Boolean value)
        {
            ShowRowStripes = value;
            return this;
        }

        public IXLTable SetShowColumnStripes()
        {
            ShowColumnStripes = true;
            return this;
        }

        public IXLTable SetShowColumnStripes(Boolean value)
        {
            ShowColumnStripes = value;
            return this;
        }

        public IXLTable SetShowTotalsRow()
        {
            ShowTotalsRow = true;
            return this;
        }

        public IXLTable SetShowTotalsRow(Boolean value)
        {
            ShowTotalsRow = value;
            return this;
        }

        public IXLTable SetShowAutoFilter()
        {
            ShowAutoFilter = true;
            return this;
        }

        public IXLTable SetShowAutoFilter(Boolean value)
        {
            ShowAutoFilter = value;
            return this;
        }

        public new IXLRange Sort(String columnsToSortBy, XLSortOrder sortOrder = XLSortOrder.Ascending,
                                 Boolean matchCase = false, Boolean ignoreBlanks = true)
        {
            var toSortBy = new StringBuilder();
            foreach (string coPairTrimmed in columnsToSortBy.Split(',').Select(coPair => coPair.Trim()))
            {
                String coString;
                String order;
                if (coPairTrimmed.Contains(' '))
                {
                    var pair = coPairTrimmed.Split(' ');
                    coString = pair[0];
                    order = pair[1];
                }
                else
                {
                    coString = coPairTrimmed;
                    order = "ASC";
                }

                Int32 co;
                if (!Int32.TryParse(coString, out co))
                    co = Field(coString).Index + 1;

                toSortBy.Append(co);
                toSortBy.Append(" ");
                toSortBy.Append(order);
                toSortBy.Append(",");
            }
            return DataRange.Sort(toSortBy.ToString(0, toSortBy.Length - 1), sortOrder, matchCase, ignoreBlanks);
        }

        public new IXLTable Clear(XLClearOptions clearOptions = XLClearOptions.ContentsAndFormats)
        {
            base.Clear(clearOptions);
            return this;
        }

        IXLBaseAutoFilter IXLTable.AutoFilter
        {
            get { return AutoFilter; }
        }

        public new void Dispose()
        {
            if (AutoFilter != null)
                AutoFilter.Dispose();

            base.Dispose();
        }

        #endregion



        private void InitializeValues(Boolean setAutofilter)
        {
            ShowRowStripes = true;
            _showHeaderRow = true;
            Theme = XLTableTheme.TableStyleLight9;
            if (setAutofilter)
                InitializeAutoFilter();

            HeadersRow().DataType = XLCellValues.Text;

            if (RowCount() == 1)
                InsertRowsBelow(1);
        }

        public void InitializeAutoFilter()
        {
            ShowAutoFilter = true;
        }

        private void AddToTables(XLRange range, Boolean addToTables)
        {
            if (!addToTables) return;

            _uniqueNames = new HashSet<string>();
            Int32 co = 1;
            foreach (IXLCell c in range.Row(1).Cells())
            {
                if (XLHelper.IsNullOrWhiteSpace(((XLCell)c).InnerText))
                    c.Value = GetUniqueName("Column" + co.ToStringLookup());
                _uniqueNames.Add(c.GetString());
                co++;
            }
            Worksheet.Tables.Add(this);
        }


        private String GetUniqueName(String originalName)
        {
            String name = originalName;
            if (_uniqueNames.Contains(name))
            {
                Int32 i = 1;
                name = originalName + i.ToStringLookup();
                while (_uniqueNames.Contains(name))
                {
                    i++;
                    name = originalName + i.ToStringLookup();
                }
            }

            _uniqueNames.Add(name);
            return name;
        }

        public Int32 GetFieldIndex(String name)
        {
            if (FieldNames.ContainsKey(name))
                return FieldNames[name].Index;

            throw new ArgumentOutOfRangeException("The header row doesn't contain field name '" + name + "'.");
        }

        internal Boolean _showHeaderRow;
        public Boolean ShowHeaderRow
        {
            get { return _showHeaderRow; }
            set
            {
                if (_showHeaderRow == value) return;

                if (_showHeaderRow)
                {
                    var headersRow = HeadersRow();
                    _uniqueNames = new HashSet<string>();
                    Int32 co = 1;
                    foreach (IXLCell c in headersRow.Cells())
                    {
                        if (XLHelper.IsNullOrWhiteSpace(((XLCell)c).InnerText))
                            c.Value = GetUniqueName("Column" + co.ToStringLookup());
                        _uniqueNames.Add(c.GetString());
                        co++;
                    }
                    
                    headersRow.Clear();
                    RangeAddress.FirstAddress = new XLAddress(Worksheet, RangeAddress.FirstAddress.RowNumber + 1,
                                          RangeAddress.FirstAddress.ColumnNumber,
                                          RangeAddress.FirstAddress.FixedRow,
                                          RangeAddress.FirstAddress.FixedColumn);

                    HeadersRow().DataType = XLCellValues.Text;
                }
                else
                {
                    using(var asRange = Worksheet.Range(
                        RangeAddress.FirstAddress.RowNumber - 1 , 
                        RangeAddress.FirstAddress.ColumnNumber,
                        RangeAddress.LastAddress.RowNumber,
                        RangeAddress.LastAddress.ColumnNumber
                        ))
                        using (var firstRow = asRange.FirstRow())
                            {
                                IXLRangeRow rangeRow;
                                if (firstRow.IsEmpty(true))
                                {
                                    rangeRow = firstRow;
                                    RangeAddress.FirstAddress = new XLAddress(Worksheet, 
                                          RangeAddress.FirstAddress.RowNumber - 1,
                                          RangeAddress.FirstAddress.ColumnNumber,
                                          RangeAddress.FirstAddress.FixedRow,
                                          RangeAddress.FirstAddress.FixedColumn);
                                }
                                else
                                {
                                    var fAddress = RangeAddress.FirstAddress;
                                    var lAddress = RangeAddress.LastAddress;

                                    rangeRow = firstRow.InsertRowsBelow(1, false).First();


                                    RangeAddress.FirstAddress = new XLAddress(Worksheet, fAddress.RowNumber,
                                                                              fAddress.ColumnNumber,
                                                                              fAddress.FixedRow,
                                                                              fAddress.FixedColumn);

                                    RangeAddress.LastAddress = new XLAddress(Worksheet, lAddress.RowNumber + 1,
                                                                             lAddress.ColumnNumber,
                                                                             lAddress.FixedRow,
                                                                             lAddress.FixedColumn);
                                }

                                Int32 co = 1;
                                foreach (var name in FieldNames.Keys)
                                {
                                    rangeRow.Cell(co).SetValue(name);
                                    co++;
                                }

                            }
                }
                _showHeaderRow = value;
            }
        }
        public IXLTable SetShowHeaderRow()
        {
            return SetShowHeaderRow(true);
        } 
        public IXLTable SetShowHeaderRow(Boolean value)
        {
            ShowHeaderRow = value;
            return this;
        }

        public void ExpandTableRows(Int32 rows)
        {
            RangeAddress.LastAddress = new XLAddress(Worksheet, RangeAddress.LastAddress.RowNumber + rows,
                                                     RangeAddress.LastAddress.ColumnNumber,
                                                     RangeAddress.LastAddress.FixedRow,
                                                     RangeAddress.LastAddress.FixedColumn);
        }

    }
}