using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;

namespace UVC.Data
{
    /// <summary>
    /// DataObject 객체 컬렉션의 변경사항을 추적하는 데이터 배열 클래스
    /// </summary>
    public class DataArray : List<DataObject>, IDataObject
    {
        // 추가 된 항목 목록
        protected List<DataObject> addedList = new List<DataObject>();
        // 제거 된 항목 목록
        protected List<DataObject> removedList = new List<DataObject>();
        // 수정 된 항목 목록
        protected List<DataObject> modifiedList = new List<DataObject>();


        // 추가된 항목에 접근할 수 있는 읽기 전용 컬렉션
        public ReadOnlyCollection<DataObject> AddedItems => addedList.AsReadOnly();

        // 제거된 항목에 접근할 수 있는 읽기 전용 컬렉션
        public ReadOnlyCollection<DataObject> RemovedItems => removedList.AsReadOnly();

        // 제거된 항목에 접근할 수 있는 읽기 전용 컬렉션
        public ReadOnlyCollection<DataObject> ModifiedList => modifiedList.AsReadOnly();


        /// <summary>
        /// 변경 추적 활성화 여부
        /// </summary>
        public bool IsChangeTracking { get; set; } = false;

        /// <summary>
        /// 기본 생성자
        /// </summary>
        public DataArray() : base()
        {
        }

        /// <summary>
        /// 초기 용량을 지정하는 생성자
        /// </summary>
        /// <param name="capacity">초기 용량</param>
        public DataArray(int capacity) : base(capacity)
        {
        }

        /// <summary>
        /// 기존 컬렉션으로부터 생성하는 생성자
        /// </summary>
        /// <param name="collection">초기 항목을 포함하는 컬렉션</param>
        public DataArray(IEnumerable<DataObject> collection) : base(collection)
        {
        }

        public DataArray(string jsonString) : base()
        {
            if (!string.IsNullOrEmpty(jsonString))
            {
                try
                {
                    JArray jArray = JArray.Parse(jsonString);
                    foreach (var item in jArray)
                    {
                        Add(ConvertToDataObject(item));
                    }
                }
                catch (Exception ex)
                {
                    throw new ArgumentException("Invalid JSON string format.", nameof(jsonString), ex);
                }
            }
        }

        /// <summary>
        /// JArray로부터 DataArray를 생성하는 생성자
        /// </summary>
        /// <param name="jArray">JSON 배열</param>
        public DataArray(JArray jArray) : base()
        {
            if (jArray != null)
            {
                foreach (var item in jArray)
                {
                    Add(ConvertToDataObject(item));
                }
            }
        }

        /// <summary>
        /// JToken을 DataObject로 변환합니다.
        /// </summary>
        private DataObject ConvertToDataObject(JToken token)
        {
            if (token.Type == JTokenType.Object)
            {
                return new DataObject((JObject)token);
            }
            else
            {
                // JObject가 아닌 경우, 새 DataObject를 만들고 값을 넣어줍니다
                var dataObject = new DataObject();
                dataObject.Add("value", ConvertJTokenToObject(token));
                return dataObject;
            }
        }

        private object ConvertJTokenToObject(JToken token)
        {
            switch (token.Type)
            {
                case JTokenType.String:
                    return token.ToString();
                case JTokenType.Integer:
                    return token.ToObject<int>();
                case JTokenType.Float:
                    return token.ToObject<float>();
                case JTokenType.Boolean:
                    return token.ToObject<bool>();
                case JTokenType.Object:
                    return new DataObject((JObject)token);
                case JTokenType.Array:
                    JArray array = (JArray)token;
                    return new DataArray(array);
                default:
                    return token.ToString();
            }
        }

        /// <summary>
        /// 모든 아이템이 추가 된것으로 표시합니다.
        /// 전체 데이터가 갱신되었을 때 사용합니다.
        /// </summary>
        public void InitData()
        {
            addedList.Clear();
            addedList.AddRange(this);
        }

        /// <summary>
        /// 다른 DataObject와 현재 객체를 비교하여 다른 부분만 설정합니다.
        /// 변경된 키는 자동으로 추적됩니다.
        /// </summary>
        /// <param name="other">비교할 DataObject</param>
        public void UpdateDifferent(IDataObject other)
        {
            if (other == null) return;
            if (other is DataArray otherArray)
            {
                addedList.Clear();
                removedList.Clear();
                modifiedList.Clear();

                // Id 기준으로 객체들을 비교하기 위한 사전 생성
                var thisDict = this.ToDictionary(item => item.Id, item => item);
                var otherDict = otherArray.ToDictionary(item => item.Id, item => item);

                // 제거된 항목 확인 (현재 배열에는 있지만 other에는 없는 항목)
                foreach (var id in thisDict.Keys.Where(id => !otherDict.ContainsKey(id)))
                {
                    removedList.Add(thisDict[id]);
                }

                // 추가된 항목 확인 (other에는 있지만 현재 배열에는 없는 항목)
                foreach (var id in otherDict.Keys.Where(id => !thisDict.ContainsKey(id)))
                {
                    addedList.Add(otherDict[id]);
                }

                // 수정된 항목 확인 (양쪽 모두에 있지만 내용이 다른 항목)
                foreach (var id in thisDict.Keys.Where(id => otherDict.ContainsKey(id)))
                {
                    var thisItem = thisDict[id];
                    var otherItem = otherDict[id];

                    if (!thisItem.ToString().Equals(otherItem.ToString()))
                    {
                        modifiedList.Add(thisItem);
                        thisItem.UpdateDifferent(otherItem);
                    }
                }

                // 실제 컬렉션 업데이트
                // 현재 배열에서 제거된 항목들을 제거
                for (int i = this.Count - 1; i >= 0; i--)
                {
                    if (removedList.Contains(this[i]))
                    {
                        this.RemoveAt(i);
                    }
                }

                // 추가된 항목들을 현재 배열에 추가
                foreach (var item in addedList)
                {
                    this.Add(item);
                }
            }
        }


        /// <summary>
        /// 업데이트 된 객체를 반환합니다.
        /// </summary>
        /// <returns></returns>
        public IDataObject GetUpdatedObject()
        {
            return this;
        }

        /// <summary>
        /// 업데이트 된 속성의 수.
        /// </summary>
        /// <returns>업데이트된 속성의 총 개수입니다. 업데이트된 속성이 없으면 0을 반환합니다.</returns>
        public int UpdatedCount { get => addedList.Count + modifiedList.Count + removedList.Count; }
        

        /// <summary>
        /// 항목을 추가합니다.
        /// </summary>
        /// <param name="item">추가할 항목</param>
        public new void Add(DataObject item)
        {
            base.Add(item);

            // 변경 사항을 추적하고 이벤트 발생
            if (IsChangeTracking)
            {
                int index = Count - 1;
                addedList.Add(item);
            }
        }

        /// <summary>
        /// 항목을 제거합니다.
        /// </summary>
        /// <param name="item">제거할 항목</param>
        /// <returns>제거 성공 여부</returns>
        public new bool Remove(DataObject item)
        {
            int index = IndexOf(item);
            if (index >= 0)
            {
                RemoveAt(index);
                return true;
            }
            return false;
        }

        /// <summary>
        /// 지정된 인덱스의 항목을 제거합니다.
        /// </summary>
        /// <param name="index">제거할 항목의 인덱스</param>
        public new void RemoveAt(int index)
        {
            if (index < 0 || index >= Count)
                throw new ArgumentOutOfRangeException(nameof(index));

            DataObject removedItem = this[index];
            base.RemoveAt(index);

            if (IsChangeTracking)
            {
                removedList.Add(removedItem);
            }
        }

        /// <summary>
        /// 컬렉션의 모든 항목을 제거합니다.
        /// </summary>
        public new void Clear()
        {
            if (Count > 0)
            {
                var oldItems = new List<DataObject>(this);
                base.Clear();

                if (IsChangeTracking)
                {
                    // 모든 항목을 제거 목록에 추가
                    removedList.AddRange(oldItems);
                }
            }
        }

        /// <summary>
        /// 지정된 인덱스에 항목을 삽입합니다.
        /// </summary>
        /// <param name="index">삽입할 위치</param>
        /// <param name="item">삽입할 항목</param>
        public new void Insert(int index, DataObject item)
        {
            base.Insert(index, item);

            if (IsChangeTracking)
            {
                addedList.Add(item);
            }
        }

        /// <summary>
        /// 지정된 인덱스의 항목을 교체합니다.
        /// </summary>
        /// <param name="index">교체할 위치</param>
        /// <param name="item">새 항목</param>
        public new DataObject this[int index]
        {
            get { return base[index]; }
            set
            {
                if (index < 0 || index >= Count)
                    throw new ArgumentOutOfRangeException(nameof(index));

                DataObject oldItem = base[index];
                base[index] = value;

                if (IsChangeTracking)
                {
                    addedList.Add(value);
                    removedList.Add(oldItem);
                }
            }
        }

        /// <summary>
        /// 범위의 항목을 추가합니다.
        /// </summary>
        /// <param name="collection">추가할 항목의 컬렉션</param>
        public new void AddRange(IEnumerable<DataObject> collection)
        {
            if (collection == null)
                throw new ArgumentNullException(nameof(collection));

            int startIndex = Count;
            base.AddRange(collection);

            if (IsChangeTracking)
            {
                var items = collection.ToList();

                for (int i = 0; i < items.Count; i++)
                {
                    addedList.Add(items[i]);
                }
            }
        }


        /// <summary>
        /// 변경된 인덱스 목록을 초기화합니다.
        /// </summary>
        public void ResetChangedIndices()
        {
            addedList.Clear();
            removedList.Clear();
            modifiedList.Clear();
        }


        /// <summary>
        /// 변경 내역을 저장하지 않고 항목을 추가합니다.
        /// </summary>
        /// <param name="item">추가할 항목</param>
        public void AddWithoutTracking(DataObject item)
        {
            bool oldTracking = IsChangeTracking;
            IsChangeTracking = false;
            Add(item);
            IsChangeTracking = oldTracking;
        }


        /// <summary>
        /// DataArray를 JArray로 변환합니다.
        /// </summary>
        public JArray ToJArray()
        {
            JArray array = new JArray();
            foreach (var item in this)
            {
                array.Add(item);
            }
            return array;
        }

        /// <summary>
        /// DataArray를 JArray로 암시적 변환합니다.
        /// </summary>
        public static implicit operator JArray(DataArray dataArray)
        {
            return dataArray.ToJArray();
        }

        /// <summary>
        /// JArray를 DataArray로 암시적 변환합니다.
        /// </summary>
        public static implicit operator DataArray(JArray jArray)
        {
            return new DataArray(jArray);
        }
    }
}