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.Core.DB.conditions;
using FLocal.Common.actions;

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

		public class TableSpec : ISqlObjectTableSpec {
			public const string TABLE = "PMConversations";
			public const string FIELD_ID = "Id";
			public const string FIELD_OWNERID = "OwnerAccountId";
			public const string FIELD_INTERLOCUTORID = "InterlocutorAccountId";
			public const string FIELD_TOTALMESSAGES = "TotalMessages";
			public const string FIELD_LASTMESSAGEID = "LastMessageId";
			public const string FIELD_LASTMESSAGEDATE = "LastMessageDate";
			public const string FIELD_LASTREADMESSAGEID = "LastReadMessageId";
			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);
			}
		}

		private int _totalMessages;
		public int totalMessages {
			get {
				this.LoadIfNotLoaded();
				return this._totalMessages;
			}
		}

		private int _lastMessageId;
		public int lastMessageId {
			get {
				this.LoadIfNotLoaded();
				return this._lastMessageId;
			}
		}
		public PMMessage lastMessage {
			get {
				return PMMessage.LoadById(this.lastMessageId);
			}
		}

		private DateTime _lastMessageDate;
		public DateTime lastMessageDate {
			get {
				this.LoadIfNotLoaded();
				return this._lastMessageDate;
			}
		}
		
		private int? _lastReadMessageId;
		public int? lastReadMessageId {
			get {
				this.LoadIfNotLoaded();
				return this._lastReadMessageId;
			}
		}
		public PMMessage lastReadMessage {
			get {
				return PMMessage.LoadById(this.lastReadMessageId.Value);
			}
		}

		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._totalMessages = int.Parse(data[TableSpec.FIELD_TOTALMESSAGES]);
			this._lastMessageId = int.Parse(data[TableSpec.FIELD_LASTMESSAGEID]);
			this._lastMessageDate = new DateTime(long.Parse(data[TableSpec.FIELD_LASTMESSAGEDATE]));
			this._lastReadMessageId = Util.ParseInt(data[TableSpec.FIELD_LASTREADMESSAGEID]);
		}

		public XElement exportToXmlSimpleWithParent(UserContext context) {
			return new XElement("conversation",
				new XElement("id", this.id),
				new XElement("name", this.interlocutor.user.name)
			);
		}

		public XElement exportToXml(UserContext context, bool includeFirstPost, params XElement[] additional) {
			if((context.account == null) || (context.account.id != this.owner.id)) throw new AccessViolationException();
			XElement result = new XElement("conversation",
				new XElement("id", this.id),
				new XElement("owner", this.owner.exportToXml(context)),
				new XElement("interlocutor", this.interlocutor.exportToXml(context)),
				new XElement("totalMessages", this.totalMessages),
				new XElement("lastMessageId", this.lastMessageId),
				new XElement("lastMessageDate", this.lastMessageDate.ToXml()),
				new XElement("lastReadMessageId", this.lastReadMessageId),
				new XElement("afterLastRead", this.lastReadMessageId + 1),
				new XElement(
					"totalNewMessages",
					Config.instance.mainConnection.GetCountByConditions(
						PMMessage.TableSpec.instance,
						new ComplexCondition(
							ConditionsJoinType.AND,
							new ComparisonCondition(
								PMMessage.TableSpec.instance.getColumnSpec(PMMessage.TableSpec.FIELD_OWNERID),
								ComparisonType.EQUAL,
								this.ownerId.ToString()
							),
							new ComparisonCondition(
								PMMessage.TableSpec.instance.getColumnSpec(PMMessage.TableSpec.FIELD_INTERLOCUTORID),
								ComparisonType.EQUAL,
								this.interlocutorId.ToString()
							),
							new ComparisonCondition(
								PMMessage.TableSpec.instance.getIdSpec(),
								ComparisonType.GREATERTHAN,
								(this.lastReadMessageId.HasValue ? this.lastReadMessageId.Value : 0).ToString()
							)
						)
					)
				),
				context.formatTotalPosts(this.totalMessages)
			);
			if(additional.Length > 0) {
				result.Add(additional);
			}
			return result;
		}

		public IEnumerable<PMMessage> getMessages(Diapasone diapasone, UserContext context, bool isAscending) {
			return PMMessage.LoadByIds(
				from stringId in Config.instance.mainConnection.LoadIdsByConditions(
					PMMessage.TableSpec.instance,
					new ComplexCondition(
						ConditionsJoinType.AND,
						new ComparisonCondition(
							PMMessage.TableSpec.instance.getColumnSpec(PMMessage.TableSpec.FIELD_OWNERID),
							ComparisonType.EQUAL,
							this.ownerId.ToString()
						),
						new ComparisonCondition(
							PMMessage.TableSpec.instance.getColumnSpec(PMMessage.TableSpec.FIELD_INTERLOCUTORID),
							ComparisonType.EQUAL,
							this.interlocutorId.ToString()
						)
					),
					diapasone,
					new JoinSpec[0],
					new SortSpec[] {
						new SortSpec(
							PMMessage.TableSpec.instance.getIdSpec(),
							isAscending
						),
					}
				) select int.Parse(stringId)
			);
		}

		public void markAsRead(Account account, PMMessage minMessage, PMMessage maxMessage) {
			if(this.ownerId != account.id) throw new AccessViolationException();
			ChangeSetUtil.ApplyChanges(new AbstractChange[] {
				new UpdateChange(
					TableSpec.instance,
					new Dictionary<string,AbstractFieldValue> {
						{
							TableSpec.FIELD_LASTREADMESSAGEID,
							new IncrementFieldValue(
								s => {
									if((s == null) || (s == "")) {
										s = "0"; //workaround
									}
									if(maxMessage.id < int.Parse(s)) {
										return (s == "0") ? null : s; //if some newer posts were already read
									}
									long count = Config.instance.mainConnection.GetCountByConditions(
										PMMessage.TableSpec.instance,
										new ComplexCondition(
											ConditionsJoinType.AND,
											new ComparisonCondition(
												PMMessage.TableSpec.instance.getColumnSpec(PMMessage.TableSpec.FIELD_OWNERID),
												ComparisonType.EQUAL,
												this.ownerId.ToString()
											),
											new ComparisonCondition(
												PMMessage.TableSpec.instance.getColumnSpec(PMMessage.TableSpec.FIELD_INTERLOCUTORID),
												ComparisonType.EQUAL,
												this.interlocutorId.ToString()
											),
											new ComparisonCondition(
												PMMessage.TableSpec.instance.getIdSpec(),
												ComparisonType.GREATERTHAN,
												s
											),
											new ComparisonCondition(
												PMMessage.TableSpec.instance.getIdSpec(),
												ComparisonType.LESSTHAN,
												minMessage.id.ToString()
											)
										)
									);
									if(count > 0) {
										return (s == "0") ? null : s; //if there are some unread posts earlier than minPost
									} else {
										return maxMessage.id.ToString();
									}
								}
							)
						}
					},
					this.id
				)
			});
		}

		public static IEnumerable<PMConversation> getConversations(Account owner, Diapasone diapasone, bool isAscending) {
			return LoadByIds(
				from stringId in Config.instance.mainConnection.LoadIdsByConditions(
					TableSpec.instance,
					new ComparisonCondition(
						TableSpec.instance.getColumnSpec(TableSpec.FIELD_OWNERID),
						ComparisonType.EQUAL,
						owner.id.ToString()
					),
					diapasone,
					new JoinSpec[0],
					new SortSpec[] {
						new SortSpec(
							TableSpec.instance.getColumnSpec(TableSpec.FIELD_LASTMESSAGEID),
							isAscending
						)
					}
				) select int.Parse(stringId)
			);
		}

		public static PMConversation LoadByAccounts(Account owner, Account interlocutor) {
			return LoadById(
					int.Parse(Config.instance.mainConnection.LoadIdsByConditions(
						TableSpec.instance,
						new ComplexCondition(
							ConditionsJoinType.AND,
							new ComparisonCondition(
								TableSpec.instance.getColumnSpec(TableSpec.FIELD_OWNERID),
								ComparisonType.EQUAL,
								owner.id.ToString()
							),
							new ComparisonCondition(
								TableSpec.instance.getColumnSpec(TableSpec.FIELD_INTERLOCUTORID),
								ComparisonType.EQUAL,
								interlocutor.id.ToString()
							)
						),
						Diapasone.unlimited
					).Single())
				);
		}

		public static PMMessage SendPMMessage(Account sender, Account receiver, string title, string bodyUBB) {
			string bodyIntermediate = UBBParser.UBBToIntermediate(bodyUBB);
			AbstractChange insertPmReceiver = new InsertChange(
				PMMessage.TableSpec.instance,
				new Dictionary<string,AbstractFieldValue> {
					{ PMMessage.TableSpec.FIELD_OWNERID, new ScalarFieldValue(receiver.id.ToString()) },
					{ PMMessage.TableSpec.FIELD_INTERLOCUTORID, new ScalarFieldValue(sender.id.ToString()) },
					{ PMMessage.TableSpec.FIELD_DIRECTION, new ScalarFieldValue(PMMessage.ENUM_DIRECTION_INCOMING) },
					{ PMMessage.TableSpec.FIELD_POSTDATE, new ScalarFieldValue(DateTime.Now.ToUTCString()) },
					{ PMMessage.TableSpec.FIELD_TITLE, new ScalarFieldValue(title) },
					{ PMMessage.TableSpec.FIELD_BODY, new ScalarFieldValue(bodyIntermediate) },
					{ PMMessage.TableSpec.FIELD_BODYUBB, new ScalarFieldValue(bodyUBB) },
					{ PMMessage.TableSpec.FIELD_INCOMINGPMID, new ScalarFieldValue(null) },
					{ PMMessage.TableSpec.FIELD_ISREAD, new ScalarFieldValue("0") },
				}
			);
			AbstractChange insertPmSender = new InsertChange(
				PMMessage.TableSpec.instance,
				new Dictionary<string,AbstractFieldValue> {
					{ PMMessage.TableSpec.FIELD_OWNERID, new ScalarFieldValue(sender.id.ToString()) },
					{ PMMessage.TableSpec.FIELD_INTERLOCUTORID, new ScalarFieldValue(receiver.id.ToString()) },
					{ PMMessage.TableSpec.FIELD_DIRECTION, new ScalarFieldValue(PMMessage.ENUM_DIRECTION_OUTGOING) },
					{ PMMessage.TableSpec.FIELD_POSTDATE, new ScalarFieldValue(DateTime.Now.ToUTCString()) },
					{ PMMessage.TableSpec.FIELD_TITLE, new ScalarFieldValue(title) },
					{ PMMessage.TableSpec.FIELD_BODY, new ScalarFieldValue(bodyIntermediate) },
					{ PMMessage.TableSpec.FIELD_BODYUBB, new ScalarFieldValue(bodyUBB) },
					{ PMMessage.TableSpec.FIELD_INCOMINGPMID, new ReferenceFieldValue(insertPmReceiver) },
					{ PMMessage.TableSpec.FIELD_ISREAD, new ScalarFieldValue("1") },
				}
			);
			AbstractChange updateConversationSender = new InsertOrUpdateChange(
				TableSpec.instance,
				new Dictionary<string,AbstractFieldValue> {
					{ TableSpec.FIELD_OWNERID, new ScalarFieldValue(sender.id.ToString()) },
					{ TableSpec.FIELD_INTERLOCUTORID, new ScalarFieldValue(receiver.id.ToString()) },
					{ TableSpec.FIELD_TOTALMESSAGES, new ScalarFieldValue("1") },
					{ TableSpec.FIELD_LASTMESSAGEID, new ReferenceFieldValue(insertPmSender) },
					{ TableSpec.FIELD_LASTMESSAGEDATE, new ScalarFieldValue(DateTime.Now.ToUTCString()) },
					{ TableSpec.FIELD_LASTREADMESSAGEID, new ReferenceFieldValue(insertPmSender) },
				},
				new Dictionary<string,AbstractFieldValue> {
					{ TableSpec.FIELD_TOTALMESSAGES, new IncrementFieldValue() },
					{ TableSpec.FIELD_LASTMESSAGEID, new TwoWayReferenceFieldValue(insertPmSender, TwoWayReferenceFieldValue.GREATEST) },
					{ TableSpec.FIELD_LASTMESSAGEDATE, new ScalarFieldValue(DateTime.Now.ToUTCString()) },
				},
				new ComplexCondition(
					ConditionsJoinType.AND,
					new ComparisonCondition(
						TableSpec.instance.getColumnSpec(TableSpec.FIELD_OWNERID),
						ComparisonType.EQUAL,
						sender.id.ToString()
					),
					new ComparisonCondition(
						TableSpec.instance.getColumnSpec(TableSpec.FIELD_INTERLOCUTORID),
						ComparisonType.EQUAL,
						receiver.id.ToString()
					)
				)
			);
			AbstractChange updateConversationReceiver = new InsertOrUpdateChange(
				TableSpec.instance,
				new Dictionary<string,AbstractFieldValue> {
					{ TableSpec.FIELD_OWNERID, new ScalarFieldValue(receiver.id.ToString()) },
					{ TableSpec.FIELD_INTERLOCUTORID, new ScalarFieldValue(sender.id.ToString()) },
					{ TableSpec.FIELD_TOTALMESSAGES, new ScalarFieldValue("1") },
					{ TableSpec.FIELD_LASTMESSAGEID, new ReferenceFieldValue(insertPmReceiver) },
					{ TableSpec.FIELD_LASTMESSAGEDATE, new ScalarFieldValue(DateTime.Now.ToUTCString()) },
					{ TableSpec.FIELD_LASTREADMESSAGEID, new ScalarFieldValue(null) },
				},
				new Dictionary<string,AbstractFieldValue> {
					{ TableSpec.FIELD_TOTALMESSAGES, new IncrementFieldValue() },
					{ TableSpec.FIELD_LASTMESSAGEID, new TwoWayReferenceFieldValue(insertPmReceiver, TwoWayReferenceFieldValue.GREATEST) },
					{ TableSpec.FIELD_LASTMESSAGEDATE, new ScalarFieldValue(DateTime.Now.ToUTCString()) },
				},
				new ComplexCondition(
					ConditionsJoinType.AND,
					new ComparisonCondition(
						TableSpec.instance.getColumnSpec(TableSpec.FIELD_OWNERID),
						ComparisonType.EQUAL,
						receiver.id.ToString()
					),
					new ComparisonCondition(
						TableSpec.instance.getColumnSpec(TableSpec.FIELD_INTERLOCUTORID),
						ComparisonType.EQUAL,
						sender.id.ToString()
					)
				)
			);
			AbstractChange updateIndicatorReceiver = new UpdateChange(
				AccountIndicator.TableSpec.instance,
				new Dictionary<string,AbstractFieldValue> {
					{ AccountIndicator.TableSpec.FIELD_PRIVATEMESSAGES, new IncrementFieldValue() },
					{ AccountIndicator.TableSpec.FIELD_UNREADPRIVATEMESSAGES, new IncrementFieldValue() },
				},
				AccountIndicator.LoadByAccount(receiver).id
			);
			ChangeSetUtil.ApplyChanges(
				insertPmReceiver,
				insertPmSender,
				updateConversationReceiver,
				updateConversationSender,
				updateIndicatorReceiver
			);
			return PMMessage.LoadById(insertPmSender.getId().Value);
		}

	}

}
