// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;

namespace System.Data.Common
{
    [ListBindable(false)]
    public sealed class DataTableMappingCollection : MarshalByRefObject, ITableMappingCollection
    {
        private List<DataTableMapping> _items; // delay creation until AddWithoutEvents, Insert, CopyTo, GetEnumerator

        public DataTableMappingCollection() { }

        // explicit ICollection implementation
        bool ICollection.IsSynchronized => false;
        object ICollection.SyncRoot => this;

        // explicit IList implementation
        bool IList.IsReadOnly => false;
        bool IList.IsFixedSize => false;
        object IList.this[int index]
        {
            get { return this[index]; }
            set
            {
                ValidateType(value);
                this[index] = (DataTableMapping)value;
            }
        }

        object ITableMappingCollection.this[string index]
        {
            get { return this[index]; }
            set
            {
                ValidateType(value);
                this[index] = (DataTableMapping)value;
            }
        }
        ITableMapping ITableMappingCollection.Add(string sourceTableName, string dataSetTableName) =>
            Add(sourceTableName, dataSetTableName);

        ITableMapping ITableMappingCollection.GetByDataSetTable(string dataSetTableName) =>
            GetByDataSetTable(dataSetTableName);

        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public int Count => (null != _items) ? _items.Count : 0;

        private Type ItemType => typeof(DataTableMapping);

        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public DataTableMapping this[int index]
        {
            get
            {
                RangeCheck(index);
                return _items[index];
            }
            set
            {
                RangeCheck(index);
                Replace(index, value);
            }
        }

        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public DataTableMapping this[string sourceTable]
        {
            get
            {
                int index = RangeCheck(sourceTable);
                return _items[index];
            }
            set
            {
                int index = RangeCheck(sourceTable);
                Replace(index, value);
            }
        }

        public int Add(object value)
        {
            ValidateType(value);
            Add((DataTableMapping)value);
            return Count - 1;
        }

        private DataTableMapping Add(DataTableMapping value)
        {
            AddWithoutEvents(value);
            return value;
        }

        public void AddRange(DataTableMapping[] values) => AddEnumerableRange(values, false);

        public void AddRange(System.Array values) => AddEnumerableRange(values, false);

        private void AddEnumerableRange(IEnumerable values, bool doClone)
        {
            if (null == values)
            {
                throw ADP.ArgumentNull(nameof(values));
            }

            foreach (object value in values)
            {
                ValidateType(value);
            }

            if (doClone)
            {
                foreach (ICloneable value in values)
                {
                    AddWithoutEvents(value.Clone() as DataTableMapping);
                }
            }
            else
            {
                foreach (DataTableMapping value in values)
                {
                    AddWithoutEvents(value);
                }
            }
        }

        public DataTableMapping Add(string sourceTable, string dataSetTable) =>
            Add(new DataTableMapping(sourceTable, dataSetTable));

        private void AddWithoutEvents(DataTableMapping value)
        {
            Validate(-1, value);
            value.Parent = this;
            ArrayList().Add(value);
        }

        // implemented as a method, not as a property because the VS7 debugger
        // object browser calls properties to display their value, and we want this delayed
        private List<DataTableMapping> ArrayList() => _items ?? (_items = new List<DataTableMapping>());

        public void Clear()
        {
            if (0 < Count)
            {
                ClearWithoutEvents();
            }
        }

        private void ClearWithoutEvents()
        {
            if (null != _items)
            {
                foreach (DataTableMapping item in _items)
                {
                    item.Parent = null;
                }
                _items.Clear();
            }
        }

        public bool Contains(string value) => (-1 != IndexOf(value));

        public bool Contains(object value) => (-1 != IndexOf(value));

        public void CopyTo(Array array, int index) => ((ICollection)ArrayList()).CopyTo(array, index);

        public void CopyTo(DataTableMapping[] array, int index) => ArrayList().CopyTo(array, index);

        public DataTableMapping GetByDataSetTable(string dataSetTable)
        {
            int index = IndexOfDataSetTable(dataSetTable);
            if (0 > index)
            {
                throw ADP.TablesDataSetTable(dataSetTable);
            }
            return _items[index];
        }

        public IEnumerator GetEnumerator() => ArrayList().GetEnumerator();

        public int IndexOf(object value)
        {
            if (null != value)
            {
                ValidateType(value);
                for (int i = 0; i < Count; ++i)
                {
                    if (_items[i] == value)
                    {
                        return i;
                    }
                }
            }
            return -1;
        }

        public int IndexOf(string sourceTable)
        {
            if (!string.IsNullOrEmpty(sourceTable))
            {
                for (int i = 0; i < Count; ++i)
                {
                    string value = _items[i].SourceTable;
                    if ((null != value) && (0 == ADP.SrcCompare(sourceTable, value)))
                    {
                        return i;
                    }
                }
            }
            return -1;
        }

        public int IndexOfDataSetTable(string dataSetTable)
        {
            if (!string.IsNullOrEmpty(dataSetTable))
            {
                for (int i = 0; i < Count; ++i)
                {
                    string value = _items[i].DataSetTable;
                    if ((null != value) && (0 == ADP.DstCompare(dataSetTable, value)))
                    {
                        return i;
                    }
                }
            }
            return -1;
        }

        public void Insert(int index, object value)
        {
            ValidateType(value);
            Insert(index, (DataTableMapping)value);
        }

        public void Insert(int index, DataTableMapping value)
        {
            if (null == value)
            {
                throw ADP.TablesAddNullAttempt(nameof(value));
            }
            Validate(-1, value);
            value.Parent = this;
            ArrayList().Insert(index, value);
        }

        private void RangeCheck(int index)
        {
            if ((index < 0) || (Count <= index))
            {
                throw ADP.TablesIndexInt32(index, this);
            }
        }

        private int RangeCheck(string sourceTable)
        {
            int index = IndexOf(sourceTable);
            if (index < 0)
            {
                throw ADP.TablesSourceIndex(sourceTable);
            }
            return index;
        }

        public void RemoveAt(int index)
        {
            RangeCheck(index);
            RemoveIndex(index);
        }

        public void RemoveAt(string sourceTable)
        {
            int index = RangeCheck(sourceTable);
            RemoveIndex(index);
        }

        private void RemoveIndex(int index)
        {
            Debug.Assert((null != _items) && (0 <= index) && (index < Count), "RemoveIndex, invalid");
            _items[index].Parent = null;
            _items.RemoveAt(index);
        }

        public void Remove(object value)
        {
            ValidateType(value);
            Remove((DataTableMapping)value);
        }

        public void Remove(DataTableMapping value)
        {
            if (null == value)
            {
                throw ADP.TablesAddNullAttempt(nameof(value));
            }
            int index = IndexOf(value);

            if (-1 != index)
            {
                RemoveIndex(index);
            }
            else
            {
                throw ADP.CollectionRemoveInvalidObject(ItemType, this);
            }
        }

        private void Replace(int index, DataTableMapping newValue)
        {
            Validate(index, newValue);
            _items[index].Parent = null;
            newValue.Parent = this;
            _items[index] = newValue;
        }

        private void ValidateType(object value)
        {
            if (null == value)
            {
                throw ADP.TablesAddNullAttempt(nameof(value));
            }
            else if (!ItemType.IsInstanceOfType(value))
            {
                throw ADP.NotADataTableMapping(value);
            }
        }

        private void Validate(int index, DataTableMapping value)
        {
            if (null == value)
            {
                throw ADP.TablesAddNullAttempt(nameof(value));
            }
            if (null != value.Parent)
            {
                if (this != value.Parent)
                {
                    throw ADP.TablesIsNotParent(this);
                }
                else if (index != IndexOf(value))
                {
                    throw ADP.TablesIsParent(this);
                }
            }
            string name = value.SourceTable;
            if (string.IsNullOrEmpty(name))
            {
                index = 1;
                do
                {
                    name = ADP.SourceTable + index.ToString(System.Globalization.CultureInfo.InvariantCulture);
                    index++;
                } while (-1 != IndexOf(name));
                value.SourceTable = name;
            }
            else
            {
                ValidateSourceTable(index, name);
            }
        }

        internal void ValidateSourceTable(int index, string value)
        {
            int pindex = IndexOf(value);
            if ((-1 != pindex) && (index != pindex))
            {
                // must be non-null and unique
                throw ADP.TablesUniqueSourceTable(value);
            }
        }

        [EditorBrowsable(EditorBrowsableState.Advanced)]
        public static DataTableMapping GetTableMappingBySchemaAction(DataTableMappingCollection tableMappings, string sourceTable, string dataSetTable, MissingMappingAction mappingAction)
        {
            if (null != tableMappings)
            {
                int index = tableMappings.IndexOf(sourceTable);
                if (-1 != index)
                {
#if DEBUG
                    if (AdapterSwitches.DataSchema.TraceWarning)
                    {
                        Debug.WriteLine($"mapping match on SourceTable \"{sourceTable}\"");
                    }
#endif
                    return tableMappings._items[index];
                }
            }
            if (string.IsNullOrEmpty(sourceTable))
            {
                throw ADP.InvalidSourceTable(nameof(sourceTable));
            }
            switch (mappingAction)
            {
                case MissingMappingAction.Passthrough:
#if DEBUG
                    if (AdapterSwitches.DataSchema.TraceInfo)
                    {
                        Debug.WriteLine($"mapping passthrough of SourceTable \"{sourceTable}\" -> \"{dataSetTable}\"");
                    }
#endif
                    return new DataTableMapping(sourceTable, dataSetTable);

                case MissingMappingAction.Ignore:
#if DEBUG
                    if (AdapterSwitches.DataSchema.TraceWarning)
                    {
                        Debug.WriteLine($"mapping filter of SourceTable \"{sourceTable}\"\"");
                    }
#endif
                    return null;

                case MissingMappingAction.Error:
#if DEBUG
                    if (AdapterSwitches.DataSchema.TraceError)
                    {
                        Debug.WriteLine($"mapping error on SourceTable \"\"{sourceTable}\"\"");
                    }
#endif
                    throw ADP.MissingTableMapping(sourceTable);

                default:
                    throw ADP.InvalidMissingMappingAction(mappingAction);
            }
        }
    }
}
