'**************************************************
' FILE      : BBCodeElement.vb
' AUTHOR    : Paulo Santos
' CREATION  : 4/29/2009 11:33:36 AM
' COPYRIGHT : Copyright © 2009
'             PJ on Development
'             All Rights Reserved.
'
' Description:
'       TODO: Add file description
'
' Change log:
' 0.1   4/29/2009 11:33:36 AM
'       Paulo Santos
'       Created.
'***************************************************

Imports System.Globalization
Imports System.Text.RegularExpressions

''' <summary>
''' Represents an BBCode element.
''' </summary>
Public Class BBCodeElement
    Inherits BBCodeNode

    Private __Name As String
    Private __Nodes As BBCodeNodeCollection
    Private __Attributes As BBCodeAttributeDictionary
    Private __ReplacementFormat As String = String.Empty
    Private __RequireClosingTag As TriState = TriState.UseDefault
    Private __IsValueFormatted As Boolean

    Private Shared ReadOnly __RxAttribute As New Regex("\{(?<name>[_a-zA-Z0-9].*?)\}")

    ''' <summary>Initializes an instance of the <see cref="BBCodeElement" /> class.
    ''' This is the default constructor for this class.</summary>
    Friend Sub New()
    End Sub

    ''' <summary>Initializes an instance of the <see cref="BBCodeElement" /> class.</summary>
    ''' <param name="name">The name of the element.</param>
    Protected Sub New(ByVal name As String)
        __Name = name.ToUpperInvariant()
    End Sub

    ''' <summary>Initializes an instance of the <see cref="BBCodeElement" /> class.</summary>
    ''' <param name="parser">The parser used to create this element.</param>
    Friend Sub New(ByVal parser As BBCodeParser)
        MyBase.New(parser)
    End Sub

    ''' <summary>
    ''' Gets the name of the element.
    ''' </summary>
    Public ReadOnly Property Name() As String
        Get
            Return __Name
        End Get
    End Property

    ''' <summary>
    ''' Gets the replacement format for this element.
    ''' </summary>
    ''' <value>The replacement format.</value>
    ''' <remarks>
    ''' <para>In order to use the any parameter in the replacement format use the following syntax: {paramName}.</para>
    ''' <para/>
    ''' <para>The parameter names are case insensitive.</para>
    ''' <para/>
    ''' <para>There are two reserved parameter keywords for formatting: DEFAULT and VALUE.</para>
    ''' <para><c>DEFAULT</c> : The text following the first equal sign after the name of the BBCode element.</para>
    ''' <para><c>VALUE</c>: The HTML generated by the BBCode between the start and end element tag.</para>
    ''' <para><example>[url=http://tempuri.org]text[/url]</example></para>
    ''' <para>In the example above the parameter DEFAULT would have the value "http://tempuri.org", while VALUE would be "text".</para>
    ''' <para/>
    ''' <para>To replace [url=http://tempuri.org]text[/url] the with &lt;a href="http://tempuri.org"&gt;text&lt;/a&gt; the following ReplacementFormat should be used:</para>
    ''' <example>
    ''' &lt;a href="{default|value}"&gt;{value|default}&lt;/a&gt;
    ''' </example>
    ''' <para>The example above, will set the href attribute with either the default value or the text inside the [url] element. The pipe (|) implies in finding the first non-empty attribute.</para>
    ''' </remarks>
    Public ReadOnly Property ReplacementFormat() As String
        Get
            If (String.IsNullOrEmpty(__ReplacementFormat)) Then
                If (Me.Parser.Dictionary.ContainsKey(Me.Name)) Then
                    __ReplacementFormat = Me.Parser.Dictionary(Me.Name).ReplacementFormat
                End If
            End If
            Return __ReplacementFormat
        End Get
    End Property

    ''' <summary>
    ''' Gets a value indicating if the element requires a closing tag.
    ''' </summary>
    ''' <value><c>True</c> if the element requires a closing tag; otherwise <c>False</c>.</value>
    Public ReadOnly Property RequireClosingTag() As Boolean
        Get
            If (__RequireClosingTag = TriState.UseDefault) Then
                If (Not String.IsNullOrEmpty(Me.Name) AndAlso Me.Parser.Dictionary.ContainsKey(Me.Name)) Then
                    __RequireClosingTag = If(Me.Parser.Dictionary(Me.Name).RequireClosingTag, TriState.True, TriState.False)
                ElseIf (Not String.IsNullOrEmpty(Me.Name) AndAlso Me.Parser.ElementTypes.ContainsKey(Me.Name)) Then
                    __RequireClosingTag = If(Me.Parser.ElementTypes(Me.Name).RequireClosingTag, TriState.True, TriState.False)
                End If
            End If
            Return (__RequireClosingTag = TriState.True)
        End Get
    End Property

    ''' <summary>
    ''' Gets the list of attributes.
    ''' </summary>
    Public ReadOnly Property Attributes() As BBCodeAttributeDictionary
        Get
            If (__Attributes Is Nothing) Then
                __Attributes = New BBCodeAttributeDictionary
            End If
            Return __Attributes
        End Get
    End Property

    ''' <summary>
    ''' Gets the list of sub nodes.
    ''' </summary>
    Public ReadOnly Property Nodes() As BBCodeNodeCollection
        Get
            If (__Nodes Is Nothing) Then
                __Nodes = New BBCodeNodeCollection(Me)
            End If
            Return __Nodes
        End Get
    End Property

    ''' <summary>Transforms this instance of <see cref="BBCodeElement" /> into its desired text representation.</summary>
    ''' <param name="formatter">An object that implements the <see cref="ITextFormatter" /> interface.</param>
    ''' <returns>The text formatted by the <see cref="ITextFormatter" />.</returns>
    Public Overrides Function Format(ByVal formatter As ITextFormatter) As String
        IsValueFormatted = False
        Dim sb As New Text.StringBuilder(Me.ReplacementFormat)
        Dim attribs = __RxAttribute.Matches(Me.ReplacementFormat)
        For Each attrib As Match In attribs
            sb.Replace(attrib.Value, GetAttribute(attrib.Groups("name").Value, formatter))
        Next
        If Not IsValueFormatted Then
            sb.Append(GetAttribute("value", formatter))
        End If
        Return sb.ToString()
    End Function

    ''' <summary>Gets or sets the inner </summary>
    ''' <value>The BBCode between the start and end tags.</value>
    Public NotOverridable Overrides Property InnerBBCode() As String
        Get
            Dim sb As New System.Text.StringBuilder()
            For Each n As BBCodeNode In Nodes
                sb.Append(n.OuterBBCode)
            Next
            Return sb.ToString()
        End Get
        Set(ByVal value As String)
            If Not RequireClosingTag Then
                Throw New InvalidOperationException("The InnerBBCode property cannot be set for elements that does not require closing tags.")
            End If
            Me.Nodes.Clear()
            Me.Nodes.AddRange(Me.Parser.Parse(value).Nodes)
        End Set
    End Property

    ''' <summary>Gets or sets the outer </summary>
    ''' <value>The BBCode of this instance of the <see cref="BBCodeNode" /> .</value>
    Public NotOverridable Overrides ReadOnly Property OuterBBCode() As String
        Get
            Dim sb As New Text.StringBuilder()
            sb.Append("[")
            sb.Append(Me.Name)
            If ((Me.Attributes.Count = 1) AndAlso Me.Attributes.ContainsKey("default")) Then
                sb.AppendFormat(CultureInfo.InvariantCulture, "={0}", Quote(Me.Attributes("default")))
            Else
                For Each key In Me.Attributes.Keys
                    sb.AppendFormat(CultureInfo.InvariantCulture, " {0}={1}", key, Quote(Me.Attributes(key)))
                Next
            End If
            If (Me.RequireClosingTag) Then
                sb.Append("]")
                sb.Append(Me.InnerBBCode)
                sb.Append("[/")
                sb.Append(Me.Name)
            End If
            sb.Append("]")
            Return sb.ToString()
        End Get
    End Property

    ''' <summary>Gets or sets the plain text of the node.</summary>
    ''' <value>The plain text between the start and end tags.</value>
    Public NotOverridable Overrides Property InnerText() As String
        Get
            Dim sb As New Text.StringBuilder()
            For Each n In Me.Nodes
                sb.Append(n.InnerText)
            Next
            Return sb.ToString()
        End Get
        Set(ByVal value As String)
            If (Not RequireClosingTag) Then
                Throw New InvalidOperationException("The InnerText property cannot be set for elements that does not require closing tags.")
            End If

            '*
            '* Removes all nodex from the element
            '*
            Me.Nodes.Clear()
            If (Not String.IsNullOrEmpty(value)) Then
                '*
                '* Only append a BBCodeText element if the value is not empty
                '*
                Me.Nodes.Add(New BBCodeText(value))
            End If
        End Set
    End Property

    ''' <summary>
    ''' Sets the element name of this instance.
    ''' </summary>
    ''' <param name="name">The new element name of this instance.</param>
    Friend Sub SetName(ByVal name As String)
        __Name = name
    End Sub

    ''' <summary>
    ''' Gets a value indicating wheter or not the <see cref="BBCodeElement.Nodes"/> have been formatted.
    ''' </summary>
    ''' <returns><c>True</c> if the values have been formatted; otherwise <c>False</c>.</returns>
    Private Property IsValueFormatted() As Boolean
        Get
            Return __IsValueFormatted
        End Get
        Set(ByVal value As Boolean)
            __IsValueFormatted = value
        End Set
    End Property

    Private Function GetAttribute(ByVal name As String, ByVal formatter As ITextFormatter) As String
        Dim attribs = name.ToUpperInvariant().Split("|".ToCharArray(), StringSplitOptions.RemoveEmptyEntries)
        For Each attrib In attribs
            If (String.CompareOrdinal(attrib, "VALUE") = 0) Then
                IsValueFormatted = True
                Dim sb As New Text.StringBuilder()
                For Each node In Me.Nodes
                    sb.Append(node.Format(formatter))
                Next
                Return sb.ToString()
            ElseIf (Me.Attributes.ContainsKey(attrib)) Then
                Return (Me.Attributes.Item(attrib))
            End If
        Next
        Return String.Empty
    End Function

End Class
