using System;
using System.Collections.Generic;
using System.Threading;

using Bitrix.Components;
using Bitrix.DataLayer;
using Bitrix.DataTypes;
using Bitrix.IO;
using Bitrix.Security;
using Bitrix.Services;
using Bitrix.Services.Text;
using Bitrix.UI;

namespace Bitrix.Blog.Components
{
	public partial class BlogEditComponent : BXComponent
	{
		private BXBlogAuthorization auth;
		private BXBlog blog;
		private string blogSlug;
		private Data data;
		private List<string> errorSummary;
		private bool templateIncluded;
		private Mode componentMode;
		private ErrorCode fatalError = ErrorCode.FatalComponentNotExecuted;
		private Exception fatalException;
		private BXBlogCategoryCollection siteCategories;
		private List<string> slugIgnoreList;

		public BXBlogAuthorization Auth
		{
			get { return auth; }
		}
		public BXBlog Blog
		{
			get { return blog; }
			set { blog = value; }
		}
		public string BlogSlug
		{
			get
			{
				return (blogSlug ?? (blogSlug = Parameters.GetString("BlogSlug")));
			}
		}

		public int MaxBlogGroups
		{
			get
			{
				return Math.Max(1, Parameters.GetInt("MaxBlogGroups", 1));
			}
		}

		public int MinSlugLength
		{
			get
			{
				return Math.Max(1, Parameters.GetInt("MinSlugLength", 3));
			}
		}
		public int MaxSlugLength
		{
			get
			{
				return Math.Min(Math.Max(1, Parameters.GetInt("MaxSlugLength", 10)), 128);
			}
		}
		public List<string> SlugIgnoreList
		{
			get 
			{
				if (slugIgnoreList == null)
					slugIgnoreList = Parameters.GetListString("SlugIgnoreList", new List<string>());
				return slugIgnoreList;
			}
		}

		public Mode ComponentMode
		{
			get { return componentMode; }
		}
		public Data ComponentData
		{
			get { return data; }
		}
		public BXBlogCategoryCollection SiteCategories
		{
			get
			{
				if (siteCategories == null)
				{
					siteCategories = BXBlogCategory.GetList(
						new BXFilter(new BXFilterItem(BXBlogCategory.Fields.Sites.SiteId, BXSqlFilterOperators.Equal, DesignerSite)), 
						new BXOrderBy(new BXOrderByPair(BXBlogCategory.Fields.Sort, BXOrderByDirection.Asc)),
						null,
						null,
						BXTextEncoder.EmptyTextEncoder
					);
				}

				return siteCategories;
			}
		}
		public List<string> ErrorSummary
		{
			get
			{
				return errorSummary ?? (errorSummary = new List<string>());
			}
		}
		public ErrorCode FatalError
		{
			get
			{
				return fatalError;
			}
		}
		public Exception FatalException
		{
			get
			{
				return fatalException;
			}
		}
		public bool CanModerate
		{
			get { return BXPrincipal.Current.IsCanOperate(BXBlog.Operations.BlogApprove); }
		}
		public bool IsOwner
		{
			get { return blog != null && blog.OwnerId == BXIdentity.Current.Id; }
		}

		protected void Page_Load(object sender, EventArgs e)
		{
			fatalError = ErrorCode.None;

			try
			{
				componentMode = BXStringUtility.IsNullOrTrimEmpty(BlogSlug) ? Mode.Add : Mode.Edit;

				if (!LoadEntities())
					return;

				if (!CheckPermissions())
					return;

				data = new Data(this);
				data.BlogSlug = BlogSlug;

				if (Blog != null)
				{
					data.BlogName = Blog.Name;
					data.BlogDescription = Blog.Description;
					data.Categories = Blog.GetCategoryIds();
					data.NotifyComments = Blog.NotifyOfComments;
					data.BlogActive = Blog.Active;
				}

				if (!templateIncluded)
				{
					templateIncluded = true;

					BXPublicPage bitrixPage = Page as BXPublicPage;
					if (bitrixPage != null && !IsComponentDesignMode && Parameters.Get<bool>("SetPageTitle", true))
						bitrixPage.BXTitle = componentMode == Mode.Add ? GetMessageRaw("PageTitle.CreateBlog") : GetMessageRaw("PageTitle.EditBlog");

					IncludeComponentTemplate();
				}
			}
			catch (Exception ex)
			{
				Fatal(ex);
			}
		}
		protected override void OnPreRender(EventArgs e)
		{
			base.OnPreRender(e);

			foreach (string param in new string[] { Parameters.GetString("ThemeCssFilePath"), Parameters.GetString("ColorCssFilePath") })
			{
				if (BXStringUtility.IsNullOrTrimEmpty(param))
					continue;
				string path = param;
				try
				{
					path = BXPath.ToVirtualRelativePath(path);
					if (BXSecureIO.FileExists(path))
						BXPage.RegisterStyle(path);
				}
				catch
				{
				}
			}
		}
		protected override void PreLoadComponentDefinition()
		{
			Title = GetMessageRaw("Title");
			Description = GetMessageRaw("Description");
			Group = BXBlogModuleConfiguration.GetComponentGroup();

			BXCategory mainCategory = BXCategory.Main;
			BXCategory urlCategory = BXCategory.UrlSettings;
			BXCategory additionalSettingsCategory = BXCategory.AdditionalSettings;

			ParamsDefinition["ThemeCssFilePath"] = new BXParamText(GetMessageRaw("Param.ThemeCssFilePath"), "~/bitrix/components/bitrix/blog/templates/.default/style.css", mainCategory);
			ParamsDefinition["ColorCssFilePath"] = new BXParamText(GetMessageRaw("Param.ColorCssFilePath"), "~/bitrix/components/bitrix/blog/templates/.default/themes/default/style.css", mainCategory);
			ParamsDefinition["BlogSlug"] = new BXParamText(GetMessageRaw("Param.BlogSlug"), "", mainCategory);
			ParamsDefinition["MaxBlogGroups"] = new BXParamText(GetMessageRaw("Param.MaxBlogGroups"), "1", mainCategory);
			ParamsDefinition["SlugIgnoreList"] = new BXParamText(GetMessageRaw("Param.SlugIgnoreList"), "admin;blog;blogs;users;user;search;posts;rss;tags", mainCategory);
			ParamsDefinition["MinSlugLength"] = new BXParamText(GetMessageRaw("Param.MinSlugLength"), "3", mainCategory);
			ParamsDefinition["MaxSlugLength"] = new BXParamText(GetMessageRaw("Param.MaxSlugLength"), "10", mainCategory);
			ParamsDefinition["RedirectUrlTemplate"] = new BXParamText(GetMessageRaw("Param.RedirectUrlTemplate"), "blog.aspx?blog=#blogSlug#", urlCategory);
			ParamsDefinition["SetPageTitle"] = new BXParamYesNo(GetMessageRaw("Param.SetPageTitle"), true, additionalSettingsCategory);

		}

		private bool LoadEntities()
		{
			if (componentMode == Mode.Edit)
			{
				BXFilter filter = new BXFilter(
					new BXFilterItem(BXBlog.Fields.Slug, BXSqlFilterOperators.Equal, BlogSlug),
					new BXFilterItem(BXBlog.Fields.Categories.Category.Sites.SiteId, BXSqlFilterOperators.Equal, DesignerSite)
				);

				if (!BXPrincipal.Current.IsCanOperate(BXBlog.Operations.BlogApprove))
					filter.Add(new BXFilterItem(BXBlog.Fields.Active, BXSqlFilterOperators.Equal, true));

				BXBlogCollection blogCollection = BXBlog.GetList(filter, null, null, null, BXTextEncoder.EmptyTextEncoder);

				if (blogCollection.Count == 0)
				{
					Fatal(ErrorCode.FatalBlogNotFound);
					return false;
				}

				Blog = blogCollection[0];
			}
			else
			{
				int userId = BXPrincipal.Current.GetIdentity().Id;

				BXBlogCollection blogCollection = BXBlog.GetList(
					new BXFilter(
						new BXFilterItem(BXBlog.Fields.Owner.Id, BXSqlFilterOperators.Equal, userId),
						new BXFilterItem(BXBlog.Fields.Categories.Category.Sites.SiteId, BXSqlFilterOperators.Equal, DesignerSite)
					),
					null
				);

				if (blogCollection.Count != 0)
				{
					Fatal(ErrorCode.FatalBlogAlreadyExists);
					return false;
				}
			}

			return true;
		}
		private bool CheckPermissions()
		{

			auth = new BXBlogAuthorization(blog);

			if (BXIdentity.Current.Id < 1)
			{
				Fatal(ErrorCode.UnauthorizedNotLogIn);
				return false;
			}
			if (componentMode == Mode.Add && !auth.CanCreateBlog)
			{
				Fatal(ErrorCode.UnauthorizedCreateBlog);
				return false;
			}
			else if (componentMode == Mode.Edit && !auth.CanEditThisBlogSettings)
			{
				Fatal(ErrorCode.UnauthorizedEditBlog);
				return false;
			}

			return true;
		}
		private BXParamsBag<object> GetReplaceParameters()
		{
			BXParamsBag<object> parameters = new BXParamsBag<object>();
			parameters["BlogSlug"] = BlogSlug;
			if (Blog != null)
				parameters["BlogId"] = Blog.Id;
			return parameters;
		}
		private void Fatal(ErrorCode code)
		{
			if (code == ErrorCode.FatalException)
				throw new InvalidOperationException("Use method with Exception argument");
			fatalError = code;
			if (!templateIncluded)
			{
				templateIncluded = true;
				IncludeComponentTemplate();
			}
		}
		private void Fatal(Exception ex)
		{
			if (ex == null)
				throw new ArgumentNullException("ex");

			fatalError = ErrorCode.FatalException;
			fatalException = ex;
			if (!templateIncluded)
			{
				templateIncluded = true;
				IncludeComponentTemplate();
			}
		}

		public string GetErrorHtml(ErrorCode code)
		{
			switch (code)
			{
				case ErrorCode.FatalException:
					return BXPrincipal.Current.IsCanOperate(BXRoleOperation.Operations.SystemMaintenance) ? ("<pre>" + Encode(fatalException.ToString()) + "</pre>") : GetMessage("Error.Unknown");
				case ErrorCode.FatalBlogAlreadyExists:
					return GetMessage("Error.FatalBlogAlreadyExists");
				case ErrorCode.FatalBlogNotFound:
					return GetMessage("Error.FatalBlogNotFound");
				case ErrorCode.UnauthorizedNotLogIn:
					return componentMode == Mode.Edit ? GetMessage("Error.UnauthorizedNotLogIn.Edit") : GetMessage("Error.UnauthorizedNotLogIn.Add");
				case ErrorCode.UnauthorizedCreateBlog:
					return GetMessage("Error.UnauthorizedCreateBlog");
				case ErrorCode.UnauthorizedEditBlog:
					return GetMessage("Error.UnauthorizedEditBlog");
				default:
					return GetMessage("Error.Unknown");
			}
		}
		public bool Validate()
		{
			ErrorSummary.Clear();

			data.BlogName = data.BlogName != null ? data.BlogName.Trim() : null;
			data.BlogDescription = data.BlogDescription != null ? data.BlogDescription.Trim() : null;

			if (BXStringUtility.IsNullOrTrimEmpty(data.BlogName))
				ErrorSummary.Add(GetMessage("Error.BlogNameRequired"));

			if (componentMode == Mode.Add)
			{
				if (BXStringUtility.IsNullOrTrimEmpty(data.BlogSlug))
					ErrorSummary.Add(GetMessage("Error.BlogSlugRequired"));	
				else if (!BXBlog.SlugRegex.IsMatch(data.BlogSlug))
					ErrorSummary.Add(GetMessage("Error.BlogSlugInvalid"));
				else if (SlugIgnoreList.Contains(data.BlogSlug))
					ErrorSummary.Add(String.Format(GetMessage("Error.BlogSlugInIgnoreList"), data.BlogSlug));
				else if (data.BlogSlug.Length < MinSlugLength || data.BlogSlug.Length > MaxSlugLength)
					ErrorSummary.Add(String.Format(GetMessage("Error.SlugLength"), MinSlugLength, MaxSlugLength));
				else if (BXBlog.GetBySlug(data.BlogSlug) != null)
					ErrorSummary.Add(GetMessage("Error.BlogSlugAlreadyExists"));
			}

			if (data.Categories == null || data.Categories.Length <= 0)
				ErrorSummary.Add(GetMessage("Error.BlogCategoryRequired"));
			else if (data.Categories.Length > MaxBlogGroups)
				ErrorSummary.Add(String.Format(GetMessage("Error.BlogCategoryLimit"), MaxBlogGroups));
			else
			{
				foreach (int categoryId in data.Categories)
				{
					if (!SiteCategories.Exists(delegate(BXBlogCategory category) { return categoryId == category.Id; }))
					{
						ErrorSummary.Add(GetMessage("Error.BlogWrongCategory"));
						break;
					}
				}
			}

			return ErrorSummary.Count == 0;
		}
		public void Save()
		{
			if (FatalError != ErrorCode.None)
				return;
			try
			{
				if (blog == null)
				{
					blog = new BXBlog();
					blog.OwnerId = ((BXIdentity)BXPrincipal.Current.Identity).Id;
					blog.Active = true;
				}
				else if (BXPrincipal.Current.IsCanOperate(BXBlog.Operations.BlogApprove))
					blog.Active = data.BlogActive;

				blog.Name = data.BlogName;
				blog.Description = data.BlogDescription;

				if (componentMode == Mode.Add)
					blog.Slug = data.BlogSlug;

				blog.NotifyOfComments = data.NotifyComments;
				blog.SetCategoryIds(data.Categories);
				blog.Save();

				blogSlug = data.BlogSlug;

				string redirectUrl = Parameters.GetString("RedirectUrlTemplate");
				if (BXStringUtility.IsNullOrTrimEmpty(redirectUrl))
					Response.Redirect(BXSefUrlManager.CurrentUrl.ToString());
				RedirectTemplateUrl(redirectUrl, GetReplaceParameters());
			}
			catch (ThreadAbortException)
			{
			}
			catch (Exception ex)
			{
				Fatal(ex);
			}
		}

		//NESTED CLASSES
		public enum Mode
		{
			Add,
			Edit
		}

		public enum ErrorCode
		{
			None = 0,
			Fatal = 1,
			Unauthorized = 2,

			FatalBlogNotFound = Fatal | 4,
			FatalBlogAlreadyExists = Fatal | 8 | 4,
			FatalException = Fatal | 8,
			FatalComponentNotExecuted = Fatal | 16,

			UnauthorizedNotLogIn = Unauthorized | 4 | 8,
			UnauthorizedCreateBlog = Unauthorized | 4,
			UnauthorizedEditBlog = Unauthorized | 8
		}

		public class Data
		{
			private BlogEditComponent component;
			private bool blogActive;
			private string blogName;
			private string blogDescription;
			private string blogSlug;
			private bool notifyComments;
			private int[] categories;

			public bool BlogActive
			{
				get { return blogActive; }
				set { blogActive = value; }
			}
			public string BlogName
			{
				get { return blogName; }
				set { blogName = value; }
			}
			public string BlogDescription
			{
				get { return blogDescription; }
				set { blogDescription = value; }
			}
			public string BlogSlug
			{
				get { return blogSlug; }
				set { blogSlug = value; }
			}
			public bool NotifyComments
			{
				get { return notifyComments; }
				set { notifyComments = value; }
			}
			public int[] Categories
			{
				get { return categories; }
				set { categories = value; }
			}

			internal Data(BlogEditComponent component)
			{
				this.component = component;
			}
		}
	}

	public class BlogEditTemplate : BXComponentTemplate<BlogEditComponent>
	{
		protected virtual void LoadData(BlogEditComponent.Data data)
		{
		}
		protected virtual void SaveData(BlogEditComponent.Data data)
		{
		}

		protected override void OnInit(EventArgs e)
		{
			base.OnInit(e);
			if (!IsPostBack && Component.FatalError == BlogEditComponent.ErrorCode.None)
				LoadData(Component.ComponentData);
		}
		protected override void OnPreRender(EventArgs e)
		{
			base.OnPreRender(e);
			if ((Component.FatalError & BlogEditComponent.ErrorCode.Fatal) != 0)
				BXError404Manager.Set404Status(Response);
		}

		protected void SaveClick(object sender, EventArgs e)
		{
			SaveData(Component.ComponentData);

			if (Component.Validate())
				Component.Save();
		}
	}
}
