using System.Diagnostics;
using System.Text;
namespace RhythmBase.Components.RichText
{
///
/// Represents a list of rich text strings.
///
[DebuggerDisplay($"{{{nameof(GetDebuggerDisplay)}(),nq}}")]
public struct RDLine()
: IRDRichTextLine
where TStyle : IRDRichStringStyle, new()
{
///
/// Gets or sets the list of rich text strings.
///
private RDPhrase[] texts = [];
///
/// The length of the string.
///
public readonly int Length => texts.Sum(i => i.Length);
///
/// Gets or sets the at the specified index.
///
/// The index of the to get or set.
/// The at the specified index.
/// Thrown when the index is out of range.
public RDLine this[Index index]
{
get
{
int i = index.GetOffset(Length);
if (Length <= i)
throw new ArgumentOutOfRangeException(nameof(index));
int ti = 0;
while (texts[ti].Length < i)
{
i -= texts[ti].Length;
ti++;
}
RDLine line = new()
{
texts = [texts[ti][i]]
};
return line;
}
set
{
int i = index.GetOffset(Length);
if (Length <= i)
throw new ArgumentOutOfRangeException(nameof(index));
texts = Concat([this[..i], value, this[(i + 1)..]]).texts;
}
}
///
/// Gets or sets the within the specified range.
///
/// The range of the to get or set.
/// The within the specified range.
/// Thrown when the range is out of bounds.
public RDLine this[Range range]
{
get
{
int start = range.Start.GetOffset(Length);
int end = range.End.GetOffset(Length);
if (!(start <= end && end <= Length))
throw new ArgumentOutOfRangeException(nameof(range));
int ti = 0, tstart, tend;
RDPhrase[] strings = [];
while (texts[ti].Length <= start)
{
start -= texts[ti].Length;
end -= texts[ti].Length;
ti++;
}
tstart = ti;
while (texts[ti].Length < end)
{
end -= texts[ti].Length;
ti++;
}
tend = ti;
if (tstart == tend)
strings = [texts[tstart][start..end]];
else
{
for (int i = tstart + 1; i < tend; i++)
strings = [.. strings, texts[i]];
strings = [texts[tstart][start..], .. strings, texts[tend][..end]];
}
RDLine line = new()
{
texts = strings
};
return line;
}
set
{
int start = range.Start.GetOffset(Length);
int end = range.End.GetOffset(Length);
if (!(start < end && end <= Length))
throw new ArgumentOutOfRangeException(nameof(range));
texts = Concat([this[..start], value, this[end..]]).texts;
}
}
///
public static RDLine Concat(params RDLine[] lines)
{
RDPhrase[] texts = [.. lines[0].texts];
foreach (RDLine line in lines[1..])
{
if (texts[^1].Style == line.texts[0].Style)
{
RDPhrase before = texts[^1], after = line.texts[0];
RDPhrase richString = new(before.Text + after.Text)
{
Style = before.Style,
Events = [.. before.Events, .. after.Events.Select(i => new RDDialogueTone(i.Type, i.Index + before.Length) { Pause = i.Pause })]
};
texts = [.. texts[..^1], richString, .. line.texts[1..]];
}
else
texts = [.. texts, .. line.texts];
}
return new() { texts = texts };
}
///
/// Implicitly converts a to a .
///
/// The to convert.
/// A new containing the specified .
public static implicit operator RDLine(RDPhrase text) => new() { texts = [text] };
///
/// Implicitly converts a to a .
///
/// The to convert.
/// A new containing the specified .
public static implicit operator RDLine(RDPhrase[] texts) => new() { texts = texts };
///
/// Implicitly converts a to a .
///
/// The to convert.
/// A new containing the specified .
public static implicit operator RDLine(string text) => new() { texts = [new RDPhrase(text)] };
///
/// Deserializes a string into an .
///
/// The string to deserialize.
/// A new containing the deserialized content.
/// Thrown when the input text is null.
/// Thrown when the input text has an invalid format.
static public RDLine Deserialize(string text)
{
RDLine line = "";
TStyle style = new();
int start = 0;
while (start < text.Length)
{
TStyle tempStyle = style;
int end = text.IndexOf('<', start);
if (end == -1)
{
line += DeserializeStringPart(text[start..], tempStyle);
break;
}
int start2 = text.IndexOf('>', end);
int end2 = text.IndexOf('<', end + 1);
if (start2 == -1)
break;
if (end2 != -1 && end2 < start2)
{
line += DeserializeStringPart(text[start..end2], tempStyle);
start = end2;
continue;
}
string textpart = text[start..end];
line += DeserializeStringPart(textpart, tempStyle);
string[] keyvalue = text[(end + 1)..start2].Split('=', 2);
if (keyvalue[0].StartsWith('/') && style.ResetProperty(keyvalue[0][1..]))
start = start2 + 1;
else if (style.SetProperty(keyvalue[0], keyvalue.Length == 2 ? keyvalue[1] : "true"))
start = start2 + 1;
else
start = start2 + 1;
}
return line;
}
private static RDPhrase DeserializeStringPart(string text, TStyle style)
{
if (!TStyle.HasPhrase)
return new RDPhrase(text) { Style = style };
int pstart = 0;
RDDialogueTone[] events = [];
while (pstart < text.Length)
{
int pend = text.IndexOf('[', pstart);
if (pend == -1)
{
break;
}
int pstart2 = text.IndexOf(']', pend);
int pend2 = text.IndexOf('[', pend + 1);
if (pstart2 == -1)
break;
if (pend2 != -1 && pend2 < pstart2)
{
pstart = pend2 + 1;
continue;
}
string btag = text[(pend + 1)..pstart2];
if (RDDialogueTone.Create(btag, pend, out RDDialogueTone? e) && e is RDDialogueTone ei)
events = [.. events, ei];
text = text[..pend] + text[(pstart2 + 1)..];
}
return new RDPhrase(text) { Style = style, Events = events };
}
///
/// Serializes the current instance to a string.
///
/// A string representation of the current instance.
///
/// The serialization process converts the rich text line into a string format, including any styling information.
///
public readonly string Serialize()
{
StringBuilder sb = new();
TStyle style = new();
int ci = 0;
foreach (RDPhrase str in texts)
{
sb.Append(TStyle.GetXmlTag(style, str.Style));
string part = str.Text;
int offset = 0;
foreach (RDDialogueTone e in str.Events)
{
string serialized = e.Serialize();
part = part.Insert(e.Index + offset, serialized);
offset += serialized.Length;
}
sb.Append(part);
ci += str.Length;
style = str.Style;
}
sb.Append(TStyle.GetXmlTag(style, new()));
return sb.ToString();
}
///
/// Concatenates two instances.
///
/// The left .
/// The right .
/// A new that is the result of concatenating the two specified instances.
public static RDLine operator +(RDLine left, RDLine right) => Concat([.. left.texts, .. right.texts]);
///
public override readonly string ToString() => string.Join("", texts);
///
private readonly string GetDebuggerDisplay() => ToString();
}
}