// ----------------------------------------------------------------------------------------------------------------------
// The Photon Chat Api enables clients to connect to a chat server and communicate with other clients. 
// ChatClient is the main class of this api. 
// Photon Chat Api - Copyright (C) 2014 Exit Games GmbH 
// ----------------------------------------------------------------------------------------------------------------------
#if UNITY_4_7 || UNITY_5 || UNITY_5_3_OR_NEWER
#define SUPPORTED_UNITY
#endif
namespace Photon.Chat
{
    using System.Collections.Generic;
    using System.Text;
    #if SUPPORTED_UNITY || NETFX_CORE
    using Hashtable = ExitGames.Client.Photon.Hashtable;
    using SupportClass = ExitGames.Client.Photon.SupportClass;
    #endif
    /// 
    /// A channel of communication in Photon Chat, updated by ChatClient and provided as READ ONLY.
    ///  
    /// 
    /// Contains messages and senders to use (read!) and display by your GUI.
    /// Access these by:
    ///     ChatClient.PublicChannels
    ///     ChatClient.PrivateChannels
    ///  
    public class ChatChannel
    {
        /// Name of the channel (used to subscribe and unsubscribe). 
        public readonly string Name;
        /// Senders of messages in chronological order. Senders and Messages refer to each other by index. Senders[x] is the sender of Messages[x]. 
        public readonly List Senders = new List();
        /// Messages in chronological order. Senders and Messages refer to each other by index. Senders[x] is the sender of Messages[x]. 
        public readonly List Messages = new List();
        /// If greater than 0, this channel will limit the number of messages, that it caches locally. 
        public int MessageLimit;
        /// Unique channel ID. 
        public int ChannelID;
        /// Is this a private 1:1 channel? 
        public bool IsPrivate { get; protected internal set; }
        /// Count of messages this client still buffers/knows for this channel. 
        public int MessageCount { get { return this.Messages.Count; } }
        /// 
        /// ID of the last message received.
        ///  
        public int LastMsgId { get; protected set; }
        private Dictionary properties;
        /// Whether or not this channel keeps track of the list of its subscribers. 
        public bool PublishSubscribers { get; protected set; }
        /// Maximum number of channel subscribers. 0 means infinite. 
        public int MaxSubscribers { get; protected set; }
        /// Subscribed users. 
        public readonly HashSet Subscribers = new HashSet();
        ///  Properties of subscribers  
        private Dictionary> usersProperties;
        /// Used internally to create new channels. This does NOT create a channel on the server! Use ChatClient.Subscribe. 
        public ChatChannel(string name)
        {
            this.Name = name;
        }
        
        /// Used internally to add messages to this channel. 
        public void Add(string sender, object message, int msgId)
        {
            this.Senders.Add(sender);
            this.Messages.Add(message);
            this.LastMsgId = msgId;
            this.TruncateMessages();
        }
        /// Used internally to add messages to this channel. 
        public void Add(string[] senders, object[] messages, int lastMsgId)
        {
            this.Senders.AddRange(senders);
            this.Messages.AddRange(messages);
            this.LastMsgId = lastMsgId;
            this.TruncateMessages();
        }
        /// Reduces the number of locally cached messages in this channel to the MessageLimit (if set). 
        public void TruncateMessages()
        {
            if (this.MessageLimit <= 0 || this.Messages.Count <= this.MessageLimit)
            {
                return;
            }
            int excessCount = this.Messages.Count - this.MessageLimit;
            this.Senders.RemoveRange(0, excessCount);
            this.Messages.RemoveRange(0, excessCount);
        }
        /// Clear the local cache of messages currently stored. This frees memory but doesn't affect the server. 
        public void ClearMessages()
        {
            this.Senders.Clear();
            this.Messages.Clear();
        }
        /// Provides a string-representation of all messages in this channel. 
        /// All known messages in format "Sender: Message", line by line. 
        public string ToStringMessages()
        {
            StringBuilder txt = new StringBuilder();
            for (int i = 0; i < this.Messages.Count; i++)
            {
                txt.AppendLine(string.Format("{0}: {1}", this.Senders[i], this.Messages[i]));
            }
            return txt.ToString();
        }
        internal void ReadChannelProperties(Dictionary newProperties)
        {
            if (newProperties != null && newProperties.Count > 0)
            {
                if (this.properties == null)
                {
                    this.properties = new Dictionary(newProperties.Count);
                }
                foreach (var pair in newProperties)
                {
                    if (pair.Value == null)
                    {
                        this.properties.Remove(pair.Key);
                    }
                    else
                    {
                        this.properties[pair.Key] = pair.Value;
                    }
                }
                object temp;
                if (this.properties.TryGetValue(ChannelWellKnownProperties.PublishSubscribers, out temp))
                {
                    this.PublishSubscribers = (bool)temp;
                }
                if (this.properties.TryGetValue(ChannelWellKnownProperties.MaxSubscribers, out temp))
                {
                    this.MaxSubscribers = (int)temp;
                }
            }
        }
        internal bool AddSubscribers(string[] users)
        {
            if (users == null)
            {
                return false;
            }
            bool result = true;
            for (int i = 0; i < users.Length; i++)
            {
                if (!this.Subscribers.Add(users[i]))
                {
                    result = false;
                }
            }
            return result;
        }
        internal bool AddSubscriber(string userId)
        {
            return this.Subscribers.Add(userId);
        }
        internal bool RemoveSubscriber(string userId)
        {
            if (this.usersProperties != null)
            {
                this.usersProperties.Remove(userId);
            }
            return this.Subscribers.Remove(userId);
        }
        #if CHAT_EXTENDED
        internal void ReadUserProperties(string userId, Dictionary changedProperties)
        {
            if (this.usersProperties == null)
            {
                this.usersProperties = new Dictionary>();
            }
            Dictionary userProperties;
            if (!this.usersProperties.TryGetValue(userId, out userProperties)) 
            {
                userProperties = new Dictionary();
                this.usersProperties.Add(userId, userProperties);
            }
            foreach (var property in changedProperties)
            {
                //UnityEngine.Debug.LogFormat("User {0} property {1} = {2}", userId, property.Key, property.Value);
                if (property.Value == null)
                {
                    userProperties.Remove(property.Key);
                }
                else
                {
                    userProperties[property.Key] = property.Value;
                }
            }
        }
        
        internal bool TryGetChannelProperty(object propertyKey, out T propertyValue)
        {
            propertyValue = default;
            object temp;
            if (properties != null && properties.TryGetValue(propertyKey, out temp) && temp is T)
            {
                propertyValue = (T)temp;
                return true;
            }
            return false;
        }
        internal bool TryGetUserProperty(string userId, object propertyKey, out T propertyValue)
        {
            propertyValue = default;
            object temp;
            Dictionary userProperties;
            if (this.usersProperties != null && usersProperties.TryGetValue(userId, out userProperties) && userProperties.TryGetValue(propertyKey, out temp) && temp is T)
            {
                propertyValue = (T)temp;
                return true;
            }
            return false;
        }
        public bool TryGetCustomChannelProperty(string propertyKey, out T propertyValue)
        {
            return this.TryGetChannelProperty(propertyKey, out propertyValue);
        }
        public bool TryGetCustomUserProperty(string userId, string propertyKey, out T propertyValue)
        {
            return this.TryGetUserProperty(userId, propertyKey, out propertyValue);
        }
        #endif
    }
}