// ^^^^^ LOAD STORY EDITOR ^^^^^ //

    // vvvvv SAVE STORY EDITOR vvvvv //

    // returns if it completed the save or not
    public static bool SaveItems(bool saveAs)
    {
        // use event regardless of outcome to prevent unexpected event pass-through
        Event.current.Use();

        if (mainEditor == null)
        {
            Debug.Log("Cannot Save: Story Editor reference unhooked!");
            return(false);
        }

        // open the file explorer save window if on a new file
        // otherwise, save to the current file
        string path;

        if (saveAs || (!saveAs && string.IsNullOrEmpty(mainEditor.fileName)))
        {
            path = EditorUtility.SaveFilePanel("Save Story Entry", ProjectPathManager.LastExportPath, "entry", "sdexml");
            mainEditor.fileName = path;
        }
        else
        {
            path = mainEditor.fileName;
        }

        if (string.IsNullOrEmpty(path))
        {
            Debug.Log("canceled save");
            return(false);
        }

        EditorStoryNodeEntry   storyEntry = new EditorStoryNodeEntry();
        List <EditorNodeEntry> nodes      = GenerateNodeEntries(mainEditor.nodes);
        List <string>          flags      = new List <string>();

        foreach (TextArea flag in mainEditor.localFlagsMenu.items)
        {
            flags.Add(flag.text);
        }

        // assign nodes/flags
        storyEntry.nodes      = nodes;
        storyEntry.localFlags = flags;
        storyEntry.offset     = mainEditor.offset;

        // write to disk
        XmlSerializer serializer = new XmlSerializer(typeof(EditorStoryNodeEntry));
        Encoding      encoding   = Encoding.GetEncoding("UTF-8");

        using (StreamWriter stream = new StreamWriter(path, false, encoding)) {
            serializer.Serialize(stream, storyEntry);
        }

        ProjectPathManager.LastExportPath = path;
        return(true);
    }
    /*
     * InitializeNodes() is a helper function for loading Story Dialog Editor data.
     *
     * Returns the mapping of input connection points and their associated output connection points
     */
    private static Dictionary <int, List <int> > InitializeNodes(EditorStoryNodeEntry storyEntry, Dictionary <int, ConnectionPoint> connectionPointMap)
    {
        // the map of input points and their associated output points
        Dictionary <int, List <int> > connectionMap = new Dictionary <int, List <int> >();

        // generate nodes and data from entries
        Node tempNode;

        foreach (EditorNodeEntry entry in storyEntry.nodes)
        {
            tempNode = NodeManager.AddNodeAt(new Vector2(entry.rect.x, entry.rect.y), entry.nodeType, markHistory: false, center: false);
            connectionPointMap[entry.inPoint.CPEID] = tempNode.inPoint;
            connectionMap[entry.inPoint.CPEID]      = entry.inPoint.linkedCPEIDs;

            // add flag data if set local/global
            if (entry.nodeType == NodeType.SetLocalFlag || entry.nodeType == NodeType.CheckLocalFlag)
            {
                tempNode.localFlagDropdown.selectedItem = mainEditor.localFlagsMenu.GetTextArea(entry.selectedFlag);
            }
            else if (entry.nodeType == NodeType.SetGlobalFlag || entry.nodeType == NodeType.CheckGlobalFlag)
            {
                tempNode.globalItemDropdown.selectedItem = entry.selectedFlag;
            }
            else if (entry.nodeType == NodeType.SetGlobalVariable || entry.nodeType == NodeType.CheckGlobalVariable)
            {
                tempNode.globalItemDropdown.selectedItem = entry.selectedFlag;
                tempNode.globalVariableField.text        = entry.globalVariableValue;
            }

            // map Node outpoint/splitter depending on NodeType
            if (entry.nodeType == NodeType.SetLocalFlag ||
                entry.nodeType == NodeType.SetGlobalFlag ||
                entry.nodeType == NodeType.SetGlobalVariable ||
                entry.nodeType == NodeType.Interrupt)
            {
                // add outpoint entry if available
                if (tempNode.outPoint != null && entry.outPoint != null)
                {
                    connectionPointMap[entry.outPoint.CPEID] = tempNode.outPoint;
                }
            }
            else if (entry.nodeType == NodeType.CheckLocalFlag ||
                     entry.nodeType == NodeType.CheckGlobalFlag ||
                     entry.nodeType == NodeType.CheckGlobalVariable)
            {
                // add splitter entries if available
                if (tempNode.splitter != null && entry.outPos != null && entry.outNeg != null)
                {
                    connectionPointMap[entry.outPos.CPEID] = tempNode.splitter.positiveOutpoint;
                    connectionPointMap[entry.outNeg.CPEID] = tempNode.splitter.negativeOutpoint;
                }
            }

            // record child container outpoints depending on NodeType and populate fields
            if (entry.nodeType == NodeType.Dialog || entry.nodeType == NodeType.Decision)
            {
                DBox child = tempNode.childContainer as DBox;
                SDEContainerEntry childEntry = entry.childContainer;

                while (childEntry != null)
                {
                    // set text and outpoint mapping for the child container
                    child.textArea.text = childEntry.text;
                    connectionPointMap[childEntry.outPoint.CPEID] = child.outPoint;

                    childEntry = childEntry.child;

                    // generate the child's child if there needs to be one
                    if (childEntry != null)
                    {
                        switch (entry.nodeType)
                        {
                        case NodeType.Dialog:
                            child.child = ScriptableObject.CreateInstance <DialogBox>();
                            ((DialogBox)child.child).Init(child, "");
                            break;

                        case NodeType.Decision:
                            child.child = ScriptableObject.CreateInstance <DecisionBox>();
                            ((DecisionBox)child.child).Init(child, "");
                            break;
                        }
                    }

                    child = child.child as DBox;
                }
            }
            else if (entry.nodeType == NodeType.Interrupt)
            {
                tempNode.SetBottomLevelInterrupt(entry.bottomLevel);

                DialogInterrupt   child;
                SDEContainerEntry childEntry = entry.childContainer;

                if (childEntry != null)
                {
                    // create the first child of the parent node
                    tempNode.childContainer = ScriptableObject.CreateInstance <DialogInterrupt>();
                    tempNode.childContainer.Init(tempNode);
                    ((DialogInterrupt)tempNode.childContainer).label.text = childEntry.text;

                    // record the connection point
                    connectionPointMap[childEntry.outPoint.CPEID] = tempNode.childContainer.outPoint;

                    child      = tempNode.childContainer as DialogInterrupt;
                    childEntry = childEntry.child;

                    while (childEntry != null)
                    {
                        // generate the child interrupt and populate it
                        child.child = ScriptableObject.CreateInstance <DialogInterrupt>();
                        ((DialogInterrupt)child.child).Init(child);
                        ((DialogInterrupt)child.child).label.text = childEntry.text;

                        // record the connection point
                        connectionPointMap[childEntry.outPoint.CPEID] = child.child.outPoint;

                        child      = child.child as DialogInterrupt;
                        childEntry = childEntry.child;
                    }
                }
            }
        }

        return(connectionMap);
    }
    // vvvvv LOAD STORY EDITOR vvvvv //

    public static void LoadItems(string path)
    {
        if (mainEditor == null)
        {
            // open a new window if it's unhooked
            Debug.Log("Window not found, opening new Story Dialog Editor window.");
            StoryDialogEditor.OpenWindow();
        }
        else if (HistoryManager.needsSave)
        {
            // create dialog entry to warn user that they have unsaved changes
            if (!EditorUtility.DisplayDialog("Load new entry", "Are you sure you want to open a new entry and close the current one?", "yes", "no"))
            {
                return;
            }
        }

        Debug.Log("attempting to load: " + path);

        EditorStoryNodeEntry storyEntry = new EditorStoryNodeEntry();

        XmlSerializer serializer = new XmlSerializer(typeof(EditorStoryNodeEntry));
        Encoding      encoding   = Encoding.GetEncoding("UTF-8");

        using (StreamReader stream = new StreamReader(path, encoding)) {
            storyEntry = serializer.Deserialize(stream) as EditorStoryNodeEntry;
        }

        // destroy the scene before populating it
        mainEditor.DestroyScene();

        // set the editor's offset to match the saved entry
        mainEditor.offset = storyEntry.offset;

        // load flags
        foreach (string entry in storyEntry.localFlags)
        {
            mainEditor.localFlagsMenu.AddItem(entry, markHistory: false);
        }

        // CPEID maps to ConnectionPoint to generate connections later
        Dictionary <int, ConnectionPoint> connectionPointMap = new Dictionary <int, ConnectionPoint>();

        // initialize all the nodes and generate the connection map
        Dictionary <int, List <int> > connectionMap = InitializeNodes(storyEntry, connectionPointMap);

        // create all the connections
        foreach (int inCPEID in connectionMap.Keys)
        {
            ConnectionManager.selectedInPoint = connectionPointMap[inCPEID];

            bool deletable = true;
            if (((Node)ConnectionManager.selectedInPoint.parent).nodeType == NodeType.Interrupt)
            {
                deletable = false;
            }
            foreach (int outCPEID in connectionMap[inCPEID])
            {
                ConnectionManager.selectedOutPoint = connectionPointMap[outCPEID];
                ConnectionManager.CreateConnection(deletable, markHistory: false);
            }
        }
        ConnectionManager.ClearConnectionSelection();

        Debug.Log("loaded: " + path);
        mainEditor.fileName = path;
    }