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 Poll : SqlObject<Poll> {

		private class PollOptionInfo {
			public string name;
			public int votes;
			public List<int> voters;
			public bool selected;
		}

		public class TableSpec : ISqlObjectTableSpec {
			public const string TABLE = "Polls";
			public const string FIELD_ID = "Id";
			public const string FIELD_TITLE = "Title";
			public const string FIELD_ISDETAILED = "IsDetailed";
			public const string FIELD_ISMULTIOPTION = "IsMultiOption";
			public const string FIELD_OPTIONS = "Options";
			public const string FIELD_POSTERID = "PosterId";
			public const string FIELD_POSTDATE = "PostDate";
			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); }
		}

		public class Vote : SqlObject<Vote> {

			public class TableSpec : ISqlObjectTableSpec {
				public const string TABLE = "Votes";
				public const string FIELD_ID = "Id";
				public const string FIELD_POLLID = "PollId";
				public const string FIELD_USERID = "UserId";
				public const string FIELD_VOTEINFO = "VoteInfo";
				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 _userId;
			public int userId {
				get {
					this.LoadIfNotLoaded();
					return this._userId;
				}
			}
			public User user {
				get {
					return User.LoadById(this.userId);
				}
			}

			private int _pollId;
			public int pollId {
				get {
					this.LoadIfNotLoaded();
					return this._pollId;
				}
			}
			public Poll poll {
				get {
					return Poll.LoadById(this.pollId);
				}
			}

			private HashSet<int> _options;
			public HashSet<int> options {
				get {
					this.LoadIfNotLoaded();
					return this._options;
				}
			}

			protected override void doFromHash(Dictionary<string, string> data) {
				this._userId = int.Parse(data[TableSpec.FIELD_USERID]);
				this._pollId = int.Parse(data[TableSpec.FIELD_POLLID]);
				this._options = new HashSet<int>(from elem in XElement.Parse(data[TableSpec.FIELD_VOTEINFO]).Descendants("vote") select int.Parse(elem.Attribute("optionId").Value));
			}

		}

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

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

		private bool _isDetailed;
		public bool isDetailed {
			get {
				this.LoadIfNotLoaded();
				return this._isDetailed;
			}
		}

		private bool _isMultiOption;
		public bool isMultiOption {
			get {
				this.LoadIfNotLoaded();
				return this._isMultiOption;
			}
		}

		private Dictionary<int, string> _options;
		public Dictionary<int, string> options {
			get {
				this.LoadIfNotLoaded();
				return this._options;
			}
		}

		private int _posterId;
		public int posterId {
			get {
				this.LoadIfNotLoaded();
				return this._posterId;
			}
		}
		public User poster {
			get {
				return User.LoadById(this._posterId);
			}
		}

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

		protected override void doFromHash(Dictionary<string, string> data) {
			this._title = data[TableSpec.FIELD_TITLE];
			this._isDetailed = Util.string2bool(data[TableSpec.FIELD_ISDETAILED]);
			this._isMultiOption = Util.string2bool(data[TableSpec.FIELD_ISMULTIOPTION]);
			this._options = (from elem in XElement.Parse(data[TableSpec.FIELD_OPTIONS]).Descendants("option") select new KeyValuePair<int, string>(int.Parse(elem.Attribute("id").Value), elem.Attribute("name").Value)).ToDictionary();
			this._posterId = int.Parse(data[TableSpec.FIELD_POSTERID]);
			this._postDate = Util.ParseDateTimeFromTimestamp(data[TableSpec.FIELD_POSTDATE]).Value;
		}

		public XElement exportToXml(UserContext context) {
			return new XElement("poll",
				new XElement("id", this.id),
				new XElement("title", this.title),
				new XElement("isDetailed", this.isDetailed.ToPlainString()),
				new XElement("isMultiOption", this.isMultiOption.ToPlainString()),
				new XElement(
					"options",
					from kvp in this.options
					select new XElement(
						"option",
						new XElement("id", kvp.Key),
						new XElement("name", kvp.Value)
					)
				),
				new XElement("poster", this.poster.exportToXmlForViewing(context)),
				new XElement("postDate", this.postDate.ToXml())
			);
		}

		private Dictionary<int, PollOptionInfo> GetOptionsWithVotes(User user) {
			Dictionary<int, PollOptionInfo> result = this.options.ToDictionary(kvp => kvp.Key, kvp => new PollOptionInfo { name = kvp.Value, votes = 0, voters = new List<int>(), selected = false });
			foreach(
				var vote
				in
				Vote.LoadByIds(
					from stringId in Config.instance.mainConnection.LoadIdsByConditions(
						Vote.TableSpec.instance,
						new ComparisonCondition(
							Vote.TableSpec.instance.getColumnSpec(Vote.TableSpec.FIELD_POLLID),
							ComparisonType.EQUAL,
							this.id.ToString()
						),
						Diapasone.unlimited
					) select int.Parse(stringId)
				)
			) {
				foreach(int optionId in vote.options) {
					result[optionId].votes += 1;
					if(this.isDetailed) {
						result[optionId].voters.Add(vote.userId);
					}
				}
				if(user != null && vote.user.id == user.id) {
					foreach(int optionId in vote.options) {
						result[optionId].selected = true;
					}
				}
			}
			return result;
		}

		public XElement exportToXmlWithVotes(UserContext context) {
			return new XElement("poll",
				new XElement("id", this.id),
				new XElement("title", this.title),
				new XElement("isDetailed", this.isDetailed.ToPlainString()),
				new XElement("isMultiOption", this.isMultiOption.ToPlainString()),
				new XElement(
					"options",
					from kvp in this.GetOptionsWithVotes((context.account != null) ? context.account.user : null)
					select new XElement(
						"option",
						new XElement("id", kvp.Key),
						new XElement("name", kvp.Value.name),
						new XElement("isSelected", kvp.Value.selected),
						new XElement("votes", kvp.Value.votes),
						new XElement(
							"voters",
							from userId in kvp.Value.voters select User.LoadById(userId).exportToXmlForViewing(context)
						)
					),
					new XElement(
						"total",
						Config.instance.mainConnection.GetCountByConditions(
							Vote.TableSpec.instance,
							new ComparisonCondition(
								Vote.TableSpec.instance.getColumnSpec(Vote.TableSpec.FIELD_POLLID),
								ComparisonType.EQUAL,
								this.id.ToString()
							)
						)
					)
				),
				new XElement("poster", this.poster.exportToXmlForViewing(context)),
				new XElement("postDate", this.postDate.ToXml())
			);
		}

		public void GiveVote(User user, HashSet<int> options) {
			foreach(int option in options) {
				if(!this.options.ContainsKey(option)) throw new CriticalException("invalid option");
			}
			AbstractFieldValue voteInfo = new ScalarFieldValue(
				new XElement("votes",
					from option in options select new XElement("vote", new XAttribute("optionId", option))
				).ToString()
			);
			ChangeSetUtil.ApplyChanges(
				new InsertOrUpdateChange(
					Vote.TableSpec.instance,
					new Dictionary<string,AbstractFieldValue> {
						{ Vote.TableSpec.FIELD_POLLID, new ScalarFieldValue(this.id.ToString()) },
						{ Vote.TableSpec.FIELD_USERID, new ScalarFieldValue(user.id.ToString()) },
						{ Vote.TableSpec.FIELD_VOTEINFO, voteInfo }
					},
					new Dictionary<string,AbstractFieldValue> {
						{ Vote.TableSpec.FIELD_VOTEINFO, voteInfo },
					},
					new ComplexCondition(
						ConditionsJoinType.AND,
						new ComparisonCondition(
							Vote.TableSpec.instance.getColumnSpec(Vote.TableSpec.FIELD_POLLID),
							ComparisonType.EQUAL,
							this.id.ToString()
						),
						new ComparisonCondition(
							Vote.TableSpec.instance.getColumnSpec(Vote.TableSpec.FIELD_USERID),
							ComparisonType.EQUAL,
							user.id.ToString()
						)
					)
				)
			);
		}

		public static Poll Create(User poster, bool isDetailed, bool isMultiOption, string titleUbb, List<string> optionsUbb) {
			List<XElement> options = new List<XElement>();
			for(int i=0; i<optionsUbb.Count; i++) {
				options.Add(
					new XElement(
						"option",
						new XAttribute("id", i+1),
						new XAttribute("name", UBBParser.UBBToIntermediate(optionsUbb[i]))
					)
				);
			}
			AbstractChange pollInsert = new InsertChange(
				TableSpec.instance,
				new Dictionary<string,AbstractFieldValue> {
					{ TableSpec.FIELD_ISDETAILED, new ScalarFieldValue(isDetailed ? "1" : "0") },
					{ TableSpec.FIELD_ISMULTIOPTION, new ScalarFieldValue(isMultiOption ? "1" : "0") },
					{ TableSpec.FIELD_POSTERID, new ScalarFieldValue(poster.id.ToString()) },
					{ TableSpec.FIELD_POSTDATE, new ScalarFieldValue(DateTime.Now.ToUTCString()) },
					{ TableSpec.FIELD_TITLE, new ScalarFieldValue(UBBParser.UBBToIntermediate(titleUbb)) },
					{ TableSpec.FIELD_OPTIONS, new ScalarFieldValue((new XElement("options", options)).ToString()) },
				}
			);
			ChangeSetUtil.ApplyChanges(pollInsert);
			return Poll.LoadById(pollInsert.getId().Value);
		}

	}
}
