#if USE_CELTX
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using UnityEngine;
namespace PixelCrushers.DialogueSystem.Celtx
{
    /// 
    /// This class does the actual work of converting Celtx Data (Raw) into a dialogue database.
    /// 
    public class CeltxToDialogueDatabase
    {
        Template template = Template.FromDefault();
        DialogueDatabase database;
        CeltxData celtxData;
        public DialogueDatabase ProcessRawCeltxData(CeltxDataRaw celtxDataRaw, DialogueDatabase database, bool importGameplayScriptText, bool importBreakdownCatalogContent, bool checkSequenceSyntax)
        {
            try
            {
                this.database = database;
                celtxData = new CeltxData();
                List docContentList = celtxDataRaw.doc.content;
                Debug.Log("Converting Celtx Data to Dialogue System database");
                CxContent cxMetadata = docContentList.Find(c => c.type.Equals("cxmetadata"));
                ProcessCxCatalog(cxMetadata);
                ProcessCxVariables(cxMetadata);
                ProcessCxConditions(cxMetadata);
                List sequenceList = docContentList.Where(c => c.type.Equals("cxsequence")).ToList();
                ProcessSequenceList(sequenceList);
                if (checkSequenceSyntax) CheckSequenceSyntax();
                ConvertBlankNodesToGroupNodes();
            }
            catch (System.Exception e)
            {
                LogError(MethodBase.GetCurrentMethod(), e);
            }
            return database;
        }
        private void ProcessCxCatalog(CxContent cxMetadata)
        {
            List cxCatalogContent;
            try
            {
                cxCatalogContent = cxMetadata.content.Find(c => c.type.Equals("cxcatalog")).content;
            }
            catch (System.Exception)
            {
                Debug.Log("Skipping catalog because it is null or empty");
                return;
            }
            foreach (CxContent contentObject in cxCatalogContent)
            {
                switch (contentObject.attrs.type)
                {
                    case "character":
                        ConvertCharacterCatalogEntryToDSActor(contentObject.attrs);
                        break;
                    case "location":
                        ConvertLocationCatalogEntryToDSLocation(contentObject.attrs);
                        break;
                    case "item":
                        ConvertItemCatalogEntryToDSItem(contentObject.attrs);
                        break;
                    case "branch":
                    case "jump":
                    case "sequence":
                        ExtractCustomVarsList(contentObject.attrs);
                        break;
                }
            }
        }
        private void ProcessCxVariables(CxContent cxMetadata)
        {
            List cxVariablesList;
            try
            {
                cxVariablesList = cxMetadata.content.Find(c => c.type.Equals("cxconditions")).attrs.variables;
            }
            catch (System.Exception)
            {
                Debug.Log("Skipping variables because there are none");
                return;
            }
            foreach (CxVariable variableObject in cxVariablesList)
            {
                ConvertCeltxVariableToDSVariable(variableObject);
            }
        }
        private void ProcessCxConditions(CxContent cxMetadata)
        {
            List cxConditionsContent;
            try
            {
                cxConditionsContent = cxMetadata.content.Find(c => c.type.Equals("cxconditions")).content;
            }
            catch (System.Exception)
            {
                Debug.Log("Skipping variables because there are none");
                return;
            }
            foreach (CxContent contentObject in cxConditionsContent)
            {
                ExtractCeltxCondition(contentObject.attrs);
            }
            foreach (CeltxCondition condition in celtxData.conditions)
            {
                GenerateLuaCondition(condition);
            }
        }
        private void AppendToField(List fields, string title, string value, FieldType fieldType)
        {
            string currentFieldValue = Field.LookupValue(fields, title);
            if (value == null || value.Equals("") || currentFieldValue != null && currentFieldValue.Equals(value)) { return; }
            string updatedString;
            if (currentFieldValue == null || currentFieldValue.Equals("") || currentFieldValue.Equals("[]")) { updatedString = value; }
            else { updatedString = currentFieldValue + " " + value; }
            Field.SetValue(fields, title, updatedString, fieldType);
        }
        #region CxSequence Processing
        private void ProcessSequenceList(List sequenceList)
        {
            sequenceList.ForEach(c => ExtractSequenceLinkingData(c));
            celtxData.sequenceLinkingDataList.ForEach(i => CheckIfRoot(i));
            List rootItems = celtxData.sequenceLinkingDataList.Where(i => i.isRoot).ToList();
            rootItems.ForEach(i => ProcessConversation(i, sequenceList));
        }
        private void ExtractSequenceLinkingData(CxContent cxSequenceContentObject)
        {
            try
            {
                SequenceLinkingData sequenceLinkingData = new SequenceLinkingData();
                sequenceLinkingData.id = cxSequenceContentObject.attrs.id;
                sequenceLinkingData.name = cxSequenceContentObject.attrs.name;
                if (cxSequenceContentObject.attrs.transitions != null && cxSequenceContentObject.attrs.transitions.Count == 1 && cxSequenceContentObject.attrs.transitions[0].id != null)
                {
                    sequenceLinkingData.linkedIds.Add(cxSequenceContentObject.attrs.transitions[0].id);
                }
                List cxBranches = cxSequenceContentObject.content[0].content.Where(c => c.type.Equals("cxbranch")).ToList();
                if (cxBranches != null && cxBranches.Count > 0)
                {
                    foreach (CxContent branch in cxBranches)
                    {
                        foreach (CxLinked linkedItem in branch.attrs.linked)
                        {
                            sequenceLinkingData.linkedIds.Add(linkedItem.id);
                        }
                    }
                }
                List cxJumps = cxSequenceContentObject.content[0].content.Where(c => c.type.Equals("cxjump")).ToList();
                if (cxJumps != null && cxJumps.Count > 0)
                {
                    foreach (CxContent jump in cxJumps)
                    {
                        sequenceLinkingData.linkedIds.Add(jump.attrs.linked_jump.id);
                    }
                }
                celtxData.idsWithIncomingLinks.AddRange(sequenceLinkingData.linkedIds);
                celtxData.sequenceLinkingDataList.Add(sequenceLinkingData);
            }
            catch (System.Exception e)
            {
                LogError(MethodBase.GetCurrentMethod(), e, cxSequenceContentObject.attrs.id);
            }
        }
        public void CheckIfRoot(SequenceLinkingData sequenceLinkingData)
        {
            try
            {
                if (!celtxData.idsWithIncomingLinks.Contains(sequenceLinkingData.id))
                {
                    sequenceLinkingData.isRoot = true;
                }
            }
            catch (System.Exception e)
            {
                LogError(MethodBase.GetCurrentMethod(), e, sequenceLinkingData.id);
            }
        }
        public void ProcessConversation(SequenceLinkingData sequenceLinkingData, List sequenceList)
        {
            try
            {
                string sequenceId = sequenceLinkingData.id;
                CxContent cxSequenceContentObject = sequenceList.Find(c => c.attrs.id.Equals(sequenceLinkingData.id));
                DialogueEntry conversationStartNode = ProcessConversationRootSequence(cxSequenceContentObject, sequenceId);
                if (conversationStartNode == null) return;
                ProcessConversationChildSequences(sequenceId, conversationStartNode, sequenceLinkingData, sequenceList);
            }
            catch (System.Exception e)
            {
                LogError(MethodBase.GetCurrentMethod(), e, sequenceLinkingData.id);
            }
        }
        private DialogueEntry ProcessConversationRootSequence(CxContent cxSequenceContentObject, string sequenceId)
        {
            try
            {
                string sequenceName = cxSequenceContentObject.attrs.name;
                string sequenceCatalogId = cxSequenceContentObject.attrs.catalog_id;
                List pageContents = GetSequencePageContents(cxSequenceContentObject);
                if (pageContents == null)
                {
                    LogWarning("Skipping this sequence map because the Conversation Root node's page is null or empty. Please ensure the node has 2 characters",
                        cxSequenceContentObject.attrs.id, cxSequenceContentObject.attrs.name);
                    return null;
                }
                List characters = pageContents.FindAll(c => c.type.Equals("cxcharacter"));
                if (characters == null || characters.Count < 2)
                {
                    LogWarning("Skipping this sequence map because the Conversation Root node has less than 2 characters. Please ensure the node has 2 characters",
                        cxSequenceContentObject.attrs.id, cxSequenceContentObject.attrs.name);
                    return null;
                }
                Conversation conversation = template.CreateConversation(template.GetNextConversationID(database), sequenceName);
                database.conversations.Add(conversation);
                conversation.ActorID = GetActorIdForCharacter(characters[0]);
                conversation.ConversantID = GetActorIdForCharacter(characters[1]);
                DialogueEntry startNode = CreateNextDialogueEntryForConversation(conversation, "START", sequenceId);
                startNode.Sequence = "None()";
                AddSetVariableScript(startNode, sequenceCatalogId);
                return startNode;
            }
            catch (System.Exception e)
            {
                LogError(MethodBase.GetCurrentMethod(), e, cxSequenceContentObject.attrs.id);
                return null;
            }
        }
        public void ProcessConversationChildSequences(string sourceCxId, DialogueEntry lastCreatedDialogueEntry, SequenceLinkingData sequenceLinkingData, List sequenceList)
        {
            try
            {
                foreach (string linkedSequenceId in sequenceLinkingData.linkedIds)
                {
                    if (sequenceLinkingData.linksProcessed.Contains(linkedSequenceId))
                    {
                        continue;
                    }
                    SequenceLinkingData targetSequenceLinkingData = celtxData.sequenceLinkingDataList.Find(o => o.id.Equals(linkedSequenceId));
                    if (!targetSequenceLinkingData.isRoot)
                    {
                        CxContent targetCxSequenceContentObject = sequenceList.Find(c => c.attrs.id.Equals(targetSequenceLinkingData.id));
                        DialogueEntry targetSequenceLastCreatedDialogueEntry = ProcessTargetConversationSequence(targetCxSequenceContentObject, targetSequenceLinkingData, lastCreatedDialogueEntry, sourceCxId);
                        if (targetSequenceLastCreatedDialogueEntry != null)
                        {
                            ProcessConversationChildSequences(targetCxSequenceContentObject.attrs.id, targetSequenceLastCreatedDialogueEntry, targetSequenceLinkingData, sequenceList);
                        }
                    }
                    sequenceLinkingData.linksProcessed.Add(linkedSequenceId);
                }
                sequenceLinkingData.sequenceProcessingComplete = true;
            }
            catch (System.Exception e)
            {
                LogError(MethodBase.GetCurrentMethod(), e, sequenceLinkingData.id);
            }
        }
        private DialogueEntry ProcessTargetConversationSequence(CxContent targetSequenceContentObject, SequenceLinkingData sequenceLinkingData, DialogueEntry parentDialogueEntry, string sourceCxId)
        {
            try
            {
                string sequenceId = sequenceLinkingData.id;
                string sequenceName = targetSequenceContentObject.attrs.name;
                string sequenceCatalogId = targetSequenceContentObject.attrs.catalog_id;
                int conversationId = -1;
                conversationId = parentDialogueEntry.conversationID;
                Conversation conversation = database.GetConversation(conversationId);
                DialogueEntry targetEntry = null;
                foreach (DialogueEntry entry in conversation.dialogueEntries)
                {
                    if (Field.LookupValue(entry.fields, CeltxFields.CeltxId).Equals(sequenceId))
                    {
                        targetEntry = entry;
                        break;
                    }
                }
                if (targetEntry == null)
                {
                    targetEntry = CreateNextDialogueEntryForConversation(conversation, sequenceName, sequenceId);
                    AddSetVariableScript(targetEntry, sequenceCatalogId);
                }
                else
                {
                    string linkConditionId = GetLinkConditionId(sourceCxId, sequenceId);
                    if (parentDialogueEntry.outgoingLinks.Count > 0)
                    {
                        foreach (Link link in parentDialogueEntry.outgoingLinks)
                        {
                            string linkCxId = Field.LookupValue(conversation.GetDialogueEntry(link.destinationDialogueID).fields, CeltxFields.CeltxId);
                            string targetLinkId = Field.LookupValue(parentDialogueEntry.fields, CeltxFields.CeltxId) + "-" + Field.LookupValue(targetEntry.fields, CeltxFields.CeltxId);
                            if (linkCxId.Contains(targetLinkId))
                            {
                                return targetEntry;
                            }
                        }
                    }
                }
                LinkDialogueEntries(parentDialogueEntry, targetEntry, GetLinkConditionId(sourceCxId, sequenceId));
                // Process actual sequence node contents
                if (!sequenceLinkingData.sequenceProcessingComplete)
                {
                    return ProcessSequenceNodeContents(targetEntry, targetSequenceContentObject);
                }
                else
                {
                    return null;
                }
            }
            catch (System.Exception e)
            {
                LogError(MethodBase.GetCurrentMethod(), e, targetSequenceContentObject.attrs.id);
                return null;
            }
        }
        private List GetSequencePageContents(CxContent cxSequenceContentObject)
        {
            if (cxSequenceContentObject.content != null && cxSequenceContentObject.content.Count > 0)
            {
                CxContent page = cxSequenceContentObject.content[0];
                if (page.type.Equals("cxpage"))
                {
                    return page.content;
                }
            }
            return null;
        }
        private DialogueEntry ProcessSequenceNodeContents(DialogueEntry initialEntry, CxContent cxSequenceContentObject)
        {
            DialogueEntry currentEntry = initialEntry;
            Conversation conversation = database.GetConversation(currentEntry.conversationID);
            try
            {
                List sequencePageContentList = cxSequenceContentObject.content[0].content;
                int entryCount = 1;
                bool createNewEntry = false;
                foreach (CxContent pageContentItem in sequencePageContentList)
                {
                    if (!pageContentItem.type.Equals("cxgameplay") &&
                        !pageContentItem.type.Equals("cxcharacter") &&
                        !pageContentItem.type.Equals("cxparenthetical") &&
                        !pageContentItem.type.Equals("cxdialog"))
                    {
                        continue;
                    }
                    string combinedText = GetCombinedText(pageContentItem.content);
                    if (createNewEntry)
                    {
                        entryCount += 1;
                        currentEntry = CreateAdditionalEntryForSequence(conversation, currentEntry, initialEntry, entryCount);
                        createNewEntry = false;
                    }
                    switch (pageContentItem.type)
                    {
                        case "cxgameplay":
                            if (StringStartsWithTag(combinedText, "[SEQ]"))
                            {
                                currentEntry.Sequence = GetTextWithoutTag(combinedText);
                            }
                            else if (StringStartsWithTag(combinedText, "[COND]"))
                            {
                                if (!string.IsNullOrEmpty(currentEntry.conditionsString)) currentEntry.conditionsString += ";\n";
                                currentEntry.conditionsString += GetTextWithoutTag(combinedText);
                            }
                            else if (StringStartsWithTag(combinedText, "[SCRIPT]"))
                            {
                                if (!string.IsNullOrEmpty(currentEntry.userScript)) currentEntry.userScript += ";\n";
                                currentEntry.userScript += GetTextWithoutTag(combinedText);
                            }
                            break;
                        case "cxcharacter":
                            currentEntry.ActorID = GetActorIdForCharacter(pageContentItem);
                            if (currentEntry.ActorID == conversation.ActorID)
                            {
                                currentEntry.ConversantID = conversation.ConversantID;
                            }
                            else
                            {
                                currentEntry.ConversantID = conversation.ActorID;
                            }
                            break;
                        case "cxparenthetical":
                            if (StringStartsWithTag(combinedText, "[C]"))
                            {
                                currentEntry.ConversantID = database.GetActor(GetTextWithoutTag(combinedText).ToUpper()).id;
                            }
                            else if (StringStartsWithTag(combinedText, "[VO]"))
                            {
                                AppendToField(currentEntry.fields, CeltxFields.VoiceOverFile, GetTextWithoutTag(combinedText), FieldType.Files);
                            }
                            else
                            {
                                currentEntry.MenuText = combinedText;
                            }
                            break;
                        case "cxdialog":
                            currentEntry.DialogueText = GetCombinedText(pageContentItem.content);
                            createNewEntry = true;
                            break;
                    }
                }
            }
            catch (System.Exception e)
            {
                LogError(MethodBase.GetCurrentMethod(), e, cxSequenceContentObject.attrs.id);
            }
            return currentEntry;
        }
        private DialogueEntry CreateAdditionalEntryForSequence(Conversation conversation, DialogueEntry currentEntry, DialogueEntry initialEntry, int entryCount)
        {
            DialogueEntry newEntry = CreateNextDialogueEntryForConversation(conversation, initialEntry.Title + "-" + entryCount,
                Field.LookupValue(initialEntry.fields, CeltxFields.CeltxId) + "-" + entryCount);
            newEntry.ActorID = currentEntry.ActorID;
            newEntry.ConversantID = currentEntry.ConversantID;
            LinkDialogueEntries(currentEntry, newEntry, null);
            return newEntry;
        }
        private void AddSetVariableScript(DialogueEntry entry, string sequenceCatalogId)
        {
            try
            {
                if (celtxData.customVarListLookupByCxSequenceId.ContainsKey(sequenceCatalogId))
                {
                    if (!celtxData.customVarListLookupByCxSequenceId.ContainsKey(sequenceCatalogId)) Debug.LogError("Celtx Import: Can't find custom variable list for sequence ID " + sequenceCatalogId);
                    List cxCustomVars = celtxData.customVarListLookupByCxSequenceId[sequenceCatalogId];
                    StringBuilder luaScript = new StringBuilder();
                    foreach (CxCustomVar cxCustomVar in cxCustomVars)
                    {
                        if (luaScript.Length != 0) { luaScript.Append("; "); }
                        if (!celtxData.variableLookupByCeltxId.ContainsKey(cxCustomVar.id)) Debug.LogError("Celtx Import: Can't find variable with ID " + cxCustomVar.id);
                        Variable var = database.GetVariable(celtxData.variableLookupByCeltxId[cxCustomVar.id]);
                        if (var == null) Debug.LogError("Celtx Import: Dialogue database doesn't contain variable named " + celtxData.variableLookupByCeltxId[cxCustomVar.id]);
                        StringBuilder stringBuilder = new StringBuilder();
                        stringBuilder.Append("Variable");
                        stringBuilder.Append("[\"" + var.Name + "\"]");
                        stringBuilder.Append(" = ");
                        if (var.Type == FieldType.Boolean)
                        {
                            if (!cxCustomVar.boolVal.ToString().Equals(var.InitialValue)) { stringBuilder.Append(cxCustomVar.boolVal.ToString().ToLower()); }
                            else { stringBuilder.Append(var.InitialValue); }
                        }
                        else if (var.Type == FieldType.Number)
                        {
                            if (!cxCustomVar.longVal.ToString().Equals(var.InitialValue)) { stringBuilder.Append(cxCustomVar.longVal.ToString()); }
                            else { stringBuilder.Append(var.InitialValue); }
                        }
                        else if (var.Type == FieldType.Text)
                        {
                            if (cxCustomVar.type != null && cxCustomVar.type.Equals("radio"))
                            {
                                stringBuilder.Append(cxCustomVar.config.options[(int)cxCustomVar.longVal].strVal);
                            }
                            else
                            {
                                if (cxCustomVar.strVal != null && !cxCustomVar.strVal.Equals(var.InitialValue)) { stringBuilder.Append("\"" + cxCustomVar.strVal + "\""); }
                                else { stringBuilder.Append("\"" + var.InitialValue + "\""); }
                            }
                        }
                        luaScript.Append(stringBuilder.ToString());
                    }
                    entry.userScript = luaScript.ToString();
                }
            }
            catch (System.Exception e)
            {
                LogError(MethodBase.GetCurrentMethod(), e, "SetVar[" + sequenceCatalogId + "]");
            }
        }
        private int GetActorIdForCharacter(CxContent characterContentItem)
        {
            if (!celtxData.actorIdLookupByCxCharacterCatalogId.ContainsKey(characterContentItem.attrs.catalog_id)) Debug.LogError("Celtx Import: Lookup failed for actor with ID " + characterContentItem.attrs.catalog_id);
            return celtxData.actorIdLookupByCxCharacterCatalogId[characterContentItem.attrs.catalog_id];
        }
        private DialogueEntry CreateNextDialogueEntryForConversation(Conversation conversation, string title, string celtxId, bool isGroup = false)
        {
            try
            {
                DialogueEntry dialogueEntry = template.CreateDialogueEntry(template.GetNextDialogueEntryID(conversation), conversation.id, title);
                conversation.dialogueEntries.Add(dialogueEntry);
                dialogueEntry.isGroup = isGroup;
                dialogueEntry.ActorID = conversation.ActorID;
                dialogueEntry.ConversantID = conversation.ConversantID;
                if (celtxId != null)
                {
                    Field.SetValue(dialogueEntry.fields, CeltxFields.CeltxId, celtxId);
                    celtxData.dialogueEntryLookupByCeltxId.Add(celtxId, dialogueEntry);
                }
                return dialogueEntry;
            }
            catch (System.Exception e)
            {
                LogError(MethodBase.GetCurrentMethod(), e, celtxId, title);
            }
            return null;
        }
        private void ConvertBlankNodesToGroupNodes()
        {
            foreach (Conversation conversation in database.conversations)
            {
                foreach (DialogueEntry entry in conversation.dialogueEntries)
                {
                    var isBlankNode = string.IsNullOrEmpty(entry.DialogueText) &&
                        string.IsNullOrEmpty(entry.MenuText) &&
                        string.IsNullOrEmpty(entry.Sequence);
                    if (isBlankNode)
                    {
                        entry.isGroup = true;
                    }
                }
            }
        }
        #endregion
        #region Linking
        private string GetLinkConditionId(string sourceCeltxId, string destinationCeltxId)
        {
            try
            {
                foreach (CeltxCondition condition in celtxData.conditions)
                {
                    foreach (CxOnObj onObj in condition.links)
                    {
                        if (onObj.from.Equals(sourceCeltxId) && onObj.to.Equals(destinationCeltxId))
                        {
                            return condition.id;
                        }
                    }
                }
            }
            catch (System.Exception e)
            {
                LogError(MethodBase.GetCurrentMethod(), e, sourceCeltxId + "-" + destinationCeltxId);
            }
            return null;
        }
        private void LinkDialogueEntries(DialogueEntry source, DialogueEntry destination, string conditionId)
        {
            try
            {
                if (conditionId == null)
                {
                    source.outgoingLinks.Add(new Link(source.conversationID, source.id, destination.conversationID, destination.id));
                }
                else
                {
                    CeltxCondition condition = celtxData.conditions.Find(c => c.id.Equals(conditionId));
                    DialogueEntry entryToLinkToCondition = source;
                    if (condition.delay)
                    {
                        DialogueEntry delayEntry = CreateNextDialogueEntryForConversation(database.GetConversation(source.conversationID),
                            "[D]-" + condition.name,
                            "D-" + Field.LookupValue(source.fields, CeltxFields.CeltxId) + "-" + Field.LookupValue(destination.fields, CeltxFields.CeltxId), true);
                        source.outgoingLinks.Add(new Link(source.conversationID, source.id, delayEntry.conversationID, delayEntry.id));
                        entryToLinkToCondition = delayEntry;
                    }
                    DialogueEntry conditionEntry = CreateNextDialogueEntryForConversation(database.GetConversation(source.conversationID),
                        "[COND]" + condition.name,
                        "C-" + Field.LookupValue(source.fields, CeltxFields.CeltxId) + "-" + Field.LookupValue(destination.fields, CeltxFields.CeltxId), true);
                    conditionEntry.conditionsString = condition.luaConditionString;
                    entryToLinkToCondition.outgoingLinks.Add(new Link(entryToLinkToCondition.conversationID, entryToLinkToCondition.id, conditionEntry.conversationID, conditionEntry.id));
                    conditionEntry.outgoingLinks.Add(new Link(conditionEntry.conversationID, conditionEntry.id, destination.conversationID, destination.id));
                }
            }
            catch (System.Exception e)
            {
                LogError(MethodBase.GetCurrentMethod(), e, source.Title + "-" + destination.Title + "-" + conditionId);
            }
        }
        #endregion
        #region Text Processing
        private bool StringStartsWithTag(string stringToCheck, string targetTag)
        {
            if (stringToCheck == null || targetTag == null) { return false; }
            return stringToCheck.StartsWith(targetTag, System.StringComparison.OrdinalIgnoreCase);
        }
        private string GetTextWithoutTag(string text)
        {
            if (string.IsNullOrEmpty(text)) { return ""; }
            //return System.Text.RegularExpressions.Regex.Split(text, "(\\[.*\\])")[2].Trim();
            var endTagPos = text.IndexOf(']');
            return (endTagPos == -1) ? "" : text.Substring(endTagPos + 1);
        }
        private string GetCombinedText(List contentList)
        {
            if (contentList == null) { return ""; }
            StringBuilder stringBuilder = new StringBuilder();
            foreach (CxContent content in contentList)
            {
                if (content.type.Equals("text"))
                {
                    stringBuilder.Append(GetTextWithMarks(content));
                }
            }
            return stringBuilder.ToString();
        }
        private string GetTextWithMarks(CxContent textContent)
        {
            try
            {
                string text = textContent.text;
                if (textContent.marks != null && textContent.marks.Count > 0)
                {
                    string prependString = "";
                    string appendString = "";
                    foreach (CxContent mark in textContent.marks)
                    {
                        switch (mark.type)
                        {
                            case "strong":
                                prependString = prependString + "";
                                appendString = "" + appendString;
                                break;
                            case "em":
                                prependString = prependString + "";
                                appendString = "" + appendString;
                                break;
                            case "underline":
                                prependString = prependString + "";
                                appendString = "" + appendString;
                                break;
                        }
                    }
                    return prependString + text + appendString;
                }
                else
                {
                    return text;
                }
            }
            catch (System.Exception e)
            {
                LogError(MethodBase.GetCurrentMethod(), e, textContent.attrs.id);
                return "";
            }
        }
        #endregion
        #region Catalog Data Processing
        private void ConvertCharacterCatalogEntryToDSActor(CxAttrs catalogItemAttrs)
        {
            try
            {
                Actor actor = database.GetActor(catalogItemAttrs.title);
                if (actor == null)
                {
                    int actorID = template.GetNextActorID(database);
                    actor = template.CreateActor(actorID, catalogItemAttrs.title, IsPlayerCharacter(catalogItemAttrs));
                    database.actors.Add(actor);
                }
                else
                {
                    var originalActor = (database.syncInfo.syncActors && database.syncInfo.syncActorsDatabase != null)
                        ? database.syncInfo.syncActorsDatabase.GetActor(actor.Name) : null;
                    if (originalActor != null)
                    {
                        AppendToField(originalActor.fields, CeltxFields.CatalogId, catalogItemAttrs.id, FieldType.Text);
                        AppendToField(originalActor.fields, CeltxFields.Description, catalogItemAttrs.item_data.description, FieldType.Text);
                        AppendToField(originalActor.fields, CeltxFields.Pictures, getPictureString(catalogItemAttrs), FieldType.Files);
#if UNITY_EDITOR
                        UnityEditor.EditorUtility.SetDirty(database.syncInfo.syncActorsDatabase);
#endif
                    }
                    else
                    {
                        Debug.LogWarning("Celtx Import: Actor " + catalogItemAttrs.title + " was already added with Celtx ID " + actor.LookupValue(CeltxFields.CatalogId) + " but another actor with same name has Celtx ID " + catalogItemAttrs.id);
                    }
                }
                AppendToField(actor.fields, CeltxFields.CatalogId, catalogItemAttrs.id, FieldType.Text);
                AppendToField(actor.fields, CeltxFields.Description, catalogItemAttrs.item_data.description, FieldType.Text);
                AppendToField(actor.fields, CeltxFields.Pictures, getPictureString(catalogItemAttrs), FieldType.Files);
                celtxData.actorIdLookupByCxCharacterCatalogId[catalogItemAttrs.id] = actor.id;
            }
            catch (System.Exception e)
            {
                LogError(MethodBase.GetCurrentMethod(), e, catalogItemAttrs.id, catalogItemAttrs.title);
            }
        }
        private bool IsPlayerCharacter(CxAttrs characterAttrs)
        {
            try
            {
                if (characterAttrs.item_data == null)
                {
                    LogWarning(GetNullDataString("attrs.item_data") +
                        "Cannot determine if character is a player. Please ensure Character Type is set in the catalog", characterAttrs.id, characterAttrs.title);
                    return false;
                }
                if (characterAttrs.item_data.character_type == null)
                {
                    LogWarning(GetNullDataString("attrs.item_data.character_type") +
                        "Defaulting to non-player Actor. Please ensure Character Type is set in the catalog", characterAttrs.id, characterAttrs.title);
                    return false;
                }
                return characterAttrs.item_data.character_type.Equals("pc");
            }
            catch (System.Exception e)
            {
                LogError(MethodBase.GetCurrentMethod(), e, characterAttrs.id, characterAttrs.title);
            }
            return false;
        }
        private string getPictureString(CxAttrs catalogItemAttrs)
        {
            try
            {
                StringBuilder stringBuilder = new StringBuilder();
                stringBuilder.Append("[");
                if (catalogItemAttrs.item_data.media != null && catalogItemAttrs.item_data.media.Count > 0)
                {
                    foreach (CxMedia media in catalogItemAttrs.item_data.media)
                    {
                        stringBuilder.Append(media.name);
                        if (media.Equals(catalogItemAttrs.item_data.media.Last()))
                        {
                            stringBuilder.Append("]");
                        }
                        else
                        {
                            stringBuilder.Append(";");
                        }
                    }
                    return stringBuilder.ToString();
                }
                else
                {
                    return "[]";
                }
            }
            catch (System.Exception e)
            {
                LogError(MethodBase.GetCurrentMethod(), e, catalogItemAttrs.id, catalogItemAttrs.title);
            }
            return "[]";
        }
        private void ConvertLocationCatalogEntryToDSLocation(CxAttrs catalogItemAttrs)
        {
            try
            {
                Location location = database.GetLocation(catalogItemAttrs.title);
                if (location == null)
                {
                    int locationId = template.GetNextLocationID(database);
                    location = template.CreateLocation(locationId, catalogItemAttrs.title);
                    database.locations.Add(location);
                }
                else
                {
                    var originalLocation = (database.syncInfo.syncLocations && database.syncInfo.syncLocationsDatabase != null)
                        ? database.syncInfo.syncLocationsDatabase.GetLocation(location.Name) : null;
                    if (originalLocation != null)
                    {
                        AppendToField(originalLocation.fields, CeltxFields.CatalogId, catalogItemAttrs.id, FieldType.Text);
                        AppendToField(originalLocation.fields, CeltxFields.Description, catalogItemAttrs.item_data.description, FieldType.Text);
#if UNITY_EDITOR
                        UnityEditor.EditorUtility.SetDirty(database.syncInfo.syncLocationsDatabase);
#endif
                    }
                }
                AppendToField(location.fields, CeltxFields.CatalogId, catalogItemAttrs.id, FieldType.Text);
                AppendToField(location.fields, CeltxFields.Description, catalogItemAttrs.item_data.description, FieldType.Text);
            }
            catch (System.Exception e)
            {
                LogError(MethodBase.GetCurrentMethod(), e, catalogItemAttrs.id, catalogItemAttrs.title);
            }
        }
        private void ConvertItemCatalogEntryToDSItem(CxAttrs catalogItemAttrs)
        {
            try
            {
                Item item = database.GetItem(catalogItemAttrs.title);
                if (item == null)
                {
                    int itemId = template.GetNextItemID(database);
                    item = template.CreateItem(itemId, catalogItemAttrs.title);
                    database.items.Add(item);
                }
                else
                {
                    var originalItem = (database.syncInfo.syncItems && database.syncInfo.syncItemsDatabase != null)
                        ? database.syncInfo.syncItemsDatabase.GetItem(item.Name) : null;
                    if (originalItem != null)
                    {
                        AppendToField(originalItem.fields, CeltxFields.CatalogId, catalogItemAttrs.id, FieldType.Text);
                        AppendToField(originalItem.fields, CeltxFields.Description, catalogItemAttrs.item_data.description, FieldType.Text);
                        AppendToField(originalItem.fields, CeltxFields.ItemType, catalogItemAttrs.item_data.item_type, FieldType.Text);
                        AppendToField(originalItem.fields, CeltxFields.ItemProperties, catalogItemAttrs.item_data.item_properties, FieldType.Text);
                        AppendToField(originalItem.fields, CeltxFields.ItemAvailability, catalogItemAttrs.item_data.item_availability, FieldType.Text);
#if UNITY_EDITOR
                        UnityEditor.EditorUtility.SetDirty(database.syncInfo.syncItemsDatabase);
#endif
                    }
                }
                AppendToField(item.fields, CeltxFields.CatalogId, catalogItemAttrs.id, FieldType.Text);
                AppendToField(item.fields, CeltxFields.Description, catalogItemAttrs.item_data.description, FieldType.Text);
                AppendToField(item.fields, CeltxFields.ItemType, catalogItemAttrs.item_data.item_type, FieldType.Text);
                AppendToField(item.fields, CeltxFields.ItemProperties, catalogItemAttrs.item_data.item_properties, FieldType.Text);
                AppendToField(item.fields, CeltxFields.ItemAvailability, catalogItemAttrs.item_data.item_availability, FieldType.Text);
            }
            catch (System.Exception e)
            {
                LogError(MethodBase.GetCurrentMethod(), e, catalogItemAttrs.id, catalogItemAttrs.title);
            }
        }
        private void ExtractCustomVarsList(CxAttrs catalogItemAttrs)
        {
            try
            {
                string catalogId = catalogItemAttrs.id;
                List customVars = catalogItemAttrs.custom_vars;
                if (customVars != null && customVars.Count > 0)
                {
                    celtxData.customVarListLookupByCxSequenceId.Add(catalogId, customVars);
                }
            }
            catch (System.Exception e)
            {
                LogError(MethodBase.GetCurrentMethod(), e, catalogItemAttrs.id, catalogItemAttrs.title);
            }
        }
        #endregion
        #region Variables and Conditions
        private void ConvertCeltxVariableToDSVariable(CxVariable variableObject)
        {
            try
            {
                FieldType fieldType = FieldType.Text;
                string variableDefaultValue = "";
                List extraFields = new List();
                switch (variableObject.type)
                {
                    case "boolean":
                        variableDefaultValue = variableObject.config.defaultBool.ToString();
                        fieldType = FieldType.Boolean;
                        break;
                    case "number":
                    case "range":
                        variableDefaultValue = variableObject.config.defaultNum.ToString();
                        fieldType = FieldType.Number;
                        break;
                    case "date":
                    case "text":
                    case "textarea":
                    case "time":
                        variableDefaultValue = variableObject.config.defaultString;
                        fieldType = FieldType.Text;
                        break;
                    case "radio":
                        variableDefaultValue = variableObject.config.options[(int)variableObject.config.defaultNum].strVal;
                        fieldType = FieldType.Text;
                        break;
                }
                var variable = database.GetVariable(variableObject.name);
                if (variable == null)
                {
                    int variableId = template.GetNextVariableID(database);
                    variable = template.CreateVariable(variableId, variableObject.name, variableDefaultValue, fieldType);
                    database.variables.Add(variable);
                }
                else
                {
                    var originalVariable = (database.syncInfo.syncVariables && database.syncInfo.syncVariablesDatabase != null)
                        ? database.syncInfo.syncVariablesDatabase.GetVariable(variableObject.name) : null;
                    if (originalVariable != null)
                    {
                        AppendToField(originalVariable.fields, CeltxFields.Description, variableObject.desc, FieldType.Text);
#if UNITY_EDITOR
                        UnityEditor.EditorUtility.SetDirty(database.syncInfo.syncVariablesDatabase);
#endif
                    }
                }
                AppendToField(variable.fields, CeltxFields.Description, variableObject.desc, FieldType.Text);
                celtxData.variableLookupByCeltxId[variableObject.id] = variableObject.name;
            }
            catch (System.Exception e)
            {
                LogError(MethodBase.GetCurrentMethod(), e, variableObject.id, variableObject.name);
            }
        }
        private void ExtractCeltxCondition(CxAttrs conditionItemAttrs)
        {
            try
            {
                CeltxCondition condition = new CeltxCondition();
                condition.id = conditionItemAttrs.id;
                condition.catalogId = conditionItemAttrs.catalog_id;
                condition.name = conditionItemAttrs.name;
                if (conditionItemAttrs.desc != null)
                {
                    condition.delay = conditionItemAttrs.desc.Contains("[D]");
                }
                if (condition.delay) condition.description = conditionItemAttrs.desc.Substring(0, "[D]".Length);
                condition.links = conditionItemAttrs.on;
                condition.literals = conditionItemAttrs.literals;
                SetFollowingOperators(condition, conditionItemAttrs.clause);
                celtxData.conditions.Add(condition);
            }
            catch (System.Exception e)
            {
                LogError(MethodBase.GetCurrentMethod(), e, conditionItemAttrs.id, conditionItemAttrs.title);
            }
        }
        private void SetFollowingOperators(CeltxCondition condition, string clause)
        {
            try
            {
                List subSections = new List();
                List orSplit = new List();
                subSections.AddRange(System.Text.RegularExpressions.Regex.Split(clause, "(\\+|\\.)"));
                if (subSections.ElementAt(subSections.Count - 1).Equals("")) { subSections.RemoveAt(subSections.Count - 1); }
                for (int i = 0; i < subSections.Count - 1; i += 2)
                {
                    CxLiteral literal = condition.literals.Find(l => l.lit_name.Equals(subSections.ElementAt(i)));
                    literal.followingOp = subSections.ElementAt(i + 1);
                }
            }
            catch (System.Exception e)
            {
                LogError(MethodBase.GetCurrentMethod(), e, condition.id, condition.name);
            }
        }
        private void GenerateLuaCondition(CeltxCondition condition)
        {
            try
            {
                if (condition.literals == null)
                {
                    LogMessage(LogLevel.W,
                            "Condition has no literals(literals list is null). Lua script generation will be skipped for this condition. Please add literals to the condition in Celtx", condition.id);
                    return;
                }
                StringBuilder conditionString = new StringBuilder();
                string followingOp = null;
                foreach (CxLiteral literal in condition.literals)
                {
                    if (followingOp != null) { conditionString.Append(" " + followingOp + " "); }
                    if (literal.type.Equals("variable"))
                    {
                        conditionString.Append("Variable");
                        conditionString.Append("[\"" + literal.name + "\"]");
                        conditionString.Append(" " + GetLuaComparisonOperator(literal.comparisonOperator) + " ");
                        string comparisonVal;
                        if (literal.varType != null && literal.varType.Equals("radio"))
                        {
                            comparisonVal = literal.config.options[(int)literal.comparisonValue.longVal].strVal;
                        }
                        else
                        {
                            comparisonVal = literal.comparisonValue.strVal;
                        }
                        if (SurroundComparisonValueInQuotes(literal))
                        {
                            conditionString.Append("\"" + comparisonVal + "\"");
                        }
                        else
                        {
                            conditionString.Append(comparisonVal);
                        }
                    }
                    else if (literal.type.Equals("condition"))
                    {
                        conditionString.Append("(");
                        conditionString.Append(GetNestedConditionString(literal.conditionId));
                        conditionString.Append(") == ");
                        conditionString.Append(literal.comparisonValue.strVal);
                    }
                    else
                    {
                        LogMessage(LogLevel.W,
                            "Unsupported literal type(" + literal.type + ") in condition. Only variable and condition literal types are supported", literal.id);
                        continue;
                    }
                    if (literal.followingOp.Equals(".")) { followingOp = "and"; }
                    else if (literal.followingOp.Equals("+")) { followingOp = "or"; }
                    else
                    {
                        LogMessage(LogLevel.W,
                            "Unsupported following operator(" + literal.followingOp + ") in condition. Only \".\"(AND) and \"+\"(OR) are supported", literal.id);
                        continue;
                    }
                }
                condition.luaConditionString = conditionString.ToString();
            }
            catch (System.Exception e)
            {
                LogError(MethodBase.GetCurrentMethod(), e, condition.id, condition.name);
            }
        }
        private bool SurroundComparisonValueInQuotes(CxLiteral literal)
        {
            try
            {
                string variableId = literal.id;
                if (!celtxData.variableLookupByCeltxId.ContainsKey(variableId)) Debug.LogError("Celtx Import: Can't find variable with ID " + variableId);
                Variable var = database.GetVariable(celtxData.variableLookupByCeltxId[variableId]);
                if (var == null) Debug.LogError("Celtx Import: Dialogue database doesn't contain variable named " + celtxData.variableLookupByCeltxId[variableId]);
                FieldType varType = var.Type;
                if (varType == FieldType.Text)
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
            catch (System.Exception e)
            {
                LogError(MethodBase.GetCurrentMethod(), e, literal.id);
            }
            return false;
        }
        private string GetNestedConditionString(string conditionId)
        {
            try
            {
                CeltxCondition condition = celtxData.conditions.Find(c => c.id.Equals(conditionId));
                if (condition.luaConditionString == null)
                {
                    GenerateLuaCondition(condition);
                }
                return condition.luaConditionString;
            }
            catch (System.Exception e)
            {
                LogError(MethodBase.GetCurrentMethod(), e, conditionId);
            }
            return null;
        }
        private string GetLuaComparisonOperator(string literalOp)
        {
            switch (literalOp)
            {
                case "<":
                case "<=":
                case ">":
                case ">=":
                    return literalOp;
                case "=":
                    return "==";
                case "!=":
                    return "~=";
                case "exists":
                    LogMessage(LogLevel.W,
                        "Literal operator(" + literalOp + ") not supported. Defaulting to \"==\"");
                    return "==";
                case "does not exist":
                    LogMessage(LogLevel.W,
                        "Literal operator(" + literalOp + ") not supported. Defaulting to \"~=\"");
                    return "~=";
                default:
                    LogMessage(LogLevel.W,
                        "Unknown comparison operator(" + literalOp + ") in literal. Defaulting to \"==\"");
                    return "==";
            }
        }
        #endregion
        #region Check Sequence Syntax
        /// 
        /// Check syntax of all sequences in all dialogue entries in database.
        /// Log warning for any entries with syntax errors.
        /// 
        private void CheckSequenceSyntax()
        {
            if (database == null) return;
            var parser = new SequenceParser();
            foreach (Conversation conversation in database.conversations)
            {
                foreach (DialogueEntry entry in conversation.dialogueEntries)
                {
                    var sequence = entry.Sequence;
                    if (string.IsNullOrEmpty(sequence)) continue;
                    var result = parser.Parse(sequence);
                    if (result == null || result.Count == 0)
                    {
                        var text = entry.Title;
                        if (string.IsNullOrEmpty(text)) text = entry.subtitleText;
                        LogWarning("Dialogue entry " + conversation.id + ":" + entry.id +
                            " in conversation '" + conversation.Title + "' has syntax error in [SEQ] Sequence: " + sequence,
                            Field.LookupValue(entry.fields, "Celtx ID"), entry.Title);
                    }
                }
            }
        }
        #endregion
        #region Logging
        private string GetNullDataString(string nullFieldName)
        {
            return nullFieldName + " is null - ";
        }
        private string GetErrorString(MethodBase methodName, System.Exception ex)
        {
            return "Error in " + methodName + " : " + ex.ToString();
        }
        private void LogError(MethodBase methodName, System.Exception ex, string celtxId = null, string name = null)
        {
            string messageCore = GetErrorString(methodName, ex);
            LogMessage(LogLevel.E, messageCore, celtxId, name);
        }
        private void LogWarning(string messageCore, string celtxId, string name)
        {
            LogMessage(LogLevel.W, messageCore, celtxId, name);
        }
        private void LogMessage(LogLevel logLevel, string messageCore, string celtxId = null, string name = null)
        {
            StringBuilder logMessage = new StringBuilder();
            if (celtxId != null) { logMessage.Append("Celtx Object : " + celtxId + "(" + name + ")"); }
            logMessage.Append(" | " + messageCore);
            if (logLevel == LogLevel.W)
            {
                Debug.LogWarning(logMessage.ToString());
            }
            else if (logLevel == LogLevel.E)
            {
                Debug.LogError(logMessage.ToString());
            }
            else
            {
                Debug.Log(logMessage.ToString());
            }
        }
        enum LogLevel
        {
            W, E, I
        }
        #endregion
    }
}
#endif