using System;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using LeanCloud.Storage.Internal.Query;
using LeanCloud.Storage.Internal.Object;

namespace LeanCloud.Storage {
    /// <summary>
    /// 查询类
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class LCQuery<T> where T : LCObject {
        public string ClassName {
            get; private set;
        }

        internal LCCompositionalCondition condition;

        public LCQuery(string className) {
            ClassName = className;
            condition = new LCCompositionalCondition();
        }

        /// <summary>
        /// 等于
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public LCQuery<T> WhereEqualTo(string key, object value)  {
            condition.WhereEqualTo(key, value);
            return this;
        }

        /// <summary>
        /// 不等于
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public LCQuery<T> WhereNotEqualTo(string key, object value) {
            condition.WhereNotEqualTo(key, value);
            return this;
        }

        /// <summary>
        /// 包含
        /// </summary>
        /// <param name="key"></param>
        /// <param name="values"></param>
        /// <returns></returns>
        public LCQuery<T> WhereContainedIn(string key, IEnumerable values) {
            condition.WhereContainedIn(key, values);
            return this;
        }

        /// <summary>
        /// 不包含
        /// </summary>
        /// <param name="key"></param>
        /// <param name="values"></param>
        /// <returns></returns>
        public LCQuery<T> WhereNotContainedIn(string key, IEnumerable values) {
            condition.WhereNotContainedIn(key, values);
            return this;
        }

        /// <summary>
        /// 包含全部
        /// </summary>
        /// <param name="key"></param>
        /// <param name="values"></param>
        /// <returns></returns>
        public LCQuery<T> WhereContainsAll(string key, IEnumerable values) {
            condition.WhereContainsAll(key, values);
            return this;
        }

        /// <summary>
        /// 存在
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public LCQuery<T> WhereExists(string key) {
            condition.WhereExists(key);
            return this;
        }

        /// <summary>
        /// 不存在
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public LCQuery<T> WhereDoesNotExist(string key) {
            condition.WhereDoesNotExist(key);
            return this;
        }

        /// <summary>
        /// 长度等于
        /// </summary>
        /// <param name="key"></param>
        /// <param name="size"></param>
        /// <returns></returns>
        public LCQuery<T> WhereSizeEqualTo(string key, int size) {
            condition.WhereSizeEqualTo(key, size);
            return this;
        }

        /// <summary>
        /// 大于
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public LCQuery<T> WhereGreaterThan(string key, object value) {
            condition.WhereGreaterThan(key, value);
            return this;
        }

        /// <summary>
        /// 大于等于
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public LCQuery<T> WhereGreaterThanOrEqualTo(string key, object value) {
            condition.WhereGreaterThanOrEqualTo(key, value);
            return this;
        }

        /// <summary>
        /// 小于
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public LCQuery<T> WhereLessThan(string key, object value) {
            condition.WhereLessThan(key, value);
            return this;
        }

        /// <summary>
        /// 小于等于
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public LCQuery<T> WhereLessThanOrEqualTo(string key, object value) {
            condition.WhereLessThanOrEqualTo(key, value);
            return this;
        }

        /// <summary>
        /// 相邻
        /// </summary>
        /// <param name="key"></param>
        /// <param name="point"></param>
        /// <returns></returns>
        public LCQuery<T> WhereNear(string key, LCGeoPoint point) {
            condition.WhereNear(key, point);
            return this;
        }

        /// <summary>
        /// 在坐标区域内
        /// </summary>
        /// <param name="key"></param>
        /// <param name="southwest"></param>
        /// <param name="northeast"></param>
        /// <returns></returns>
        public LCQuery<T> WhereWithinGeoBox(string key, LCGeoPoint southwest, LCGeoPoint northeast) {
            condition.WhereWithinGeoBox(key, southwest, northeast);
            return this;
        }

        /// <summary>
        /// 相关
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="key"></param>
        /// <returns></returns>
        public LCQuery<T> WhereRelatedTo(LCObject parent, string key) {
            condition.WhereRelatedTo(parent, key);
            return this;
        }

        /// <summary>
        /// 前缀
        /// </summary>
        /// <param name="key"></param>
        /// <param name="prefix"></param>
        /// <returns></returns>
        public LCQuery<T> WhereStartsWith(string key, string prefix) {
            condition.WhereStartsWith(key, prefix);
            return this;
        }

        /// <summary>
        /// 后缀
        /// </summary>
        /// <param name="key"></param>
        /// <param name="suffix"></param>
        /// <returns></returns>
        public LCQuery<T> WhereEndsWith(string key, string suffix) {
            condition.WhereEndsWith(key, suffix);
            return this;
        }

        /// <summary>
        /// 字符串包含
        /// </summary>
        /// <param name="key"></param>
        /// <param name="subString"></param>
        /// <returns></returns>
        public LCQuery<T> WhereContains(string key, string subString) {
            condition.WhereContains(key, subString);
            return this;
        }

        /// <summary>
        /// 正则匹配
        /// </summary>
        /// <param name="key"></param>
        /// <param name="regex"></param>
        /// <param name="modifiers"></param>
        /// <returns></returns>
        public LCQuery<T> WhereMatches(string key, string regex, string modifiers = null) {
            condition.WhereMatches(key, regex, modifiers);
            return this;
        }

        /// <summary>
        /// 关系查询
        /// </summary>
        /// <param name="key"></param>
        /// <param name="query"></param>
        /// <returns></returns>
        public LCQuery<T> WhereMatchesQuery<K>(string key, LCQuery<K> query) where K : LCObject {
            condition.WhereMatchesQuery(key, query);
            return this;
        }

        /// <summary>
        /// 不满足子查询
        /// </summary>
        /// <typeparam name="K"></typeparam>
        /// <param name="key"></param>
        /// <param name="query"></param>
        /// <returns></returns>
        public LCQuery<T> WhereDoesNotMatchQuery<K>(string key, LCQuery<K> query) where K : LCObject {
            condition.WhereDoesNotMatchQuery(key, query);
            return this;
        }

        /// <summary>
        /// 按 key 升序
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public LCQuery<T> OrderByAscending(string key) {
            condition.OrderByAscending(key);
            return this;
        }

        /// <summary>
        /// 按 key 降序
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public LCQuery<T> OrderByDescending(string key) {
            condition.OrderByDescending(key);
            return this;
        }

        /// <summary>
        /// 增加按 key 升序
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public LCQuery<T> AddAscendingOrder(string key) {
            condition.AddAscendingOrder(key);
            return this;
        }

        /// <summary>
        /// 增加按 key 降序
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public LCQuery<T> AddDescendingOrder(string key) {
            condition.AddDescendingOrder(key);
            return this;
        }

        /// <summary>
        /// 拉取 key 的完整对象
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public LCQuery<T> Include(string key) {
            condition.Include(key);
            return this;
        }

        /// <summary>
        /// 包含 key
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public LCQuery<T> Select(string key) {
            condition.Select(key);
            return this;
        }

        /// <summary>
        /// 跳过
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public LCQuery<T> Skip(int value) {
            condition.Skip = value;
            return this;
        }

        /// <summary>
        /// 限制数量
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public LCQuery<T> Limit(int value) {
            condition.Limit = value;
            return this;
        }

        public async Task<int> Count() {
            string path = $"classes/{ClassName}";
            Dictionary<string, object> parameters = BuildParams();
            parameters["limit"] = 0;
            parameters["count"] = 1;
            Dictionary<string, object> ret = await LCApplication.HttpClient.Get<Dictionary<string, object>>(path, queryParams: parameters);
            return (int)ret["count"];
        }

        public async Task<T> Get(string objectId) {
            if (string.IsNullOrEmpty(objectId)) {
                throw new ArgumentNullException(nameof(objectId));
            }
            WhereEqualTo("objectId", objectId);
            Limit(1);
            ReadOnlyCollection<T> results = await Find();
            if (results != null) {
                if (results.Count == 0) {
                    return null;
                }
                return results[0];
            }
            return null;
        }

        public async Task<ReadOnlyCollection<T>> Find() {
            string path = $"classes/{ClassName}";
            Dictionary<string, object> parameters = BuildParams();
            Dictionary<string, object> response = await LCApplication.HttpClient.Get<Dictionary<string, object>>(path, queryParams: parameters);
            List<object> results = response["results"] as List<object>;
            List<T> list = new List<T>();
            foreach (object item in results) {
                LCObjectData objectData = LCObjectData.Decode(item as Dictionary<string, object>);
                T obj = LCObject.Create(ClassName) as T;
                obj.Merge(objectData);
                list.Add(obj);
            }
            return list.AsReadOnly();
        }

        public async Task<T> First() {
            Limit(1);
            ReadOnlyCollection<T> results = await Find();
            if (results != null && results.Count > 0) {
                return results[0];
            }
            return null;
        }

        public static LCQuery<T> And(IEnumerable<LCQuery<T>> queries) {
            if (queries == null || queries.Count() < 1) {
                throw new ArgumentNullException(nameof(queries));
            }
            LCQuery<T> compositionQuery = new LCQuery<T>(null);
            string className = null;
            foreach (LCQuery<T> query in queries) {
                if (className != null && className != query.ClassName) {
                    throw new Exception("All of the queries in an or query must be on the same class.");
                }
                className = query.ClassName;
                compositionQuery.condition.Add(query.condition);
            }
            compositionQuery.ClassName = className;
            return compositionQuery;
        }

        public static LCQuery<T> Or(IEnumerable<LCQuery<T>> queries) {
            if (queries == null || queries.Count() < 1) {
                throw new ArgumentNullException(nameof(queries));
            }
            LCQuery<T> compositionQuery = new LCQuery<T>(null);
            compositionQuery.condition = new LCCompositionalCondition(LCCompositionalCondition.Or);
            string className = null;
            foreach (LCQuery<T> query in queries) {
                if (className != null && className != query.ClassName) {
                    throw new Exception("All of the queries in an or query must be on the same class.");
                }
                className = query.ClassName;
                compositionQuery.condition.Add(query.condition);
            }
            compositionQuery.ClassName = className;
            return compositionQuery;
        }

        Dictionary<string, object> BuildParams() {
            return condition.BuildParams();
        }

        internal string BuildWhere() {
            return condition.BuildWhere();
        }
    }
}
