using System;
using System.Linq;
using System.Linq.Expressions;
using System.Collections.Generic;
namespace System.Web.Mvc.Html {
public static class HtmlTagExtensions {
/// <summary>
/// Create an <see cref="System.Web.Mvc.Html.HtmlTag"/> for a given model property.
/// </summary>
/// <typeparam name="TModel"></typeparam>
/// <typeparam name="TProperty"></typeparam>
/// <param name="htmlHelper"></param>
/// <param name="model"></param>
/// <returns></returns>
public static HtmlTag<TModel, TProperty> TagFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> model) {
return new HtmlTag<TModel, TProperty>(htmlHelper, model);
}

public static HtmlTag Tag(this HtmlHelper htmlHelper, string name) {
return new HtmlTag(name);
}

public static MarkdownEditor<TModel, TProperty> MarkdownEditorFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> model) {
return new MarkdownEditor<TModel, TProperty>(htmlHelper, model);
}

/// <summary>
/// Splits a list of tags apart based on a common delimiter.
/// </summary>
/// <param name="content"></param>
/// <param name="delimiter"></param>
/// <returns></returns>
public static string[] ToDelimitedArray(this string content, params char[] delimiter) {
return content.Split(delimiter).Select(x => x.Trim()).ToArray();
}

public static string Join(this IEnumerable<string> strings, string separator) {
return string.Join(separator, strings);
}

public static MvcHtmlString ToHtml(this HtmlTag tag) {
return MvcHtmlString.Create(tag.ToString());
}
}

public class HtmlText {

/// <summary>
/// Constructs a new HtmlText
/// </summary>
public HtmlText() {
Encode = true;
}

/// <summary>
/// Constructs a new HtmlText with the given value.
/// </summary>
/// <param name="text"></param>
public HtmlText(string text) : this() {
Text = text;
}

/// <summary>
/// Construct a new HtmlText with the given encoding and value.
/// </summary>
/// <param name="text"></param>
/// <param name="encode"></param>
public HtmlText(string text, bool encode) : this(text) {
Encode = encode;
}

/// <summary>
/// The actual text to write.
/// </summary>
public string Text { get; set; }

/// <summary>
/// Whether or not to encode the text.
/// </summary>
public bool Encode { get; set; }
}
/// <summary>
/// Represents a typesafe html tag that can be used to build complicated html models with
/// intellisense and encoding, including validation attributes.
/// </summary>
public class HtmlTag {
/// <summary>
/// The name of the html tag being created
/// </summary>
public string Tag { get; set; }

/// <summary>
/// The inner text of the Html Tag
/// </summary>
public HtmlText HtmlText { get; set; }

/// <summary>
/// Specifies whether or not there is a closing tag
/// </summary>
public Boolean HasClosingTag { get; set; }

/// <summary>
/// Construct a new Html Tag.
/// </summary>
public HtmlTag() {
// default the closing tag value
HasClosingTag = true;
// construct the list of children tags
Children = new List<HtmlTag>();
// construct the styles list
Styles = new Dictionary<string, string>();
// construct the classes list
Classes = new List<string>();
// construct the attributes
Attributes = new Dictionary<string, string>();
// construct the meta data
MetaData = new Dictionary<string, string>();
// construct the data binding collection
DataBind = new Dictionary<string, string>();
}

/// <summary>
/// Construct a new Html Tag of the given type.
/// </summary>
/// <param name="tag">The html tag to create.</param>
public HtmlTag(string tag)
: this() {
// set the html tag property, make sure to always
// use the lowercase variant just for consistency.
this.Tag = tag.ToLower();
}

/// <summary>
/// Construct a new Html Tag as a child of the given tag.
/// </summary>
/// <param name="tag"></param>
/// <param name="parent"></param>
public HtmlTag(string tag, HtmlTag parent)
: this(tag) {
if (parent != null) parent.Append(this);
}

/// <summary>
/// Construct a new Html Tag with the given validation attributes.
/// </summary>
/// <param name="validationAttributes">
/// The validation attributes to include.
/// </param>
public HtmlTag(IDictionary<string, object> validationAttributes)
: this() {
this.ValidationAttributes = validationAttributes;
}

/// <summary>
/// Construct a new Html Tag with the given validation attributes and model metadata.
/// </summary>
/// <param name="validationAttributes">
/// The validation attributes to include.
/// </param>
/// <param name="metadata">
/// The model metadata to include
/// </param>
public HtmlTag(ModelMetadata metadata, IDictionary<string, object> validationAttributes)
: this(validationAttributes) {
this.ModelMetadata = metadata;
}

/// <summary>
/// Set the html tag type.
/// </summary>
/// <param name="tag"></param>
/// <returns></returns>
public HtmlTag Of(string tag) {
Tag = tag;
return this;
}

/// <summary>
/// Sets the 'type' Html Attribute.
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public HtmlTag Type(string type) {
return Attribute("type", type);
}

/// <summary>
/// Sets the 'Id' Html Attribute.
/// </summary>
/// <param name="id">
/// The value of the id
/// </param>
/// <returns></returns>
public HtmlTag Id(string id) {
return Attribute("id", id);
}

/// <summary>
/// Sets the 'Name' Html Attribute.
/// </summary>
/// <param name="name">
/// The value of the name.
/// </param>
/// <returns></returns>
public HtmlTag Name(string name) {
return Attribute("name", name);
}

/// <summary>
/// Specify the inner Html text
/// </summary>
/// <param name="text"></param>
/// <returns></returns>
public HtmlTag Text(string text) {
this.HtmlText = new HtmlText(text); return this;
}

/// <summary>
/// Specify the inner html text and encoding.
/// </summary>
/// <param name="text"></param>
/// <param name="encode"></param>
/// <returns></returns>
public HtmlTag Text(string text, bool encode) {
this.HtmlText = new HtmlText(text, encode); return this;
}

/// <summary>
/// Specifies whether or not the tag closes itself.
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public HtmlTag Closes(bool value) {
this.HasClosingTag = value; return this;
}

/// <summary>
/// Set a specific cascading stylesheet style value.
/// </summary>
/// <param name="key">The style to set.</param>
/// <param name="value">The value of the style.</param>
/// <returns></returns>
public HtmlTag Style(string key, string value) {
Styles[key] = value;
return this;
}

/// <summary>
/// Set a specific html attribute for the tag.
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
/// <returns></returns>
public HtmlTag Attribute(string key, string value) {
Attributes[key] = value;
return this;
}

/// <summary>
/// Adds a cascading stylesheet class to the tag.
/// </summary>
/// <param name="css"></param>
/// <returns></returns>
public HtmlTag Class(string css) {
if (!Classes.Contains(css))
Classes.Add(css);
return this;
}

/// <summary>
/// Adds a data-binding attribute.
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
/// <returns></returns>
public HtmlTag Bind(string key, string value) {
DataBind[key] = value; return this;
}

/// <summary>
/// Inserts a sibling tag immediately after the current tag. Any existing sibling will follow the inserted tag.
/// </summary>
/// <param name="tag">The tag to add as a sibling</param>
/// <returns>The original tag</returns>
public HtmlTag After(HtmlTag tag) {
Next = tag; return this;
}

/// <summary>
/// Inserts a sibling tag immediately after the current tag. Any existing sibling will follow the inserted tag.
/// </summary>
/// <param name="configuration"></param>
/// <returns></returns>
public HtmlTag After(Action<HtmlTag> configuration) {
Next = new HtmlTag(); configuration(Next); return this;
}

/// <summary>
/// Inserts a parent tag immediately before the current tag.
/// </summary>
/// <param name="configuration"></param>
/// <returns></returns>
public HtmlTag Before(Action<HtmlTag> configuration) {
Previous = new HtmlTag(); configuration(Previous); return this;
}

/// <summary>
/// Creates nested child tags and returns the innermost tag. Use <see cref="Append(string)"/> if you want to return the parent tag.
/// </summary>
/// <param name="tagNames">One or more HTML element names separated by a <c>/</c> or <c>></c></param>
/// <returns>The innermost tag that was newly added</returns>
public HtmlTag Add(string tags) {
return tags
.ToDelimitedArray('/', '>')
.Aggregate(this, (parent, tag) => new HtmlTag(tag, parent));
}

/// <summary>
/// Creates nested child tags and returns the innermost tag after running <paramref name="configuration"/> on it. Use <see cref="Append(string, Action{HtmlTag})"/> if you want to return the parent tag.
/// </summary>
/// <param name="tags">One or more HTML element names separated by a <c>/</c> or <c>></c></param>
/// <param name="configuration">Modifications to perform on the newly added innermost tag</param>
/// <returns>The innermost tag that was newly added</returns>
public HtmlTag Add(string tags, Action<HtmlTag> configuration) {
var element = Add(tags); configuration(element); return this;
}

/// <summary>
/// Creates a tag of <typeparamref name="T"/> and adds it as a child. Returns the created child tag.
/// </summary>
/// <typeparam name="T">The type of <see cref="HtmlTag"/> to create</typeparam>
/// <returns>The created child tag</returns>
public T Add<T>() where T : HtmlTag, new() {
var child = new T(); Children.Add(child); return child;
}

/// <summary>
/// Adds the given tag as the last child of the parent, and returns the parent.
/// </summary>
/// <param name="child">The tag to add as a child of the parent.</param>
/// <returns>The parent tag</returns>
public HtmlTag Append(HtmlTag child) {
Children.Add(child); return this;
}

/// <summary>
/// Creates nested child tags and returns the tag on which the method was called. Use <see cref="Add(string)"/> if you want to return the innermost tag.
/// </summary>
/// <param name="tags">One or more HTML element names separated by a <c>/</c> or <c>></c></param>
/// <returns>The instance on which the method was called (the parent of the new tags)</returns>
public HtmlTag Append(string tags) {
Add(tags); return this;
}

/// <summary>
/// Creates nested child tags, runs <paramref name="configuration"/> on the innermost tag, and returns the tag on which the method was called. Use <see cref="Add(string, Action{HtmlTag})"/> if you want to return the innermost tag.
/// </summary>
/// <param name="tags"></param>
/// <param name="configuration"></param>
/// <returns>The parent tag</returns>
public HtmlTag Append(string tags, Action<HtmlTag> configuration) {
Add(tags, configuration); return this;
}

/// <summary>
/// Adds a sequence of tags as children of the current tag. Returns the parent tag.
/// </summary>
/// <param name="tags">A sequence of tags to add as children.</param>
/// <returns>The parent tag</returns>
public HtmlTag Append(IEnumerable<HtmlTag> tags) {
Children.AddRange(tags); return this;
}

public override string ToString() {
return ToString(new System.Web.UI.HtmlTextWriter(new System.IO.StringWriter(), string.Empty) { NewLine = string.Empty });
}

public string ToString(System.Web.UI.HtmlTextWriter html) {
WriteHtml(html); return html.InnerWriter.ToString();
}

internal virtual void WriteHtml(System.Web.UI.HtmlTextWriter html) {
if (Previous != null)
Previous.WriteHtml(html);

// first, construct a tag builder
var htmlTag = new TagBuilder(Tag);

// assemble data bindings if neccessary.
if (!DataBind.IsEmpty()) {
// build the data-binding
var bindings = new System.Text.StringBuilder();
// build a full string of all data bindings
DataBind.Each(data => {
// insert each binding, separated by a comma, parsed by a colon
bool last = data.Equals(DataBind.Last()) ? true : false;
bindings.AppendFormat("{0}:{1}{2}", data.Key, data.Value, last ? null : ",");
});

// add the data binding attribute with all of the appropriate bindings
Attribute("data-bind", bindings.ToString());
}

// build the html attributes for the tag
htmlTag.MergeAttributes(Attributes);
// build the data attributes for the tag
htmlTag.MergeAttributes(ValidationAttributes);
// build the stylesheet data
htmlTag.MergeAttributes(Styles);

// we have used the tag builder to do our merging and formatting,
// now we can use it to begin drawing actual html.
htmlTag.Attributes.Each(attribute => {
html.AddAttribute(attribute.Key, attribute.Value);
});

// add all of the css classes to the html writer.
if (Classes.Count > 0) {
var classValue = Classes.Join(" ");
html.AddAttribute("class", classValue);
}

// Draw the Beginning Tag
html.RenderBeginTag(Tag);

// if there is any inner text, write it now
WriteInnerText(html);

// Draw each Child Tag
Children.Each(child => child.WriteHtml(html));

// if there is a closing tag to be written, draw it
// now
if (HasClosingTag)
html.RenderEndTag();

if (Next != null)
Next.WriteHtml(html);
}

private void WriteInnerText(System.Web.UI.HtmlTextWriter html) {
// if there is no inner text, just exit.
if (HtmlText == null)
return;

if (String.IsNullOrEmpty(HtmlText.Text))
return;

if (HtmlText.Encode)
html.WriteEncodedText(HtmlText.Text);
else
html.Write(HtmlText.Text);
}
/// <summary>
/// The unobtrusive validation attributes that apply to this html tag, from the
/// model metadata.
/// </summary>
protected IDictionary<string, object> ValidationAttributes { get; set; }
/// <summary>
/// The various cascading stylesheet styles to include.
/// </summary>
protected IDictionary<string, string> Styles { get; set; }
/// <summary>
/// The various cascading stylesheet classes to include.
/// </summary>
protected List<string> Classes { get; set; }
/// <summary>
/// The html attributes that belong to the tag.
/// </summary>
protected IDictionary<string, string> Attributes { get; set; }
/// <summary>
/// The html5 meta data that is attached to the tag.
/// </summary>
protected IDictionary<string, string> MetaData { get; set; }
/// <summary>
/// The html5 data-binding
/// </summary>
protected IDictionary<string, string> DataBind { get; set; }

/// <summary>
/// The html children of this tag that should be rendered with it.
/// </summary>
protected List<HtmlTag> Children { get; set; }

/// <summary>
/// The Mvc Model Meta Data.
/// </summary>
protected ModelMetadata ModelMetadata { get; set; }

/// <summary>
/// The next Html Tag in the sequence of rendering, if applicable.
/// </summary>
protected HtmlTag Next { get; set; }

/// <summary>
/// The tag to render before this one.
/// </summary>
protected HtmlTag Previous { get; set; }
}
/// <summary>
/// Represents a typesafe html tag that can be used to build complicated html models with
/// intellisense and encoding, including validation attributes.
/// </summary>
public class HtmlTag<TModel, TProperty> : HtmlTag {
/// <summary>
/// Construct a new Html Tag of the given type.
/// </summary>
/// <param name="tag">The html tag to create.</param>
public HtmlTag(string tag): base(tag) {
}
/// <summary>
/// Construct a new Html Tag with the given HtmlHelper.
/// </summary>
/// <param name="helper">The html helper</param>
public HtmlTag(HtmlHelper<TModel> helper)
: base() {
this.Helper = helper;
}

/// <summary>
/// Construct a new Html Tag with the given HtmlHelper from a Model property.
/// </summary>
/// <param name="helper">The html helper</param>
/// <param name="model">The html model</param>
public HtmlTag(HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> model)
: this(helper) {
this.Model = model;
}

/// <summary>
/// Construct a new Html Tag with the given validation attributes and model metadata and view data
/// </summary>
/// <param name="validationAttributes">
/// The validation attributes to include.
/// </param>
/// <param name="metadata">
/// The model metadata to include
/// </param>
public HtmlTag(ModelMetadata metadata, IDictionary<string, object> validationAttributes, ViewDataDictionary<TModel> viewData)
: base(metadata, validationAttributes) {
this.ViewData = viewData;
}

/// <summary>
/// Construct a new Html Tag with the given validation attributes and model metadata and view data
/// </summary>
/// <param name="validationAttributes">
/// The validation attributes to include.
/// </param>
/// <param name="metadata">
/// The model metadata to include
/// </param>
public HtmlTag(ModelMetadata metadata, IDictionary<string, object> validationAttributes, HtmlHelper<TModel> helper)
: base(metadata, validationAttributes) {
this.Helper = helper;
this.ViewData = helper.ViewData;
}

/// <summary>
/// Set the html tag type.
/// </summary>
/// <param name="tag"></param>
/// <returns></returns>
public HtmlTag<TModel, TProperty> Of(string tag) {
Tag = tag; return this;
}

/// <summary>
/// Sets the 'type' Html Attribute.
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public HtmlTag<TModel, TProperty> Type(string type) {
return Attribute("type", type);
}

/// <summary>
/// Sets the 'Id' Html Attribute.
/// </summary>
/// <param name="id">
/// The value of the id
/// </param>
/// <returns></returns>
public HtmlTag<TModel, TProperty> Id(string id) {
return Attribute("id", id);
}

/// <summary>
/// Sets the 'Name' Html Attribute.
/// </summary>
/// <param name="name">
/// The value of the name.
/// </param>
/// <returns></returns>
public HtmlTag<TModel, TProperty> Name(string name) {
return Attribute("name", name);
}

/// <summary>
/// Set a specific cascading stylesheet style value.
/// </summary>
/// <param name="key">The style to set.</param>
/// <param name="value">The value of the style.</param>
/// <returns></returns>
public HtmlTag<TModel, TProperty> Style(string key, string value) {
Styles[key] = value;
return this;
}

/// <summary>
/// Set a specific html attribute for the tag.
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
/// <returns></returns>
public HtmlTag<TModel, TProperty> Attribute(string key, string value) {
Attributes[key] = value;
return this;
}

/// <summary>
/// Adds a cascading stylesheet class to the tag.
/// </summary>
/// <param name="css"></param>
/// <returns></returns>
public HtmlTag<TModel, TProperty> Class(string css) {
if (!Classes.Contains(css))
Classes.Add(css);
return this;
}

/// <summary>
/// Adds a data-binding attribute.
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
/// <returns></returns>
public HtmlTag<TModel, TProperty> Bind(string key, string value) {
DataBind[key] = value;
return this;
}

public HtmlTag<TModel, TProperty> Validation() {
// get the field name of the model property
string fieldName = ExpressionHelper.GetExpressionText(Model);
// decompile the model metadata so that we can have access to the attributes.
ModelMetadata metadata = ModelMetadata.FromLambdaExpression(Model, Helper.ViewData);
// retrieve the unobtrusive validation attributes
this.ValidationAttributes = Helper.GetUnobtrusiveValidationAttributes(fieldName, metadata);
// return the html tag
return this;
}

public HtmlTag<TModel, TProperty> ValidationFor(Expression<Func<TModel, TProperty>> model) {
// get the field name of the model property
string fieldName = ExpressionHelper.GetExpressionText(model);
// decompile the model metadata so that we can have access to the attributes.
ModelMetadata metadata = ModelMetadata.FromLambdaExpression(model, Helper.ViewData);
// retrieve the unobtrusive validation attributes
this.ValidationAttributes = Helper.GetUnobtrusiveValidationAttributes(fieldName, metadata);
// return the html tag
return this;
}

/// <summary>Gets the strongly typed view data dictionary.</summary>
/// <returns>The strongly typed view data dictionary.</returns>
protected ViewDataDictionary<TModel> ViewData { get; set; }

/// <summary>
/// The HtmlHelper that created this tag.
/// </summary>
protected HtmlHelper<TModel> Helper { get; set; }

/// <summary>
/// The Html Model used to Associate this Tag.
/// </summary>
protected Expression<Func<TModel, TProperty>> Model { get; set; }
}

public class MarkdownEditor<TModel, TProperty> : HtmlTag<TModel, TProperty> {

public MarkdownEditor(ModelMetadata metadata, IDictionary<string, object> validationAttributes, HtmlHelper<TModel> helper)
: base(metadata, validationAttributes, helper) {
}

/// <summary>
/// Sets the <strong>Id</strong> of the Markdown preview pane.
/// </summary>
/// <param name="name">The html element id of the preview pane to create.</param>
/// <returns>
/// The preview pane will be accessed using the <em>#(selector)</em> behavior by <em>jQuery</em>.
/// </returns>
public HtmlTag<TModel, TProperty> Preview(string name) {
this.PreviewPane = name; return this;
}

/// <summary>
/// Construct a new Html Tag with the given HtmlHelper from a Model property.
/// </summary>
/// <param name="helper">The html helper</param>
/// <param name="model">The html model</param>
public MarkdownEditor(HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> model): base(helper, model) {
}

internal override void WriteHtml(System.Web.UI.HtmlTextWriter html) {
// ensure that this textarea will be an editor
Of("textarea");
Class("mdd_editor");
Attribute("data-mdd-preview", String.Format("#{0}", this.PreviewPane));

// add the extra data that has to enclose the editor
Before(tag => tag.Of("div").Class("mdd_toolbar"))
.After(tag => tag.Of("div").Class("mdd_resizer"))
.After(tag => tag.Of("div").Id(PreviewPane));

base.WriteHtml(html);
}

public string PreviewPane { get; set; }
}

public class HtmlSelectTag<TModel, TProperty> : HtmlTag<TModel, TProperty> {
public HtmlSelectTag():base("select") {
}
/// <summary>
/// Construct a new Html Tag with the given HtmlHelper from a Model property.
/// </summary>
/// <param name="helper">The html helper</param>
/// <param name="model">The html model</param>
public HtmlSelectTag(HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> model): base(helper, model) {
Of("select");
}

public HtmlSelectTag<TModel, TProperty> Options(IEnumerable<SelectListItem> options) {
options.Each(option => {
Option(option.Text, option.Value);
});
return this;
}
public HtmlSelectTag<TModel, TProperty> Option(string display, string value) {
Append(MakeOption(display, value)); return this;
}

private static HtmlTag MakeOption(string display, string value) {
return new HtmlTag("option")
.Text(display)
.Attribute("value", value);
}
}
}