using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using FLocal.Core;
using FLocal.Core.DB;
using FLocal.Common;
using FLocal.Common.actions;

namespace FLocal.Common.dataobjects {
	public class PMMessage : SqlObject<PMMessage> {

		public const string ENUM_DIRECTION_INCOMING = "Incoming";
		public const string ENUM_DIRECTION_OUTGOING = "Outgoing";

		public class TableSpec : ISqlObjectTableSpec {
			public const string TABLE = "PMMessages";
			public const string FIELD_ID = "Id";
			public const string FIELD_OWNERID = "OwnerId";
			public const string FIELD_INTERLOCUTORID = "InterlocutorId";
			public const string FIELD_DIRECTION = "Direction";
			public const string FIELD_POSTDATE = "PostDate";
			public const string FIELD_TITLE = "Title";
			public const string FIELD_BODY = "Body";
			public const string FIELD_BODYUBB = "BodyUBB";
			public const string FIELD_INCOMINGPMID = "IncomingPMId";
			public const string FIELD_ISREAD = "IsRead";
			public static readonly TableSpec instance = new TableSpec();
			public string name { get { return TABLE; } }
			public string idName { get { return FIELD_ID; } }
			public void refreshSqlObject(int id) { Refresh(id); }
		}

		protected override ISqlObjectTableSpec table { get { return TableSpec.instance; } }

		private int _ownerId;
		public int ownerId {
			get {
				this.LoadIfNotLoaded();
				return this._ownerId;
			}
		}
		public Account owner {
			get {
				return Account.LoadById(this.ownerId);
			}
		}

		private int _interlocutorId;
		public int interlocutorId {
			get {
				this.LoadIfNotLoaded();
				return this._interlocutorId;
			}
		}
		public Account interlocutor {
			get {
				return Account.LoadById(this.interlocutorId);
			}
		}

		public Account poster {
			get {
				if(this.direction == ENUM_DIRECTION_INCOMING) {
					return this.interlocutor;
				} else {
					return this.owner;
				}
			}
		}

		private string _direction;
		public string direction {
			get {
				this.LoadIfNotLoaded();
				return this._direction;
			}
		}

		private DateTime _postDate;
		public DateTime postDate {
			get {
				this.LoadIfNotLoaded();
				return this._postDate;
			}
		}

		private string _title;
		public string title {
			get {
				this.LoadIfNotLoaded();
				return this._title;
			}
		}

		private string _body;
		public string body {
			get {
				this.LoadIfNotLoaded();
				return this._body;
			}
		}

		private string _bodyUBB;
		public string bodyUBB {
			get {
				this.LoadIfNotLoaded();
				return this._bodyUBB;
			}
		}

		private int? _incomingPMId;
		public int? incomingPMId {
			get {
				this.LoadIfNotLoaded();
				return this._incomingPMId;
			}
		}
		public PMMessage incomingPM {
			get {
				return PMMessage.LoadById(this.incomingPMId.Value);
			}
		}

		private bool _isRead;
		public bool isRead {
			get {
				this.LoadIfNotLoaded();
				return this._isRead;
			}
		}

		public PMConversation conversation {
			get {
				return PMConversation.LoadByAccounts(this.owner, this.interlocutor);
			}
		}

		protected override void doFromHash(Dictionary<string, string> data) {
			this._ownerId = int.Parse(data[TableSpec.FIELD_OWNERID]);
			this._interlocutorId = int.Parse(data[TableSpec.FIELD_INTERLOCUTORID]);
			this._direction = data[TableSpec.FIELD_DIRECTION];
			this._postDate = Util.ParseDateTimeFromTimestamp(data[TableSpec.FIELD_POSTDATE]).Value;
			this._title = data[TableSpec.FIELD_TITLE];
			this._body = data[TableSpec.FIELD_BODY];
			this._bodyUBB = data[TableSpec.FIELD_BODYUBB];
			this._incomingPMId = Util.ParseInt(data[TableSpec.FIELD_INCOMINGPMID]);
			this._isRead = Util.string2bool(data[TableSpec.FIELD_ISREAD]);
		}

		public XElement exportToXml(UserContext context, params XElement[] additional) {
			if((context.account == null) || (context.account.id != this.owner.id)) {
				throw new AccessViolationException();
			}

			XElement result = new XElement("message",
				new XElement("id", this.id),
				new XElement("poster", this.poster.exportToXml(context)),
				new XElement("owner", this.owner.exportToXml(context)),
				new XElement("interlocutor", this.interlocutor.exportToXml(context)),
				new XElement("isRead", this.isRead.ToPlainString()),
				new XElement("postDate", this.postDate.ToXml()),
				new XElement("title", this.title),
				new XElement("body", context.outputParams.preprocessBodyIntermediate(this.body)),
				new XElement("bodyUBB", this.bodyUBB)
			);
			if(this.direction == ENUM_DIRECTION_OUTGOING) {
				result.Add(new XElement("isReadByInterlocutor", this.incomingPM.isRead));
			}
			if(additional.Length > 0) {
				result.Add(additional);
			}
			return result;
		}

		private readonly object MarkAsRead_locker = new object();
		public void MarkAsRead(Account account) {
			if(account.id != this.owner.id) throw new AccessViolationException();
			if(!this.isRead) {
				lock(MarkAsRead_locker) {
					//so we can safely decrease ReadPrivateMessages counter
					//Note that this code implicitly uses assumption of single-instance Common.dll; race condition is possible with more than one server
					if(!this.isRead) {
						AccountIndicator indicator = AccountIndicator.LoadByAccount(this.owner);
						ChangeSetUtil.ApplyChanges(
							new UpdateChange(
								TableSpec.instance,
								new Dictionary<string,AbstractFieldValue> {
									{ TableSpec.FIELD_ISREAD, new ScalarFieldValue("1") },
								},
								this.id
							),
							new UpdateChange(
								AccountIndicator.TableSpec.instance,
								new Dictionary<string,AbstractFieldValue> {
									{ AccountIndicator.TableSpec.FIELD_UNREADPRIVATEMESSAGES, new IncrementFieldValue(IncrementFieldValue.DECREMENTOR) },
								},
								indicator.id
							)
						);
					}
				}
			}
		}

	}
}
