using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using System.Web; using Iesi.Collections.Generic; using Portoa.Persistence; using Portoa.Search; namespace VideoGameQuotes.Api { public class Quote : Entity, IDtoMappable, ISearchable { private string text; private readonly Iesi.Collections.Generic.ISet votes = new HashedSet(); private readonly Iesi.Collections.Generic.ISet flags = new HashedSet(); private readonly Iesi.Collections.Generic.ISet categories = new HashedSet(); public Quote() { Created = DateTime.UtcNow; } public virtual User Creator { get; set; } public virtual DateTime Created { get; set; } public virtual DateTime? Modified { get; set; } public virtual string Text { get { return text; } set { if (value != text) { ShouldIndex = true; } text = value; } } public virtual Game Game { get; set; } public virtual IEnumerable Votes { get { return votes; } } public virtual IEnumerable Flags { get { return flags; } } public virtual IEnumerable Categories { get { return categories; } } #region adding and removing stuff public virtual Quote AddCategory(Category category) { categories.Add(category); return this; } public virtual void ClearCategories() { categories.Clear(); } public virtual Quote AddFlag(string comment, QuoteFlagType type, User user) { var flag = new QuoteFlag { Comment = comment, Type = type, User = user, Quote = this }; if (flags.Add(flag)) { FlagCount++; } return this; } public virtual Quote RemoveFlag(QuoteFlag flag) { if (flags.Remove(flag)) { FlagCount--; } return this; } public virtual void ClearFlags() { flags.Clear(); FlagCount = 0; } #endregion public virtual Quote VoteFor(User user, VoteDirection direction) { var currentVote = votes.SingleOrDefault(vote => vote.Voter == user); if (currentVote != null) { if (currentVote.Direction == direction) { throw new CannotVoteTwiceException(); } votes.Remove(currentVote); if (currentVote.Direction == VoteDirection.Up) { UpVotes--; } else { DownVotes--; } currentVote.Direction = direction; currentVote.Created = DateTime.UtcNow; votes.Add(currentVote); } else { votes.Add(new Vote { Direction = direction, Quote = this, Voter = user }); } if (direction == VoteDirection.Up) { UpVotes++; } else { DownVotes++; } Score = UpVotes - DownVotes; return this; } public virtual VoteDirection? VotedFor(User user) { var vote = Votes.Where(v => v.Voter == user).SingleOrDefault(); if (vote == null) { return null; } return vote.Direction; } public virtual int UpVotes { get; private set; } public virtual int DownVotes { get; private set; } public virtual int Score { get; private set; } public virtual int FlagCount { get; private set; } public virtual QuoteDto ToDto() { return new QuoteDto { Id = Id, Text = Text, Game = Game.ToDto(), Created = Created, Categories = Categories.Select(category => category.ToDto()), TotalVotes = UpVotes + DownVotes, UpVotes = UpVotes, DownVotes = DownVotes, Score = Score }; } #region nested types public class GameCriterionHandler : CriterionHandler { protected override Func HandleInteger(int value) { return quote => quote.Game.Id == value; } } #endregion public virtual bool ShouldIndex { get; private set; } } public class QuoteDto { public int Id { get; set; } public string Text { get; set; } public GameDto Game { get; set; } public DateTime Created { get; set; } public IEnumerable Categories { get; set; } public int TotalVotes { get; set; } public int UpVotes { get; set; } public int DownVotes { get; set; } public int Score { get; set; } } public static class QuoteExtensions { private static readonly Regex urlFriendlyRegex = new Regex(@"[^A-Za-z0-9_-]"); public static string FormatTextForHtml(this Quote quote) { return HttpUtility.HtmlEncode(quote.Text).Replace("'", "’").Replace("\n", "
"); } public static string GetUrlFriendlyText(this Quote quote) { var text = urlFriendlyRegex.Replace(quote.Text.ToLowerInvariant().Replace(" ", "-").Replace("\n", ""), ""); return text.Substring(0, Math.Min(text.Length, 50)); } public static string GetAbbreviatedText(this Quote quote) { if (quote.Text.Length < 100) { return quote.Text; } //ellipsis ftw return quote.Text.Substring(0, 100) + Char.ConvertFromUtf32(0x2026); } public static string GetHumanReadableTimeSinceCreated(this Quote quote) { var timespan = DateTime.UtcNow.Subtract(quote.Created); if (timespan.TotalDays >= 365) { var years = (int)Math.Round(timespan.TotalDays / 365); return years == 1 ? "1 year ago" : years + " years ago"; } if (timespan.TotalDays >= 31) { var months = (int)Math.Round(timespan.TotalDays / 30); return months == 1 ? "1 month ago" : months + " months ago"; } if (timespan.TotalDays >= 7) { var weeks = (int)Math.Round(timespan.TotalDays / 7); return weeks == 1 ? "1 week ago" : weeks + " weeks ago"; } if (timespan.TotalDays >= 1) { return (int)timespan.TotalDays == 1 ? "1 day ago" : (int)timespan.TotalDays + " days ago"; } if (timespan.TotalMinutes >= 60) { return (int)timespan.TotalHours == 1 ? "1 hour ago" : (int)timespan.TotalHours + " hours ago"; } if (timespan.TotalSeconds >= 60) { return (int)timespan.TotalMinutes == 1 ? "1 minute ago" : (int)timespan.TotalMinutes + " minutes ago"; } return (int)timespan.TotalSeconds == 1 ? "1 second ago" : (int)timespan.TotalSeconds + " seconds ago"; } } }