using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using LeanCloud.Realtime.Internal;
using LeanCloud;
using LeanCloud.Storage.Internal;
using System.Collections;
using System.IO;

namespace LeanCloud.Realtime
{
    /// <summary>
    /// 对话
    /// </summary>
    public class AVIMConversation : IEnumerable<KeyValuePair<string, object>>, IAVObject
    {
        private DateTime? updatedAt;

        private DateTime? createdAt;

        private DateTime? lastMessageAt;

        internal DateTime? expiredAt;

        private string name;

        private AVObject convState;

        internal readonly Object mutex = new Object();
        //private readonly IDictionary<string, object> estimatedData = new Dictionary<string, object>();

        internal AVIMClient _currentClient;

        IEnumerator<KeyValuePair<string, object>> IEnumerable<KeyValuePair<string, object>>.GetEnumerator()
        {
            lock (mutex)
            {
                return ((IEnumerable<KeyValuePair<string, object>>)convState).GetEnumerator();
            }
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            lock (mutex)
            {
                return ((IEnumerable<KeyValuePair<string, object>>)convState).GetEnumerator();
            }
        }

        virtual public object this[string key]
        {
            get
            {
                return convState[key];
            }
            set
            {
                convState[key] = value;
            }
        }
        public ICollection<string> Keys
        {
            get
            {
                lock (mutex)
                {
                    return convState.Keys;
                }
            }
        }
        public T Get<T>(string key)
        {
            return this.convState.Get<T>(key);
        }
        public bool ContainsKey(string key)
        {
            return this.convState.ContainsKey(key);
        }

        internal IDictionary<string, object> EncodeAttributes()
        {
            var currentOperations = convState.StartSave();
            var jsonToSave = AVObject.ToJSONObjectForSaving(currentOperations);
            return jsonToSave;
        }

        internal void MergeFromPushServer(IDictionary<string, object> json)
        {
            if (json.Keys.Contains("cdate"))
            {
                createdAt = DateTime.Parse(json["cdate"].ToString());
                updatedAt = DateTime.Parse(json["cdate"].ToString());
                json.Remove("cdate");
            }
            if (json.Keys.Contains("lm"))
            {
                var ts = long.Parse(json["lm"].ToString());
                updatedAt = ts.ToDateTime();
                lastMessageAt = ts.ToDateTime();
                json.Remove("lm");
            }
            if (json.Keys.Contains("c"))
            {
                Creator = json["c"].ToString();
                json.Remove("c");
            }
            if (json.Keys.Contains("m"))
            {
                MemberIds = json["m"] as IList<string>;
                json.Remove("m");
            }
            if (json.Keys.Contains("mu"))
            {
                MuteMemberIds = json["mu"] as IList<string>;
                json.Remove("mu");
            }
            if (json.Keys.Contains("tr"))
            {
                IsTransient = bool.Parse(json["tr"].ToString());
                json.Remove("tr");
            }
            if (json.Keys.Contains("sys"))
            {
                IsSystem = bool.Parse(json["sys"].ToString());
                json.Remove("sys");
            }
            if (json.Keys.Contains("cid"))
            {
                ConversationId = json["cid"].ToString();
                json.Remove("cid");
            }

            if (json.Keys.Contains("name"))
            {
                Name = json["name"].ToString();
                json.Remove("name");
            }
        }

        /// <summary>
        /// 当前的AVIMClient，一个对话理论上只存在一个AVIMClient。
        /// </summary>
        public AVIMClient CurrentClient
        {
            get
            {
                if (_currentClient == null) throw new NullReferenceException("当前对话没有关联有效的 AVIMClient。");
                return _currentClient;
            }
            //set
            //{
            //    _currentClient = value;
            //}
        }
        /// <summary>
        /// 对话的唯一ID
        /// </summary>
        public string ConversationId { get; internal set; }

        /// <summary>
        /// 对话在全局的唯一的名字
        /// </summary>
        public string Name
        {
            get
            {
                if (convState.ContainsKey("name"))
                {
                    name = this.convState.Get<string>("name");
                }
                return name;
            }
            set
            {
                if (value == null)
                    this["name"] = "";
                else
                {
                    this["name"] = value;
                }
            }
        }

        /// <summary>
        /// 对话中存在的 Client 的 ClientId 列表
        /// </summary>
        public IEnumerable<string> MemberIds { get; internal set; }

        /// <summary>
        /// 对该对话静音的成员列表
        /// <remarks>
        /// 对该对话设置了静音的成员，将不会收到离线消息的推送。
        /// </remarks>
        /// </summary>
        public IEnumerable<string> MuteMemberIds { get; internal set; }

        /// <summary>
        /// 对话的创建者
        /// </summary>
        public string Creator { get; private set; }

        /// <summary>
        /// 是否为聊天室
        /// </summary>
        public bool IsTransient { get; internal set; }

        /// <summary>
        /// 是否系统对话
        /// </summary>
        public bool IsSystem { get; internal set; }

        /// <summary>
        /// 是否是唯一对话
        /// </summary>
        public bool IsUnique { get; internal set; }

        /// <summary>
        /// 对话是否为虚拟对话
        /// </summary>
        public bool IsTemporary { get; internal set; }

        /// <summary>
        /// 对话创建的时间
        /// </summary>
        public DateTime? CreatedAt
        {
            get
            {
                DateTime? nullable;
                lock (this.mutex)
                {
                    nullable = this.createdAt;
                }
                return nullable;
            }
            private set
            {
                lock (this.mutex)
                {
                    this.createdAt = value;
                }
            }
        }

        /// <summary>
        /// 对话更新的时间
        /// </summary>
        public DateTime? UpdatedAt
        {
            get
            {
                DateTime? nullable;
                lock (this.mutex)
                {
                    nullable = this.updatedAt;
                }
                return nullable;
            }
            private set
            {
                lock (this.mutex)
                {
                    this.updatedAt = value;
                }
            }
        }

        /// <summary>
        /// 对话中最后一条消息的时间，可以用此判断对话的最后活跃时间
        /// </summary>
        public DateTime? LastMessageAt
        {
            get
            {
                DateTime? nullable;
                lock (this.mutex)
                {
                    nullable = this.lastMessageAt;
                }
                return nullable;
            }
            private set
            {
                lock (this.mutex)
                {
                    this.lastMessageAt = value;
                }
            }
        }

        /// <summary>
        /// 已知 id，在本地构建一个 AVIMConversation 对象
        /// </summary>
        public AVIMConversation(string id)
            : this(id, null)
        {

        }

        /// <summary>
        /// 已知 id 在本地构建一个对话
        /// </summary>
        /// <param name="id">对话 id</param>
        /// <param name="client">AVIMClient 实例，必须是登陆成功的</param>
        public AVIMConversation(string id, AVIMClient client) : this(client)
        {
            this.ConversationId = id;
        }

        internal AVIMConversation(AVIMClient client)
        {
            this._currentClient = client;
            this.CurrentClient.OnMessageReceived += CurrentClient_OnMessageReceived;
        }

        /// <summary>
        /// AVIMConversation Build 驱动器
        /// </summary>
        /// <param name="source"></param>
        /// <param name="name"></param>
        /// <param name="creator"></param>
        /// <param name="members"></param>
        /// <param name="muteMembers"></param>
        /// <param name="isTransient"></param>
        /// <param name="isSystem"></param>
        /// <param name="attributes"></param>
        /// <param name="state"></param>
        /// <param name="isUnique"></param>
        /// <param name="isTemporary"></param>
        internal AVIMConversation(AVIMConversation source = null,
            string name = null,
            string creator = null,
            IEnumerable<string> members = null,
            IEnumerable<string> muteMembers = null,
            bool isTransient = false,
            bool isSystem = false,
            IEnumerable<KeyValuePair<string, object>> attributes = null,
            AVObject state = null,
            bool isUnique = true,
            bool isTemporary = false,
            int ttl = 86400,
            AVIMClient client = null) :
            this(client)
        {
            convState = source != null ? source.convState : new AVObject("_Conversation");


            this.Name = source?.Name;
            this.MemberIds = source?.MemberIds;
            this.Creator = source?.Creator;
            this.MuteMemberIds = source?.MuteMemberIds;

            if (!string.IsNullOrEmpty(name))
            {
                this.Name = name;
            }
            if (!string.IsNullOrEmpty(creator))
            {
                this.Creator = creator;
            }
            if (members != null)
            {
                this.MemberIds = members.ToList();
            }
            if (muteMembers != null)
            {
                this.MuteMemberIds = muteMembers.ToList();
            }

            this.IsTransient = isTransient;
            this.IsSystem = isSystem;
            this.IsUnique = isUnique;
            this.IsTemporary = isTemporary;
            this.expiredAt = DateTime.Now + new TimeSpan(0, 0, ttl);

            if (state != null)
            {
                convState = state;
                this.ConversationId = state.ObjectId;
                this.CreatedAt = state.CreatedAt;
                this.UpdatedAt = state.UpdatedAt;
                this.MergeMagicFields(state.ToDictionary(x => x.Key, x => x.Value));
            }

            if (attributes != null)
            {
                this.MergeMagicFields(attributes.ToDictionary(x => x.Key, x => x.Value));
            }
        }

        /// <summary>
        /// 从本地构建一个对话
        /// </summary>
        /// <param name="convId">对话的 objectId</param>
        /// <param name="client"></param>
        /// <returns></returns>
        public static AVIMConversation CreateWithoutData(string convId, AVIMClient client)
        {
            return new AVIMConversation(client)
            {
                ConversationId = convId,
            };
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="magicFields"></param>
        /// <param name="client"></param>
        /// <returns></returns>
        public static AVIMConversation CreateWithData(IEnumerable<KeyValuePair<string, object>> magicFields, AVIMClient client)
        {
            if (magicFields is AVObject)
            {
                return new AVIMConversation(state: (AVObject)magicFields, client: client);
            }
            return new AVIMConversation(attributes: magicFields, client: client);
        }

        #region save to cloud
        /// <summary>
        /// 将修改保存到云端
        /// </summary>
        /// <returns></returns>
        public Task SaveAsync()
        {
            var cmd = new ConversationCommand()
               .Generate(this);

            var convCmd = cmd.Option("update")
                .PeerId(this.CurrentClient.ClientId);

            return this.CurrentClient.RunCommandAsync(convCmd);
        }
        #endregion

        #region send message
        /// <summary>
        /// 向该对话发送消息。
        /// </summary>
        /// <param name="avMessage">消息体</param>
        /// <param name="receipt">是否需要送达回执</param>
        /// <param name="transient">是否是暂态消息，暂态消息不返回送达回执(ack)，不保留离线消息，不触发离线推送</param>
        /// <param name="priority">消息等级，默认是1，可选值还有 2 ，3</param>
        /// <param name="will">标记该消息是否为下线通知消息</param>
        /// <param name="pushData">如果消息的接收者已经下线了，这个字段的内容就会被离线推送到接收者
        /// <remarks>例如，一张图片消息的离线消息内容可以类似于：[您收到一条图片消息，点击查看] 这样的推送内容，参照微信的做法</remarks>
        /// </param>
        /// <returns></returns>
        public Task<IAVIMMessage> SendMessageAsync(IAVIMMessage avMessage,
            bool receipt = true,
            bool transient = false,
            int priority = 1,
            bool will = false,
            IDictionary<string, object> pushData = null)
        {
            return this.SendMessageAsync(avMessage, new AVIMSendOptions()
            {
                Receipt = receipt,
                Transient = transient,
                Priority = priority,
                Will = will,
                PushData = pushData
            });
        }

        /// <summary>
        /// 发送消息
        /// </summary>
        /// <param name="avMessage">消息体</param>
        /// <param name="options">消息的发送选项，包含了一些特殊的标记<see cref="AVIMSendOptions"/></param>
        /// <returns></returns>
        public Task<IAVIMMessage> SendMessageAsync(IAVIMMessage avMessage, AVIMSendOptions options)
        {
            if (this.CurrentClient == null) throw new Exception("当前对话未指定有效 AVIMClient，无法发送消息。");
            return this.CurrentClient.SendMessageAsync(this, avMessage, options);
        }
        #endregion

        #region recall message

        #endregion

        #region message listener and event notify

        /// <summary>
        /// Registers the listener.
        /// </summary>
        /// <param name="listener">Listener.</param>
        public void RegisterListener(IAVIMListener listener)
        {
            this.CurrentClient.RegisterListener(listener, this.ConversationIdHook);
        }

        internal bool ConversationIdHook(AVIMNotice notice)
        {
            if (!notice.RawData.ContainsKey("cid")) return false;
            return notice.RawData["cid"].ToString() == this.ConversationId;
        }
        #endregion

        #region mute && unmute
        /// <summary>
        /// 当前用户针对对话做静音操作
        /// </summary>
        /// <returns></returns>
        public Task MuteAsync()
        {
            return this.CurrentClient.MuteConversationAsync(this);
        }
        /// <summary>
        /// 当前用户取消对话的静音，恢复该对话的离线消息推送
        /// </summary>
        /// <returns></returns>
        public Task UnmuteAsync()
        {
            return this.CurrentClient.UnmuteConversationAsync(this);
        }
        #endregion

        #region 成员操作相关接口

        /// <summary>
        /// Joins the async.
        /// </summary>
        /// <returns>The async.</returns>
        public Task JoinAsync()
        {
            return AddMembersAsync(CurrentClient.ClientId);
        }


        /// <summary>
        /// Adds the members async.
        /// </summary>
        /// <returns>The members async.</returns>
        /// <param name="clientId">Client identifier.</param>
        /// <param name="clientIds">Client identifiers.</param>
        public Task AddMembersAsync(string clientId = null, IEnumerable<string> clientIds = null)
        {
            return this.CurrentClient.InviteAsync(this, clientId, clientIds);
        }

        /// <summary>
        /// Removes the members async.
        /// </summary>
        /// <returns>The members async.</returns>
        /// <param name="clientId">Client identifier.</param>
        /// <param name="clientIds">Client identifiers.</param>
        public Task RemoveMembersAsync(string clientId = null, IEnumerable<string> clientIds = null)
        {
            return this.CurrentClient.KickAsync(this, clientId, clientIds);
        }

        /// <summary>
        /// Quits the async.
        /// </summary>
        /// <returns>The async.</returns>
        public Task QuitAsync()
        {
            return RemoveMembersAsync(CurrentClient.ClientId);
        }
        #endregion

        #region load message history
        /// <summary>
        /// 获取当前对话的历史消息
        /// <remarks>不支持聊天室（暂态对话）</remarks>
        /// </summary>
        /// <param name="beforeMessageId">从 beforeMessageId 开始向前查询（和 beforeTimeStampPoint 共同使用，为防止某毫秒时刻有重复消息）</param>
        /// <param name="afterMessageId"> 截止到某个 afterMessageId (不包含)</param>
        /// <param name="beforeTimeStampPoint">从 beforeTimeStampPoint 开始向前查询</param>
        /// <param name="afterTimeStampPoint">拉取截止到 afterTimeStampPoint 时间戳（不包含）</param>
        /// <param name="direction">查询方向，默认是 1，如果是 1 表示从新消息往旧消息方向， 0 则相反,其他值无效</param>
        /// <param name="limit">获取的消息数量</param>
        /// <returns></returns>
        public Task<IEnumerable<T>> QueryMessageAsync<T>(
           string beforeMessageId = null,
           string afterMessageId = null,
           DateTime? beforeTimeStampPoint = null,
           DateTime? afterTimeStampPoint = null,
           int direction = 1,
           int limit = 20)
            where T : IAVIMMessage
        {
            return this.CurrentClient.QueryMessageAsync<T>(this, beforeMessageId, afterMessageId, beforeTimeStampPoint, afterTimeStampPoint, direction, limit)
                .OnSuccess(t =>
                {
                    //OnMessageLoad(t.Result);
                    return t.Result;
                });
        }

        /// <summary>
        /// Gets the message query.
        /// </summary>
        /// <returns>The message query.</returns>
        public AVIMMessageQuery GetMessageQuery()
        {
            return new AVIMMessageQuery(this);
        }

        /// <summary>
        /// Gets the message pager.
        /// </summary>
        /// <returns>The message pager.</returns>
        public AVIMMessagePager GetMessagePager()
        {
            return new AVIMMessagePager(this);
        }

        #endregion

        #region 字典与对象之间的转换
        internal virtual void MergeMagicFields(IDictionary<String, Object> data)
        {
            lock (this.mutex)
            {
                if (data.ContainsKey("objectId"))
                {
                    this.ConversationId = (data["objectId"] as String);
                    data.Remove("objectId");
                }
                if (data.ContainsKey("createdAt"))
                {
                    this.CreatedAt = AVDecoder.ParseDate(data["createdAt"] as string);
                    data.Remove("createdAt");
                }
                if (data.ContainsKey("updatedAt"))
                {
                    this.updatedAt = AVDecoder.ParseDate(data["updatedAt"] as string);
                    data.Remove("updatedAt");
                }
                if (data.ContainsKey("name"))
                {
                    this.Name = (data["name"] as String);
                    data.Remove("name");
                }
                if (data.ContainsKey("lm"))
                {
                    this.LastMessageAt = AVDecoder.Instance.Decode(data["lm"]) as DateTime?;
                    data.Remove("lm");
                }
                if (data.ContainsKey("m"))
                {
                    this.MemberIds = AVDecoder.Instance.DecodeList<string>(data["m"]);
                    data.Remove("m");
                }
                if (data.ContainsKey("mu"))
                {
                    this.MuteMemberIds = AVDecoder.Instance.DecodeList<string>(data["mu"]);
                    data.Remove("mu");
                }
                if (data.ContainsKey("c"))
                {
                    this.Creator = data["c"].ToString();
                    data.Remove("c");
                }
                if (data.ContainsKey("unique"))
                {
                    if (data["unique"] is bool)
                    {
                        this.IsUnique = (bool)data["unique"];
                    }
                    data.Remove("unique");
                }
                foreach (var kv in data)
                {
                    this[kv.Key] = kv.Value;
                }
            }
        }
        #endregion

        #region SyncStateAsync & unread & mark as read
        /// <summary>
        /// sync state from server.suhc unread state .etc;
        /// </summary>
        /// <returns></returns>
        public Task<AggregatedState> SyncStateAsync()
        {
            lock (mutex)
            {
                var rtn = new AggregatedState();
                rtn.Unread = GetUnreadStateFromLocal();
                return Task.FromResult(rtn);
            }
        }

        private UnreadState _unread;
        private UnreadState _lastUnreadWhenOpenSession;
        public UnreadState Unread
        {
            get
            {
                _lastUnreadWhenOpenSession = GetUnreadStateFromLocal();

                // v.2 协议，只给出上次离线之后的未读消息，本次在线的收到的消息均视为已读
                if (this.CurrentClient.LinkedRealtime.CurrentConfiguration.OfflineMessageStrategy == AVRealtime.OfflineMessageStrategy.UnreadNotice)
                {
                    _unread = _lastUnreadWhenOpenSession;
                }
                else if (this.CurrentClient.LinkedRealtime.CurrentConfiguration.OfflineMessageStrategy == AVRealtime.OfflineMessageStrategy.UnreadAck)
                {
                    if (_lastUnreadWhenOpenSession == null) _unread = new UnreadState().MergeReceived(this.Received);
                    else _unread = _lastUnreadWhenOpenSession.MergeReceived(this.Received);
                }

                return _unread;
            }

            internal set
            {
                _unread = value;
            }
        }

        private readonly object receivedMutex = new object();
        public ReceivedState Received
        {
            get; set;
        }
        public ReadState Read
        {
            get; set;
        }

        UnreadState GetUnreadStateFromLocal()
        {
            lock (mutex)
            {
                var notice = ConversationUnreadListener.Get(this.ConversationId);
                if (notice != null)
                {
                    var unreadState = new UnreadState()
                    {
                        LastMessage = notice.LastUnreadMessage,
                        SyncdAt = ConversationUnreadListener.NotifTime,
                        Count = notice.UnreadCount
                    };
                    return unreadState;
                }

                return null;
            }
        }

        internal void OnMessageLoad(IEnumerable<IAVIMMessage> messages)
        {
            var lastestInCollection = messages.OrderByDescending(m => m.ServerTimestamp).FirstOrDefault();
            if (lastestInCollection != null)
            {
                if (CurrentClient.CurrentConfiguration.AutoRead)
                {
                    this.ReadAsync(lastestInCollection);
                }
            }
        }

        /// <summary>
        /// mark this conversation as read
        /// </summary>
        /// <returns></returns>
        public Task ReadAsync(IAVIMMessage message = null, DateTime? readAt = null)
        {
            // 标记已读必须至少是从上一次离线产生的最后一条消息开始，否则无法计算 Count
            if (_lastUnreadWhenOpenSession != null)
            {
                if (_lastUnreadWhenOpenSession.LastMessage != null)
                {
                    message = _lastUnreadWhenOpenSession.LastMessage;
                }
            }
            return this.CurrentClient.ReadAsync(this, message, readAt).OnSuccess(t =>
            {
                Received = null;
                _lastUnreadWhenOpenSession = null;
                Read = new ReadState()
                {
                    ReadAt = readAt != null ? readAt.Value.ToUnixTimeStamp() : 0,
                    LastMessage = message,
                    SyncdAt = DateTime.Now.ToUnixTimeStamp()
                };

            });
        }

        /// <summary>
        /// aggregated state for the conversation
        /// </summary>
        public class AggregatedState
        {
            /// <summary>
            /// Unread state
            /// </summary>
            public UnreadState Unread { get; internal set; }
        }

        /// <summary>
        /// UnreadState recoder for the conversation
        /// </summary>
        public class UnreadState
        {
            /// <summary>
            /// unread count
            /// </summary>
            public int Count { get; internal set; }
            /// <summary>
            /// last unread message
            /// </summary>
            public IAVIMMessage LastMessage { get; internal set; }

            /// <summary>
            /// last sync timestamp
            /// </summary>
            public long SyncdAt { get; internal set; }

            internal UnreadState MergeReceived(ReceivedState receivedState)
            {
                if (receivedState == null) return this;
                var count = Count + receivedState.Count;
                var lastMessage = this.LastMessage;
                if (receivedState.LastMessage != null)
                {
                    lastMessage = receivedState.LastMessage;
                }
                var syncdAt = this.SyncdAt > receivedState.SyncdAt ? this.SyncdAt : receivedState.SyncdAt;
                return new UnreadState()
                {
                    Count = count,
                    LastMessage = lastMessage,
                    SyncdAt = syncdAt
                };
            }
        }

        public class ReceivedState
        {
            public int Count { get; internal set; }
            /// <summary>
            /// last received message
            /// </summary>
            public IAVIMMessage LastMessage { get; internal set; }

            /// <summary>
            /// last sync timestamp
            /// </summary>
            public long SyncdAt { get; internal set; }
        }

        public class ReadState
        {
            public long ReadAt { get; set; }
            public IAVIMMessage LastMessage { get; internal set; }
            public long SyncdAt { get; internal set; }
        }

        #endregion

        #region on client message received to update unread
        private void CurrentClient_OnMessageReceived(object sender, AVIMMessageEventArgs e)
        {
            if (this.CurrentClient.CurrentConfiguration.AutoRead)
            {
                this.ReadAsync(e.Message);
                return;
            }
            lock (receivedMutex)
            {
                if (this.Received == null) this.Received = new ReceivedState();
                this.Received.Count++;
                this.Received.LastMessage = e.Message;
                this.Received.SyncdAt = DateTime.Now.ToUnixTimeStamp();
            }
        }
        #endregion
    }

    /// <summary>
    /// AVIMConversation extensions.
    /// </summary>
    public static class AVIMConversationExtensions
    {

        /// <summary>
        /// Send message async.
        /// </summary>
        /// <returns>The async.</returns>
        /// <param name="conversation">Conversation.</param>
        /// <param name="message">Message.</param>
        /// <param name="options">Options.</param>
        public static Task SendAsync(this AVIMConversation conversation, IAVIMMessage message, AVIMSendOptions options)
        {
            return conversation.SendMessageAsync(message, options);
        }

        /// <summary>
        /// Send message async.
        /// </summary>
        /// <returns>The async.</returns>
        /// <param name="conversation">Conversation.</param>
        /// <param name="message">Message.</param>
        /// <param name="options">Options.</param>
        /// <typeparam name="T">The 1st type parameter.</typeparam>
        public static Task<T> SendAsync<T>(this AVIMConversation conversation, T message, AVIMSendOptions options)
            where T : IAVIMMessage
        {
            return conversation.SendMessageAsync(message, options).OnSuccess(t =>
            {
                return (T)t.Result;
            });
        }

        /// <summary>
        /// Send message async.
        /// </summary>
        /// <returns>The async.</returns>
        /// <param name="conversation">Conversation.</param>
        /// <param name="message">Message.</param>
        /// <typeparam name="T">The 1st type parameter.</typeparam>
        public static Task<T> SendAsync<T>(this AVIMConversation conversation, T message)
            where T : IAVIMMessage
        {
            return conversation.SendMessageAsync(message).OnSuccess(t =>
            {
                return (T)t.Result;
            });
        }

        /// <summary>
        /// Send text message.
        /// </summary>
        /// <returns>The text async.</returns>
        /// <param name="conversation">Conversation.</param>
        /// <param name="text">Text.</param>
        public static Task<AVIMTextMessage> SendTextAsync(this AVIMConversation conversation, string text)
        {
            return conversation.SendAsync<AVIMTextMessage>(new AVIMTextMessage(text));
        }

        #region Image messages

        /// <summary>
        /// Send image message async.
        /// </summary>
        /// <returns>The image async.</returns>
        /// <param name="conversation">Conversation.</param>
        /// <param name="url">URL.</param>
        /// <param name="name">Name.</param>
        /// <param name="textTitle">Text title.</param>
        /// <param name="customAttributes">Custom attributes.</param>
        public static Task<AVIMImageMessage> SendImageAsync(this AVIMConversation conversation, string url, string name = null, string textTitle = null, IDictionary<string, object> customAttributes = null)
        {
            return conversation.SendFileMessageAsync<AVIMImageMessage>(url, name, textTitle, customAttributes);
        }

        /// <summary>
        /// Send image message async.
        /// </summary>
        /// <returns>The image async.</returns>
        /// <param name="conversation">Conversation.</param>
        /// <param name="fileName">File name.</param>
        /// <param name="data">Data.</param>
        /// <param name="mimeType">MIME type.</param>
        /// <param name="textTitle">Text title.</param>
        /// <param name="metaData">Meta data.</param>
        /// <param name="customAttributes">Custom attributes.</param>
        public static Task<AVIMImageMessage> SendImageAsync(this AVIMConversation conversation, string fileName, Stream data, string mimeType = null, string textTitle = null, IDictionary<string, object> metaData = null, IDictionary<string, object> customAttributes = null)
        {
            return conversation.SendFileMessageAsync<AVIMImageMessage>(fileName, data, mimeType, textTitle, metaData, customAttributes);
        }

        /// <summary>
        /// Send image message async.
        /// </summary>
        /// <returns>The image async.</returns>
        /// <param name="conversation">Conversation.</param>
        /// <param name="fileName">File name.</param>
        /// <param name="data">Data.</param>
        /// <param name="mimeType">MIME type.</param>
        /// <param name="textTitle">Text title.</param>
        /// <param name="metaData">Meta data.</param>
        /// <param name="customAttributes">Custom attributes.</param>
        public static Task<AVIMImageMessage> SendImageAsync(this AVIMConversation conversation, string fileName, byte[] data, string mimeType = null, string textTitle = null, IDictionary<string, object> metaData = null, IDictionary<string, object> customAttributes = null)
        {
            return conversation.SendFileMessageAsync<AVIMImageMessage>(fileName, data, mimeType, textTitle, metaData, customAttributes);
        }
        #endregion

        #region audio message

        /// <summary>
        /// Send audio message async.
        /// </summary>
        /// <returns>The audio async.</returns>
        /// <param name="conversation">Conversation.</param>
        /// <param name="url">URL.</param>
        /// <param name="name">Name.</param>
        /// <param name="textTitle">Text title.</param>
        /// <param name="customAttributes">Custom attributes.</param>
        public static Task<AVIMAudioMessage> SendAudioAsync(this AVIMConversation conversation, string url, string name = null, string textTitle = null, IDictionary<string, object> customAttributes = null)
        {
            return conversation.SendFileMessageAsync<AVIMAudioMessage>(name, url, textTitle, customAttributes);
        }

        /// <summary>
        /// Send audio message async.
        /// </summary>
        /// <returns>The audio async.</returns>
        /// <param name="conversation">Conversation.</param>
        /// <param name="fileName">File name.</param>
        /// <param name="data">Data.</param>
        /// <param name="mimeType">MIME type.</param>
        /// <param name="textTitle">Text title.</param>
        /// <param name="metaData">Meta data.</param>
        /// <param name="customAttributes">Custom attributes.</param>
        public static Task<AVIMAudioMessage> SendAudioAsync(this AVIMConversation conversation, string fileName, Stream data, string mimeType = null, string textTitle = null, IDictionary<string, object> metaData = null, IDictionary<string, object> customAttributes = null)
        {
            return conversation.SendFileMessageAsync<AVIMAudioMessage>(fileName, data, mimeType, textTitle, metaData, customAttributes);
        }

        /// <summary>
        /// Send audio message async.
        /// </summary>
        /// <returns>The audio async.</returns>
        /// <param name="conversation">Conversation.</param>
        /// <param name="fileName">File name.</param>
        /// <param name="data">Data.</param>
        /// <param name="mimeType">MIME type.</param>
        /// <param name="textTitle">Text title.</param>
        /// <param name="metaData">Meta data.</param>
        /// <param name="customAttributes">Custom attributes.</param>
        public static Task<AVIMAudioMessage> SendAudioAsync(this AVIMConversation conversation, string fileName, byte[] data, string mimeType = null, string textTitle = null, IDictionary<string, object> metaData = null, IDictionary<string, object> customAttributes = null)
        {
            return conversation.SendFileMessageAsync<AVIMAudioMessage>(fileName, data, mimeType, textTitle, metaData, customAttributes);
        }
        #endregion

        #region video message
        /// <summary>
        /// Send video message async.
        /// </summary>
        /// <returns>The video async.</returns>
        /// <param name="conversation">Conversation.</param>
        /// <param name="url">URL.</param>
        /// <param name="name">Name.</param>
        /// <param name="textTitle">Text title.</param>
        /// <param name="customAttributes">Custom attributes.</param>
        public static Task<AVIMVideoMessage> SendVideoAsync(this AVIMConversation conversation, string url, string name = null, string textTitle = null, IDictionary<string, object> customAttributes = null)
        {
            return conversation.SendFileMessageAsync<AVIMVideoMessage>(name, url, textTitle, customAttributes);
        }
        /// <summary>
        /// Send video message async.
        /// </summary>
        /// <returns>The video async.</returns>
        /// <param name="conversation">Conversation.</param>
        /// <param name="fileName">File name.</param>
        /// <param name="data">Data.</param>
        /// <param name="mimeType">MIME type.</param>
        /// <param name="textTitle">Text title.</param>
        /// <param name="metaData">Meta data.</param>
        /// <param name="customAttributes">Custom attributes.</param>
        public static Task<AVIMVideoMessage> SendVideoAsync(this AVIMConversation conversation, string fileName, Stream data, string mimeType = null, string textTitle = null, IDictionary<string, object> metaData = null, IDictionary<string, object> customAttributes = null)
        {
            return conversation.SendFileMessageAsync<AVIMVideoMessage>(fileName, data, mimeType, textTitle, metaData, customAttributes);
        }

        /// <summary>
        /// Send video message async.
        /// </summary>
        /// <returns>The video async.</returns>
        /// <param name="conversation">Conversation.</param>
        /// <param name="fileName">File name.</param>
        /// <param name="data">Data.</param>
        /// <param name="mimeType">MIME type.</param>
        /// <param name="textTitle">Text title.</param>
        /// <param name="metaData">Meta data.</param>
        /// <param name="customAttributes">Custom attributes.</param>
        public static Task<AVIMVideoMessage> SendVideoAsync(this AVIMConversation conversation, string fileName, byte[] data, string mimeType = null, string textTitle = null, IDictionary<string, object> metaData = null, IDictionary<string, object> customAttributes = null)
        {
            return conversation.SendFileMessageAsync<AVIMVideoMessage>(fileName, data, mimeType, textTitle, metaData, customAttributes);
        }
        #endregion

        /// <summary>
        /// Send file message async.
        /// </summary>
        /// <returns>The file message async.</returns>
        /// <param name="conversation">Conversation.</param>
        /// <param name="url">URL.</param>
        /// <param name="name">Name.</param>
        /// <param name="textTitle">Text title.</param>
        /// <param name="customAttributes">Custom attributes.</param>
        /// <typeparam name="T">The 1st type parameter.</typeparam>
        public static Task<T> SendFileMessageAsync<T>(this AVIMConversation conversation, string url, string name = null, string textTitle = null, IDictionary<string, object> customAttributes = null)
            where T : AVIMFileMessage, new()
        {
            var fileMessage = AVIMFileMessage.FromUrl<T>(name, url, textTitle, customAttributes);
            return conversation.SendAsync<T>(fileMessage);
        }

        /// <summary>
        /// Send file message async.
        /// </summary>
        /// <returns>The file message async.</returns>
        /// <param name="conversation">Conversation.</param>
        /// <param name="fileName">File name.</param>
        /// <param name="data">Data.</param>
        /// <param name="mimeType">MIME type.</param>
        /// <param name="textTitle">Text title.</param>
        /// <param name="metaData">Meta data.</param>
        /// <param name="customAttributes">Custom attributes.</param>
        /// <typeparam name="T">The 1st type parameter.</typeparam>
        public static Task<T> SendFileMessageAsync<T>(this AVIMConversation conversation, string fileName, Stream data, string mimeType = null, string textTitle = null, IDictionary<string, object> metaData = null, IDictionary<string, object> customAttributes = null)
            where T : AVIMFileMessage, new()
        {
            var fileMessage = AVIMFileMessage.FromStream<T>(fileName, data, mimeType, textTitle, metaData, customAttributes);

            return fileMessage.File.SaveAsync().OnSuccess(fileUploaded =>
            {
                return conversation.SendAsync<T>(fileMessage);
            }).Unwrap();
        }

        /// <summary>
        /// Send file message async.
        /// </summary>
        /// <returns>The file message async.</returns>
        /// <param name="conversation">Conversation.</param>
        /// <param name="fileName">File name.</param>
        /// <param name="data">Data.</param>
        /// <param name="mimeType">MIME type.</param>
        /// <param name="textTitle">Text title.</param>
        /// <param name="metaData">Meta data.</param>
        /// <param name="customAttributes">Custom attributes.</param>
        /// <typeparam name="T">The 1st type parameter.</typeparam>
        public static Task<T> SendFileMessageAsync<T>(this AVIMConversation conversation, string fileName, byte[] data, string mimeType = null, string textTitle = null, IDictionary<string, object> metaData = null, IDictionary<string, object> customAttributes = null)
            where T : AVIMFileMessage, new()
        {
            var fileMessage = AVIMFileMessage.FromStream<T>(fileName, new MemoryStream(data), mimeType, textTitle, metaData, customAttributes);

            return conversation.SendFileMessageAsync<T>(fileName, new MemoryStream(data), mimeType, textTitle, metaData, customAttributes);
        }

        /// <summary>
        /// Send location async.
        /// </summary>
        /// <returns>The location async.</returns>
        /// <param name="conversation">Conversation.</param>
        /// <param name="point">Point.</param>
        public static Task SendLocationAsync(this AVIMConversation conversation, AVGeoPoint point)
        {
            var locationMessage = new AVIMLocationMessage(point);
            return conversation.SendAsync<AVIMLocationMessage>(locationMessage);
        }

        /// <summary>
        /// Query the message async.
        /// </summary>
        /// <returns>The message async.</returns>
        /// <param name="conversation">Conversation.</param>
        /// <param name="beforeMessageId">Before message identifier.</param>
        /// <param name="afterMessageId">After message identifier.</param>
        /// <param name="beforeTimeStamp">Before time stamp.</param>
        /// <param name="afterTimeStamp">After time stamp.</param>
        /// <param name="direction">Direction.</param>
        /// <param name="limit">Limit.</param>
        public static Task<IEnumerable<IAVIMMessage>> QueryMessageAsync(
            this AVIMConversation conversation,
            string beforeMessageId = null,
            string afterMessageId = null,
            long? beforeTimeStamp = null,
            long? afterTimeStamp = null,
            int direction = 1,
            int limit = 20)
        {

            return conversation.QueryMessageAsync<IAVIMMessage>(beforeMessageId,
                                                  afterMessageId,
                                                  beforeTimeStamp,
                                                  afterTimeStamp,
                                                  direction,
                                                  limit);
        }

        /// <summary>
        /// Query message with speciafic subclassing type.
        /// </summary>
        /// <returns>The message async.</returns>
        /// <param name="conversation">Conversation.</param>
        /// <param name="beforeMessageId">Before message identifier.</param>
        /// <param name="afterMessageId">After message identifier.</param>
        /// <param name="beforeTimeStamp">Before time stamp.</param>
        /// <param name="afterTimeStamp">After time stamp.</param>
        /// <param name="direction">Direction.</param>
        /// <param name="limit">Limit.</param>
        /// <typeparam name="T">The 1st type parameter.</typeparam>
        public static Task<IEnumerable<T>> QueryMessageAsync<T>(
            this AVIMConversation conversation,
            string beforeMessageId = null,
            string afterMessageId = null,
            long? beforeTimeStamp = null,
            long? afterTimeStamp = null,
            int direction = 1,
            int limit = 20)
            where T : IAVIMMessage
        {

            return conversation.QueryMessageAsync<T>(beforeMessageId,
                                                     afterMessageId,
                                                     beforeTimeStampPoint: beforeTimeStamp.HasValue ? beforeTimeStamp.Value.ToDateTime() : DateTime.MinValue,
                                                     afterTimeStampPoint: afterTimeStamp.HasValue ? afterTimeStamp.Value.ToDateTime() : DateTime.MinValue,
                                                     direction: direction,
                                                     limit: limit);
        }

        /// <summary>
        /// Query message record before the given message async.
        /// </summary>
        /// <returns>The message before async.</returns>
        /// <param name="conversation">Conversation.</param>
        /// <param name="message">Message.</param>
        /// <param name="limit">Limit.</param>
        public static Task<IEnumerable<IAVIMMessage>> QueryMessageBeforeAsync(
            this AVIMConversation conversation,
            IAVIMMessage message,
            int limit = 20)
        {
            return conversation.QueryMessageAsync(beforeMessageId: message.Id, beforeTimeStamp: message.ServerTimestamp, limit: limit);
        }

        /// <summary>
        /// Query message record after the given message async.
        /// </summary>
        /// <returns>The message after async.</returns>
        /// <param name="conversation">Conversation.</param>
        /// <param name="message">Message.</param>
        /// <param name="limit">Limit.</param>
        public static Task<IEnumerable<IAVIMMessage>> QueryMessageAfterAsync(
            this AVIMConversation conversation,
            IAVIMMessage message,
            int limit = 20)
        {
            return conversation.QueryMessageAsync(afterMessageId: message.Id, afterTimeStamp: message.ServerTimestamp, limit: limit);
        }

        /// <summary>
        /// Query messages after conversation created.
        /// </summary>
        /// <returns>The message after async.</returns>
        /// <param name="conversation">Conversation.</param>
        /// <param name="limit">Limit.</param>
        public static Task<IEnumerable<IAVIMMessage>> QueryMessageFromOldToNewAsync(
            this AVIMConversation conversation,
            int limit = 20)
        {
            return conversation.QueryMessageAsync(afterTimeStamp: conversation.CreatedAt.Value.ToUnixTimeStamp(), limit: limit);
        }


        /// <summary>
        /// Query messages in interval async.
        /// </summary>
        /// <returns>The message interval async.</returns>
        /// <param name="conversation">Conversation.</param>
        /// <param name="start">Start.</param>
        /// <param name="end">End.</param>
        /// <param name="limit">Limit.</param>
        public static Task<IEnumerable<IAVIMMessage>> QueryMessageInIntervalAsync(
            this AVIMConversation conversation,
            IAVIMMessage start,
            IAVIMMessage end,
            int limit = 20)
        {
            return conversation.QueryMessageAsync(
                beforeMessageId: end.Id,
                beforeTimeStamp: end.ServerTimestamp,
                afterMessageId: start.Id,
                afterTimeStamp: start.ServerTimestamp,
                limit: limit);
        }

        /// <summary>
        /// Recall message async.
        /// </summary>
        /// <returns>The async.</returns>
        /// <param name="conversation">Conversation.</param>
        /// <param name="message">Message.</param>
        public static Task<AVIMRecalledMessage> RecallAsync(this AVIMConversation conversation, IAVIMMessage message)
        {
            return conversation.CurrentClient.RecallAsync(message);
        }

        /// <summary>
        /// Modifiy message async.
        /// </summary>
        /// <returns>The async.</returns>
        /// <param name="conversation">Conversation.</param>
        /// <param name="oldMessage">要修改的消息对象</param>
        /// <param name="newMessage">新的消息对象</param>
        public static Task<IAVIMMessage> UpdateAsync(this AVIMConversation conversation, IAVIMMessage oldMessage, IAVIMMessage newMessage)
        {
            return conversation.CurrentClient.UpdateAsync(oldMessage, newMessage);
        }
    }

    /// <summary>
    /// AVIMConversatio builder.
    /// </summary>
    public interface IAVIMConversatioBuilder
    {
        /// <summary>
        /// Build this instance.
        /// </summary>
        /// <returns>The build.</returns>
        AVIMConversation Build();
    }

    /// <summary>
    /// AVIMConversation builder.
    /// </summary>
    public class AVIMConversationBuilder : IAVIMConversatioBuilder
    {
        /// <summary>
        /// Gets or sets the client.
        /// </summary>
        /// <value>The client.</value>
        public AVIMClient Client { get; internal set; }
        private bool isUnique;
        private Dictionary<string, object> properties;

        private string name;
        private bool isTransient;
        private bool isSystem;
        private List<string> members;


        internal static AVIMConversationBuilder CreateDefaultBuilder()
        {
            return new AVIMConversationBuilder();
        }

        /// <summary>
        /// Build this instance.
        /// </summary>
        /// <returns>The build.</returns>
        public AVIMConversation Build()
        {
            var result = new AVIMConversation(
                members: members,
                name: name,
                isUnique: isUnique,
                isSystem: isSystem,
                isTransient: isTransient,
                client: Client);

            if (properties != null)
            {
                foreach (var key in properties.Keys)
                {
                    result[key] = properties[key];
                }
            }

            return result;
        }

        /// <summary>
        /// Sets the unique.
        /// </summary>
        /// <returns>The unique.</returns>
        /// <param name="toggle">If set to <c>true</c> toggle.</param>
        public AVIMConversationBuilder SetUnique(bool toggle = true)
        {
            this.isUnique = toggle;
            return this;
        }

        /// <summary>
        /// Sets the system.
        /// </summary>
        /// <returns>The system.</returns>
        /// <param name="toggle">If set to <c>true</c> toggle.</param>
        public AVIMConversationBuilder SetSystem(bool toggle = true)
        {
            this.isSystem = toggle;
            return this;
        }

        /// <summary>
        /// Sets the transient.
        /// </summary>
        /// <returns>The transient.</returns>
        /// <param name="toggle">If set to <c>true</c> toggle.</param>
        public AVIMConversationBuilder SetTransient(bool toggle = true)
        {
            this.isTransient = toggle;
            return this;
        }

        /// <summary>
        /// Sets the name.
        /// </summary>
        /// <returns>The name.</returns>
        /// <param name="name">Name.</param>
        public AVIMConversationBuilder SetName(string name)
        {
            this.name = name;
            return this;
        }

        /// <summary>
        /// Sets the members.
        /// </summary>
        /// <returns>The members.</returns>
        /// <param name="memberClientIds">Member client identifiers.</param>
        public AVIMConversationBuilder SetMembers(IEnumerable<string> memberClientIds)
        {
            this.members = memberClientIds.ToList();
            return this;
        }

        /// <summary>
        /// Adds the member.
        /// </summary>
        /// <returns>The member.</returns>
        /// <param name="memberClientId">Member client identifier.</param>
        public AVIMConversationBuilder AddMember(string memberClientId)
        {
            return AddMembers(new string[] { memberClientId });
        }

        /// <summary>
        /// Adds the members.
        /// </summary>
        /// <returns>The members.</returns>
        /// <param name="memberClientIds">Member client identifiers.</param>
        public AVIMConversationBuilder AddMembers(IEnumerable<string> memberClientIds)
        {
            if (this.members == null) this.members = new List<string>();
            this.members.AddRange(memberClientIds);
            return this;
        }

        /// <summary>
        /// Sets the property.
        /// </summary>
        /// <returns>The property.</returns>
        /// <param name="key">Key.</param>
        /// <param name="value">Value.</param>
        public AVIMConversationBuilder SetProperty(string key, object value)
        {
            if (this.properties == null) this.properties = new Dictionary<string, object>();
            this.properties[key] = value;
            return this;
        }
    }

    /// <summary>
    /// AVIMM essage emitter builder.
    /// </summary>
    public class AVIMMessageEmitterBuilder
    {
        private AVIMConversation _conversation;
        /// <summary>
        /// Sets the conversation.
        /// </summary>
        /// <returns>The conversation.</returns>
        /// <param name="conversation">Conversation.</param>
        public AVIMMessageEmitterBuilder SetConversation(AVIMConversation conversation)
        {
            _conversation = conversation;
            return this;
        }
        /// <summary>
        /// Gets the conversation.
        /// </summary>
        /// <value>The conversation.</value>
        public AVIMConversation Conversation
        {
            get
            {
                return _conversation;
            }
        }

        private AVIMSendOptions _sendOptions;
        /// <summary>
        /// Gets the send options.
        /// </summary>
        /// <value>The send options.</value>
        public AVIMSendOptions SendOptions
        {
            get
            {
                return _sendOptions;
            }
        }
        /// <summary>
        /// Sets the send options.
        /// </summary>
        /// <returns>The send options.</returns>
        /// <param name="sendOptions">Send options.</param>
        public AVIMMessageEmitterBuilder SetSendOptions(AVIMSendOptions sendOptions)
        {
            _sendOptions = sendOptions;
            return this;
        }

        private IAVIMMessage _message;
        /// <summary>
        /// Gets the message.
        /// </summary>
        /// <value>The message.</value>
        public IAVIMMessage Message
        {
            get
            {
                return _message;
            }
        }
        /// <summary>
        /// Sets the message.
        /// </summary>
        /// <returns>The message.</returns>
        /// <param name="message">Message.</param>
        public AVIMMessageEmitterBuilder SetMessage(IAVIMMessage message)
        {
            _message = message;
            return this;
        }

        /// <summary>
        /// Send async.
        /// </summary>
        /// <returns>The async.</returns>
        public Task SendAsync()
        {
            return this.Conversation.SendAsync(this.Message, this.SendOptions);
        }
    }
}
