// Copyright (c) Lawrence Livermore National Security, LLC and other VisIt
// Project developers.  See the top-level LICENSE file for dates and other
// details.  No copyright assignment is required to contribute to VisIt.

#include <ColorTableAttributes.h>
#include <DataNode.h>
#include <DebugStream.h>
#include <ColorControlPoint.h>
#include <map>
#include <algorithm>
#include <ColorControlPointList.h>

constexpr int ADDTAG    = 0;
constexpr int REMOVETAG = 1;

// It is time to say goodbye to the tagList. It has now been broken up into a
// series of vectors that are proper class members. We will describe each one:

// Each of the following has the same number of entries; one for each tag:
// 
// tagListNames - houses the names of the tags.
// tagListActive - a flag indicating whether or not the tag is enabled.
// tagListNumRefs - a count of how many color tables have this tag.
// tagListTableItemFlag - a flag indicating if the tag table in the color
//    table window has an entry for this tag.

// Tag changes are no longer a part of the tag list. The following three
// vectors are the same length; they have one entry for each tag change that
// is made.
// 
// tagChangesTag - the name of the tag that is being added to or removed 
//    from a color table.
// tagChangesType - a constant equal to either ADDTAG (0) or REMOVETAG (1).
// tagChangesCTName - the name of the color table (std::string) being added
//    to or removed from.

// These vectors are updated on the fly to reflect the latest tag 
// changes. They are also used to determine if a tag change is legal
// (particularly if a color table is built in), and they are saved out to 
// state files.

// There are also "deferred" versions of the previous three vectors. The idea
// is that tag changes may be applied *before* all color tables have been 
// loaded. If that happens, we store the tag changes in the deferred tag 
// changes vectors, which are then applied when the color table they belong to
// is added to the CTAtts.

// ****************************************************************************
// Method: ColorTableAttributes::ColorTableAttributes
//
// Purpose:
//   Init utility for the ColorTableAttributes class.
//
// Note:       Autogenerated by xml2atts.
//
// Programmer: xml2atts
// Creation:   omitted
//
// Modifications:
//
// ****************************************************************************

void ColorTableAttributes::Init()
{
    defaultContinuous = "hot";
    defaultDiscrete = "levels";
    tagsMatchAny = true;
    tagListNames.push_back("Default"); // add the "Default" tag to the tag list.
    tagListNames.push_back("User Defined"); // add the "User Defined" tag to the tag list.
    tagListActive.push_back(true); // set the "Default" tag to on.
    tagListActive.push_back(true); // set the "User Defined" tag to on.
    tagListNumRefs.push_back(0); // the "Default" tag has no refs.
    tagListNumRefs.push_back(0); // the "User Defined" tag has no refs.
    tagListTableItemFlag.push_back(false); // the "Default" tag has no tag table entry.
    tagListTableItemFlag.push_back(false); // the "User Defined" tag has no tag table entry.

    ColorTableAttributes::SelectAll();
}

// ****************************************************************************
// Method: ColorTableAttributes::ColorTableAttributes
//
// Purpose:
//   Copy utility for the ColorTableAttributes class.
//
// Note:       Autogenerated by xml2atts.
//
// Programmer: xml2atts
// Creation:   omitted
//
// Modifications:
//
// ****************************************************************************

void ColorTableAttributes::Copy(const ColorTableAttributes &obj)
{
    AttributeGroupVector::const_iterator pos;

    colorTableNames = obj.colorTableNames;
    colorTableActiveFlags = obj.colorTableActiveFlags;
    // *** Copy the colorTables field ***
    // Delete the AttributeGroup objects and clear the vector.
    for(pos = colorTables.begin(); pos != colorTables.end(); ++pos)
        delete *pos;
    colorTables.clear();
    if(obj.colorTables.size() > 0)
        colorTables.reserve(obj.colorTables.size());
    // Duplicate the colorTables from obj.
    for(pos = obj.colorTables.begin(); pos != obj.colorTables.end(); ++pos)
    {
        ColorControlPointList *oldColorControlPointList = (ColorControlPointList *)(*pos);
        ColorControlPointList *newColorControlPointList = new ColorControlPointList(*oldColorControlPointList);
        colorTables.push_back(newColorControlPointList);
    }

    defaultContinuous = obj.defaultContinuous;
    defaultDiscrete = obj.defaultDiscrete;
    tagsMatchAny = obj.tagsMatchAny;
    tagListNames = obj.tagListNames;
    tagListActive = obj.tagListActive;
    tagListNumRefs = obj.tagListNumRefs;
    tagListTableItemFlag = obj.tagListTableItemFlag;
    tagChangesTag = obj.tagChangesTag;
    tagChangesType = obj.tagChangesType;
    tagChangesCTName = obj.tagChangesCTName;
    deferredTagChangesTag = obj.deferredTagChangesTag;
    deferredTagChangesType = obj.deferredTagChangesType;
    deferredTagChangesCTName = obj.deferredTagChangesCTName;

    ColorTableAttributes::SelectAll();
}

// Type map format string
const char *ColorTableAttributes::TypeMapFormatString = COLORTABLEATTRIBUTES_TMFS;
const AttributeGroup::private_tmfs_t ColorTableAttributes::TmfsStruct = {COLORTABLEATTRIBUTES_TMFS};


// ****************************************************************************
// Method: ColorTableAttributes::ColorTableAttributes
//
// Purpose:
//   Default constructor for the ColorTableAttributes class.
//
// Note:       Autogenerated by xml2atts.
//
// Programmer: xml2atts
// Creation:   omitted
//
// Modifications:
//
// ****************************************************************************

ColorTableAttributes::ColorTableAttributes() :
    AttributeSubject(ColorTableAttributes::TypeMapFormatString)
{
    ColorTableAttributes::Init();
}

// ****************************************************************************
// Method: ColorTableAttributes::ColorTableAttributes
//
// Purpose:
//   Constructor for the derived classes of ColorTableAttributes class.
//
// Note:       Autogenerated by xml2atts.
//
// Programmer: xml2atts
// Creation:   omitted
//
// Modifications:
//
// ****************************************************************************

ColorTableAttributes::ColorTableAttributes(private_tmfs_t tmfs) :
    AttributeSubject(tmfs.tmfs)
{
    ColorTableAttributes::Init();
}

// ****************************************************************************
// Method: ColorTableAttributes::ColorTableAttributes
//
// Purpose:
//   Copy constructor for the ColorTableAttributes class.
//
// Note:       Autogenerated by xml2atts.
//
// Programmer: xml2atts
// Creation:   omitted
//
// Modifications:
//
// ****************************************************************************

ColorTableAttributes::ColorTableAttributes(const ColorTableAttributes &obj) :
    AttributeSubject(ColorTableAttributes::TypeMapFormatString)
{
    ColorTableAttributes::Copy(obj);
}

// ****************************************************************************
// Method: ColorTableAttributes::ColorTableAttributes
//
// Purpose:
//   Copy constructor for derived classes of the ColorTableAttributes class.
//
// Note:       Autogenerated by xml2atts.
//
// Programmer: xml2atts
// Creation:   omitted
//
// Modifications:
//
// ****************************************************************************

ColorTableAttributes::ColorTableAttributes(const ColorTableAttributes &obj, private_tmfs_t tmfs) :
    AttributeSubject(tmfs.tmfs)
{
    ColorTableAttributes::Copy(obj);
}

// ****************************************************************************
// Method: ColorTableAttributes::~ColorTableAttributes
//
// Purpose:
//   Destructor for the ColorTableAttributes class.
//
// Note:       Autogenerated by xml2atts.
//
// Programmer: xml2atts
// Creation:   omitted
//
// Modifications:
//
// ****************************************************************************

ColorTableAttributes::~ColorTableAttributes()
{
    AttributeGroupVector::iterator pos;

    // Destroy the colorTables field.
    for(pos = colorTables.begin(); pos != colorTables.end(); ++pos)
        delete *pos;
}

// ****************************************************************************
// Method: ColorTableAttributes::operator =
//
// Purpose:
//   Assignment operator for the ColorTableAttributes class.
//
// Note:       Autogenerated by xml2atts.
//
// Programmer: xml2atts
// Creation:   omitted
//
// Modifications:
//
// ****************************************************************************

ColorTableAttributes&
ColorTableAttributes::operator = (const ColorTableAttributes &obj)
{
    if (this == &obj) return *this;

    ColorTableAttributes::Copy(obj);

    return *this;
}

// ****************************************************************************
// Method: ColorTableAttributes::operator ==
//
// Purpose:
//   Comparison operator == for the ColorTableAttributes class.
//
// Note:       Autogenerated by xml2atts.
//
// Programmer: xml2atts
// Creation:   omitted
//
// Modifications:
//
// ****************************************************************************

bool
ColorTableAttributes::operator == (const ColorTableAttributes &obj) const
{
    bool colorTables_equal = (obj.colorTables.size() == colorTables.size());
    for(size_t i = 0; (i < colorTables.size()) && colorTables_equal; ++i)
    {
        // Make references to ColorControlPointList from AttributeGroup *.
        const ColorControlPointList &colorTables1 = *((const ColorControlPointList *)(colorTables[i]));
        const ColorControlPointList &colorTables2 = *((const ColorControlPointList *)(obj.colorTables[i]));
        colorTables_equal = (colorTables1 == colorTables2);
    }

    // Create the return value
    return ((colorTableNames == obj.colorTableNames) &&
            true /* can ignore colorTableActiveFlags */ &&
            colorTables_equal &&
            (defaultContinuous == obj.defaultContinuous) &&
            (defaultDiscrete == obj.defaultDiscrete) &&
            (tagsMatchAny == obj.tagsMatchAny) &&
            (tagListNames == obj.tagListNames) &&
            (tagListActive == obj.tagListActive) &&
            (tagListNumRefs == obj.tagListNumRefs) &&
            (tagListTableItemFlag == obj.tagListTableItemFlag) &&
            (tagChangesTag == obj.tagChangesTag) &&
            (tagChangesType == obj.tagChangesType) &&
            (tagChangesCTName == obj.tagChangesCTName) &&
            (deferredTagChangesTag == obj.deferredTagChangesTag) &&
            (deferredTagChangesType == obj.deferredTagChangesType) &&
            (deferredTagChangesCTName == obj.deferredTagChangesCTName));
}

// ****************************************************************************
// Method: ColorTableAttributes::operator !=
//
// Purpose:
//   Comparison operator != for the ColorTableAttributes class.
//
// Note:       Autogenerated by xml2atts.
//
// Programmer: xml2atts
// Creation:   omitted
//
// Modifications:
//
// ****************************************************************************

bool
ColorTableAttributes::operator != (const ColorTableAttributes &obj) const
{
    return !(this->operator == (obj));
}

// ****************************************************************************
// Method: ColorTableAttributes::TypeName
//
// Purpose:
//   Type name method for the ColorTableAttributes class.
//
// Note:       Autogenerated by xml2atts.
//
// Programmer: xml2atts
// Creation:   omitted
//
// Modifications:
//
// ****************************************************************************

const std::string
ColorTableAttributes::TypeName() const
{
    return "ColorTableAttributes";
}

// ****************************************************************************
// Method: ColorTableAttributes::CopyAttributes
//
// Purpose:
//   CopyAttributes method for the ColorTableAttributes class.
//
// Note:       Autogenerated by xml2atts.
//
// Programmer: xml2atts
// Creation:   omitted
//
// Modifications:
//
// ****************************************************************************

bool
ColorTableAttributes::CopyAttributes(const AttributeGroup *atts)
{
    if(TypeName() != atts->TypeName())
        return false;

    // Call assignment operator.
    const ColorTableAttributes *tmp = (const ColorTableAttributes *)atts;
    *this = *tmp;

    return true;
}

// ****************************************************************************
// Method: ColorTableAttributes::CreateCompatible
//
// Purpose:
//   CreateCompatible method for the ColorTableAttributes class.
//
// Note:       Autogenerated by xml2atts.
//
// Programmer: xml2atts
// Creation:   omitted
//
// Modifications:
//
// ****************************************************************************

AttributeSubject *
ColorTableAttributes::CreateCompatible(const std::string &tname) const
{
    AttributeSubject *retval = 0;
    if(TypeName() == tname)
        retval = new ColorTableAttributes(*this);
    // Other cases could go here too.

    return retval;
}

// ****************************************************************************
// Method: ColorTableAttributes::NewInstance
//
// Purpose:
//   NewInstance method for the ColorTableAttributes class.
//
// Note:       Autogenerated by xml2atts.
//
// Programmer: xml2atts
// Creation:   omitted
//
// Modifications:
//
// ****************************************************************************

AttributeSubject *
ColorTableAttributes::NewInstance(bool copy) const
{
    AttributeSubject *retval = 0;
    if(copy)
        retval = new ColorTableAttributes(*this);
    else
        retval = new ColorTableAttributes;

    return retval;
}

// ****************************************************************************
// Method: ColorTableAttributes::SelectAll
//
// Purpose:
//   Selects all attributes.
//
// Note:       Autogenerated by xml2atts.
//
// Programmer: xml2atts
// Creation:   omitted
//
// Modifications:
//
// ****************************************************************************

void
ColorTableAttributes::SelectAll()
{
    Select(ID_colorTableNames,          (void *)&colorTableNames);
    Select(ID_colorTableActiveFlags,    (void *)&colorTableActiveFlags);
    Select(ID_colorTables,              (void *)&colorTables);
    Select(ID_defaultContinuous,        (void *)&defaultContinuous);
    Select(ID_defaultDiscrete,          (void *)&defaultDiscrete);
    Select(ID_tagsMatchAny,             (void *)&tagsMatchAny);
    Select(ID_tagListNames,             (void *)&tagListNames);
    Select(ID_tagListActive,            (void *)&tagListActive);
    Select(ID_tagListNumRefs,           (void *)&tagListNumRefs);
    Select(ID_tagListTableItemFlag,     (void *)&tagListTableItemFlag);
    Select(ID_tagChangesTag,            (void *)&tagChangesTag);
    Select(ID_tagChangesType,           (void *)&tagChangesType);
    Select(ID_tagChangesCTName,         (void *)&tagChangesCTName);
    Select(ID_deferredTagChangesTag,    (void *)&deferredTagChangesTag);
    Select(ID_deferredTagChangesType,   (void *)&deferredTagChangesType);
    Select(ID_deferredTagChangesCTName, (void *)&deferredTagChangesCTName);
}

// ****************************************************************************
// Method: ColorTableAttributes::CreateSubAttributeGroup
//
// Purpose:
//   This class contains the list of colortables.
//
// Note:       Autogenerated by xml2atts.
//
// Programmer: xml2atts
// Creation:   omitted
//
// Modifications:
//
// ****************************************************************************

AttributeGroup *
ColorTableAttributes::CreateSubAttributeGroup(int)
{
    return new ColorControlPointList;
}

///////////////////////////////////////////////////////////////////////////////
// Persistence methods
///////////////////////////////////////////////////////////////////////////////

// ****************************************************************************
// Method: ColorTableAttributes::CreateNode
//
// Purpose:
//   This method creates a DataNode representation of the object so it can be saved to a config file.
//
// Note:       Autogenerated by classwriter.
//
// Programmer: classwriter
// Creation:   Mon Jun 11 10:41:36 PDT 2001
//
// Modifications:
//   Brad Whitlock, Mon Jun 18 22:03:23 PST 2001
//   Custom coded this method since classwriter's resulting output format
//   was ridiculous.
//
//   Brad Whitlock, Wed Nov 20 12:24:43 PDT 2002
//   I made it work with discrete colortables.
//
//   Brad Whitlock, Tue May 20 10:21:14 PDT 2003
//   I made it saved out only what needs to be saved out.
//
//   Brad Whitlock, Mon Jul 7 16:51:05 PST 2003
//   I prevented it from saving out colortables that are external.
//
//   Brad Whitlock, Wed Dec 17 12:21:24 PDT 2003
//   I added another bool argument.
//
//   Brad Whitlock, Tue Mar 13 10:40:04 PDT 2007
//   Updated due to code generator changes.
//
//   Brad Whitlock, Fri Dec 14 16:07:50 PST 2007
//   Made it use ids.
//
//   Hank Childs, Thu Jul  1 14:20:26 PDT 2010
//   Write out opacities.
//
//   Brad Whitlock, Fri Apr 27 14:16:01 PDT 2012
//   Change smoothing.
//
//  Justin Privitera, Fri May 20 11:02:45 PDT 2022
//  Replaced *active* w/ *default* for everything color tables.
// 
//   Justin Privitera, Thu Jun 16 18:01:49 PDT 2022
//   Added tags and removed categories.
// 
//   Justin Privitera, Wed Jul 27 12:16:06 PDT 2022
//   Removed logic that saved info about individual color tables.
// 
//   Justin Privitera, Mon Feb 13 14:32:02 PST 2023
//   Removed logic to save the tagging flag, as it no longer exists.
// 
//   Justin Privitera, Mon Aug 28 09:57:59 PDT 2023
//   Added logic to save all of the tagging infrastructure to a node.
// 
//   Justin Privitera, Wed Sep  6 11:52:18 PDT 2023
//   Fixed bug where I was saving out the tag changes multiple times.
//
// ****************************************************************************

bool
ColorTableAttributes::CreateNode(DataNode *parentNode, bool, bool)
{
    if(parentNode == 0)
        return true;

    // Create a node for ColorTableAttributes.
    DataNode *node = new DataNode("ColorTableAttributes");
    node->AddNode(new DataNode("defaultContinuous", defaultContinuous));
    node->AddNode(new DataNode("defaultDiscrete", defaultDiscrete));
    node->AddNode(new DataNode("tagsMatchAny", tagsMatchAny));

    // we only need to save out these two parts of the tag list
    node->AddNode(new DataNode("tagListNames", tagListNames));
    node->AddNode(new DataNode("tagListActive", tagListActive));

    // The goal is to create new vectors representing both the 
    // tag changes and the deferred tag changes. We do not want 
    // to add all the deferred tag changes to the regular tag
    // changes before saving out because execution can continue
    // beyond saving a session or config file, and it is incorrect
    // to have deferred tag changes ever enter the tag changes 
    // data structures without being applied. Therefore we create
    // three new vectors and combine the deferred and actual tag
    // changes into them to save out to nodes.

    std::vector<std::string> saveTagChangesTag;
    std::vector<int>         saveTagChangesType;
    std::vector<std::string> saveTagChangesCTName;
    // add the regular tag changes
    saveTagChangesTag.insert(saveTagChangesTag.end(), 
                             tagChangesTag.begin(),
                             tagChangesTag.end());
    saveTagChangesType.insert(saveTagChangesType.end(),
                              tagChangesType.begin(),
                              tagChangesType.end());
    saveTagChangesCTName.insert(saveTagChangesCTName.end(),
                                tagChangesCTName.begin(),
                                tagChangesCTName.end());
    // add the deferred tag changes
    saveTagChangesTag.insert(saveTagChangesTag.end(),
                             deferredTagChangesTag.begin(),
                             deferredTagChangesTag.end());
    saveTagChangesType.insert(saveTagChangesType.end(),
                              deferredTagChangesType.begin(),
                              deferredTagChangesType.end());
    saveTagChangesCTName.insert(saveTagChangesCTName.end(),
                                deferredTagChangesCTName.begin(),
                                deferredTagChangesCTName.end());

    // The use case for this is as follows: If a user has user-defined
    // color tables in their .visit directory, and they make tag changes
    // to them and then save out a config or session file, their changes
    // will be saved. Next time they run visit, if they have moved the 
    // user defined color tables, they will not be loaded, so the tag 
    // changes for those missing color tables will be added to the 
    // deferred tag changes. If they again save a config/session file,
    // they will want visit to honor their original tag changes if they 
    // put their user defined color tables back into visit. Hence we
    // must save the deferred tag changes, just in case the missing 
    // color table they belong to is ever brought back in a future session.

    // the tag changes are stored in three parallel vectors
    node->AddNode(new DataNode("tagChangesTag",    saveTagChangesTag));
    node->AddNode(new DataNode("tagChangesType",   saveTagChangesType));
    node->AddNode(new DataNode("tagChangesCTName", saveTagChangesCTName));

    // Add the node to the parent node.
    parentNode->AddNode(node);

    return true;
}
// ****************************************************************************
// Method: ColorTableAttributes::SetFromNode
//
// Purpose:
//   This method sets attributes in this object from values in a DataNode representation of the object.
//
// Note:       Autogenerated by classwriter.
//
// Programmer: classwriter
// Creation:   Mon Jun 11 10:41:36 PDT 2001
//
// Modifications:
//   Brad Whitlock, Mon Jun 18 23:43:59 PST 2001
//   I rewrote it so it matches the format that is written in CreateNode.
//
//   Brad Whitlock, Wed Nov 20 12:26:24 PDT 2002
//   I made it work with discrete color tables.
//
//   Hank Childs, Thu Jul  1 14:20:26 PDT 2010
//   Add support for opacities.
//
//  Justin Privitera, Fri May 20 11:02:45 PDT 2022
//  Replaced *active* w/ *default* for everything color tables.
// 
//   Justin Privitera, Thu Jun 16 18:01:49 PDT 2022
//   Added tags and removed categories.
// 
//   Justin Privitera, Wed Jul 27 12:16:06 PDT 2022
//   Removed logic that read info about individual color tables.
// 
//   Justin Privitera, Mon Feb 13 14:32:02 PST 2023
//   Removed logic to extract the tagging flag, as it no longer exists.
// 
//   Justin Privitera, Mon Aug 28 09:57:59 PDT 2023
//   Added logic to read all of the tagging infrastructure from a node.
//
//   Justin Privitera, Tue Sep  5 12:49:42 PDT 2023
//   Filter tables by tag at the end, once all data has been read from node.
// ****************************************************************************

void
ColorTableAttributes::SetFromNode(DataNode *parentNode)
{
    if(parentNode == 0)
        return;

    DataNode *searchNode = parentNode->GetNode("ColorTableAttributes");
    if(searchNode == 0)
        return;

    DataNode *node = 0;
    if((node = searchNode->GetNode("defaultContinuous")) != 0)
        SetDefaultContinuous(node->AsString());

    if((node = searchNode->GetNode("defaultDiscrete")) != 0)
        SetDefaultDiscrete(node->AsString());

    // For older version compatibility...
    if((node = searchNode->GetNode("defaultColorTable")) != 0)
        SetDefaultContinuous(node->AsString());

    if((node = searchNode->GetNode("tagsMatchAny")) != 0)
        tagsMatchAny = node->AsBool();

    if((node = searchNode->GetNode("tagListNames")) != 0)
    {
        stringVector tagListNamesFromNode = node->AsStringVector();
        for (auto tagname : tagListNamesFromNode)
            CreateTagListEntry(tagname, false, 0, false);

        // we only care about the list of active tags if we have the tag names
        DataNode *node2;
        if ((node2 = searchNode->GetNode("tagListActive")) != 0)
        {
            intVector tagListActiveFromNode = node2->AsIntVector();

            // if the tag name and tag active lists match up 1 to 1, as expected
            if (tagListActiveFromNode.size() == tagListNamesFromNode.size())
            {
                for (size_t tagId = 0; tagId < tagListNamesFromNode.size(); tagId ++)
                    SetTagActive(tagListNamesFromNode[tagId], tagListActiveFromNode[tagId]);
            }
            else
            {
                debug1 << "ColorTableAttributes WARNING: "
                       << "The color table tag names list and active tags list "
                       << "should be the same size. "
                       << "Has your config/session file been corrupted?";
            }
        }
    }

    DataNode *node2, *node3;
    // we need all three vectors to be present for this to work
    if ((node  = searchNode->GetNode("tagChangesTag"))    != 0 &&
        (node2 = searchNode->GetNode("tagChangesType"))   != 0 &&
        (node3 = searchNode->GetNode("tagChangesCTName")) != 0)
    {
        MergeTagChanges(node->AsStringVector(),
                        node2->AsIntVector(),
                        node3->AsStringVector());
    }

    // Once we have acquired all the tagging information, we can filter.
    FilterTablesByTag();
}
///////////////////////////////////////////////////////////////////////////////
// Set property methods
///////////////////////////////////////////////////////////////////////////////

// ****************************************************************************
// Method: ColorTableAttributes::SetColorTableNames
//
// Purpose:
//   Setter for colorTableNames.
//
// Note:       There needs to be a custom setter in order to keep the 
//             colorTableActiveFlags vector the same length.
//
// Programmer: Justin Privitera
// Creation:   Thu Jun 16 11:59:26 PDT 2022
//
// Modifications:
//   Justin Privitera, Mon Aug 21 15:54:50 PDT 2023
//   Changed ColorTableAttributes `names` to `colorTableNames` and `active` to
//   `colorTableActiveFlags`.
// ****************************************************************************

void
ColorTableAttributes::SetColorTableNames(const stringVector &colorTableNames_)
{
    colorTableNames = colorTableNames_;
    Select(ID_colorTableNames, (void *)&colorTableNames);
    if (colorTableActiveFlags.size() != colorTableNames.size())
    {
        intVector newColorTableActiveFlags;
        for (int i = 0; i < colorTableNames.size(); i ++)
            newColorTableActiveFlags.push_back(true);
        SetColorTableActiveFlags(newColorTableActiveFlags);
    }
}

// ****************************************************************************
// Method: ColorTableAttributes::SetColorTableActiveFlags
//
// Purpose:
//   Setter for colorTableActiveFlags.
//
// Note:       There needs to be a custom setter to ensure that the
//             colorTableNames vector is the same length.
//
// Programmer: Justin Privitera
// Creation:   Thu Jun 16 11:59:26 PDT 2022
//
// Modifications:
//   Justin Privitera, Mon Aug 21 15:54:50 PDT 2023
//   Changed ColorTableAttributes `active` to `colorTableActiveFlags`.
// ****************************************************************************

void
ColorTableAttributes::SetColorTableActiveFlags(const intVector &colorTableActiveFlags_)
{
    if (colorTableActiveFlags_.size() == colorTableNames.size())
    {
        colorTableActiveFlags = colorTableActiveFlags_;
        Select(ID_colorTableActiveFlags, (void *)&colorTableActiveFlags);
    }
}

void
ColorTableAttributes::SetDefaultContinuous(const std::string &defaultContinuous_)
{
    defaultContinuous = defaultContinuous_;
    Select(ID_defaultContinuous, (void *)&defaultContinuous);
}

void
ColorTableAttributes::SetDefaultDiscrete(const std::string &defaultDiscrete_)
{
    defaultDiscrete = defaultDiscrete_;
    Select(ID_defaultDiscrete, (void *)&defaultDiscrete);
}

void
ColorTableAttributes::SetTagsMatchAny(bool tagsMatchAny_)
{
    tagsMatchAny = tagsMatchAny_;
    Select(ID_tagsMatchAny, (void *)&tagsMatchAny);
}

void
ColorTableAttributes::SetTagListNames(const stringVector &tagListNames_)
{
    tagListNames = tagListNames_;
    Select(ID_tagListNames, (void *)&tagListNames);
}

void
ColorTableAttributes::SetTagListActive(const intVector &tagListActive_)
{
    tagListActive = tagListActive_;
    Select(ID_tagListActive, (void *)&tagListActive);
}

void
ColorTableAttributes::SetTagListNumRefs(const intVector &tagListNumRefs_)
{
    tagListNumRefs = tagListNumRefs_;
    Select(ID_tagListNumRefs, (void *)&tagListNumRefs);
}

void
ColorTableAttributes::SetTagListTableItemFlag(const intVector &tagListTableItemFlag_)
{
    tagListTableItemFlag = tagListTableItemFlag_;
    Select(ID_tagListTableItemFlag, (void *)&tagListTableItemFlag);
}

void
ColorTableAttributes::SetTagChangesTag(const stringVector &tagChangesTag_)
{
    tagChangesTag = tagChangesTag_;
    Select(ID_tagChangesTag, (void *)&tagChangesTag);
}

void
ColorTableAttributes::SetTagChangesType(const intVector &tagChangesType_)
{
    tagChangesType = tagChangesType_;
    Select(ID_tagChangesType, (void *)&tagChangesType);
}

void
ColorTableAttributes::SetTagChangesCTName(const stringVector &tagChangesCTName_)
{
    tagChangesCTName = tagChangesCTName_;
    Select(ID_tagChangesCTName, (void *)&tagChangesCTName);
}

void
ColorTableAttributes::SetDeferredTagChangesTag(const stringVector &deferredTagChangesTag_)
{
    deferredTagChangesTag = deferredTagChangesTag_;
    Select(ID_deferredTagChangesTag, (void *)&deferredTagChangesTag);
}

void
ColorTableAttributes::SetDeferredTagChangesType(const intVector &deferredTagChangesType_)
{
    deferredTagChangesType = deferredTagChangesType_;
    Select(ID_deferredTagChangesType, (void *)&deferredTagChangesType);
}

void
ColorTableAttributes::SetDeferredTagChangesCTName(const stringVector &deferredTagChangesCTName_)
{
    deferredTagChangesCTName = deferredTagChangesCTName_;
    Select(ID_deferredTagChangesCTName, (void *)&deferredTagChangesCTName);
}

///////////////////////////////////////////////////////////////////////////////
// Get property methods
///////////////////////////////////////////////////////////////////////////////

const stringVector &
ColorTableAttributes::GetColorTableNames() const
{
    return colorTableNames;
}

stringVector &
ColorTableAttributes::GetColorTableNames()
{
    return colorTableNames;
}

const intVector &
ColorTableAttributes::GetColorTableActiveFlags() const
{
    return colorTableActiveFlags;
}

intVector &
ColorTableAttributes::GetColorTableActiveFlags()
{
    return colorTableActiveFlags;
}

const AttributeGroupVector &
ColorTableAttributes::GetColorTables() const
{
    return colorTables;
}

AttributeGroupVector &
ColorTableAttributes::GetColorTables()
{
    return colorTables;
}

const std::string &
ColorTableAttributes::GetDefaultContinuous() const
{
    return defaultContinuous;
}

std::string &
ColorTableAttributes::GetDefaultContinuous()
{
    return defaultContinuous;
}

const std::string &
ColorTableAttributes::GetDefaultDiscrete() const
{
    return defaultDiscrete;
}

std::string &
ColorTableAttributes::GetDefaultDiscrete()
{
    return defaultDiscrete;
}

bool
ColorTableAttributes::GetTagsMatchAny() const
{
    return tagsMatchAny;
}

const stringVector &
ColorTableAttributes::GetTagListNames() const
{
    return tagListNames;
}

stringVector &
ColorTableAttributes::GetTagListNames()
{
    return tagListNames;
}

const intVector &
ColorTableAttributes::GetTagListActive() const
{
    return tagListActive;
}

intVector &
ColorTableAttributes::GetTagListActive()
{
    return tagListActive;
}

const intVector &
ColorTableAttributes::GetTagListNumRefs() const
{
    return tagListNumRefs;
}

intVector &
ColorTableAttributes::GetTagListNumRefs()
{
    return tagListNumRefs;
}

const intVector &
ColorTableAttributes::GetTagListTableItemFlag() const
{
    return tagListTableItemFlag;
}

intVector &
ColorTableAttributes::GetTagListTableItemFlag()
{
    return tagListTableItemFlag;
}

const stringVector &
ColorTableAttributes::GetTagChangesTag() const
{
    return tagChangesTag;
}

stringVector &
ColorTableAttributes::GetTagChangesTag()
{
    return tagChangesTag;
}

const intVector &
ColorTableAttributes::GetTagChangesType() const
{
    return tagChangesType;
}

intVector &
ColorTableAttributes::GetTagChangesType()
{
    return tagChangesType;
}

const stringVector &
ColorTableAttributes::GetTagChangesCTName() const
{
    return tagChangesCTName;
}

stringVector &
ColorTableAttributes::GetTagChangesCTName()
{
    return tagChangesCTName;
}

const stringVector &
ColorTableAttributes::GetDeferredTagChangesTag() const
{
    return deferredTagChangesTag;
}

stringVector &
ColorTableAttributes::GetDeferredTagChangesTag()
{
    return deferredTagChangesTag;
}

const intVector &
ColorTableAttributes::GetDeferredTagChangesType() const
{
    return deferredTagChangesType;
}

intVector &
ColorTableAttributes::GetDeferredTagChangesType()
{
    return deferredTagChangesType;
}

const stringVector &
ColorTableAttributes::GetDeferredTagChangesCTName() const
{
    return deferredTagChangesCTName;
}

stringVector &
ColorTableAttributes::GetDeferredTagChangesCTName()
{
    return deferredTagChangesCTName;
}

///////////////////////////////////////////////////////////////////////////////
// Select property methods
///////////////////////////////////////////////////////////////////////////////

void
ColorTableAttributes::SelectColorTableNames()
{
    Select(ID_colorTableNames, (void *)&colorTableNames);
}

void
ColorTableAttributes::SelectColorTableActiveFlags()
{
    Select(ID_colorTableActiveFlags, (void *)&colorTableActiveFlags);
}

void
ColorTableAttributes::SelectColorTables()
{
    Select(ID_colorTables, (void *)&colorTables);
}

void
ColorTableAttributes::SelectDefaultContinuous()
{
    Select(ID_defaultContinuous, (void *)&defaultContinuous);
}

void
ColorTableAttributes::SelectDefaultDiscrete()
{
    Select(ID_defaultDiscrete, (void *)&defaultDiscrete);
}

void
ColorTableAttributes::SelectTagListNames()
{
    Select(ID_tagListNames, (void *)&tagListNames);
}

void
ColorTableAttributes::SelectTagListActive()
{
    Select(ID_tagListActive, (void *)&tagListActive);
}

void
ColorTableAttributes::SelectTagListNumRefs()
{
    Select(ID_tagListNumRefs, (void *)&tagListNumRefs);
}

void
ColorTableAttributes::SelectTagListTableItemFlag()
{
    Select(ID_tagListTableItemFlag, (void *)&tagListTableItemFlag);
}

void
ColorTableAttributes::SelectTagChangesTag()
{
    Select(ID_tagChangesTag, (void *)&tagChangesTag);
}

void
ColorTableAttributes::SelectTagChangesType()
{
    Select(ID_tagChangesType, (void *)&tagChangesType);
}

void
ColorTableAttributes::SelectTagChangesCTName()
{
    Select(ID_tagChangesCTName, (void *)&tagChangesCTName);
}

void
ColorTableAttributes::SelectDeferredTagChangesTag()
{
    Select(ID_deferredTagChangesTag, (void *)&deferredTagChangesTag);
}

void
ColorTableAttributes::SelectDeferredTagChangesType()
{
    Select(ID_deferredTagChangesType, (void *)&deferredTagChangesType);
}

void
ColorTableAttributes::SelectDeferredTagChangesCTName()
{
    Select(ID_deferredTagChangesCTName, (void *)&deferredTagChangesCTName);
}

///////////////////////////////////////////////////////////////////////////////
// AttributeGroupVector convenience methods.
///////////////////////////////////////////////////////////////////////////////

// ****************************************************************************
// Method: ColorTableAttributes::AddColorTables
//
// Purpose:
//   This class contains the list of colortables.
//
// Note:       Autogenerated by xml2atts.
//
// Programmer: xml2atts
// Creation:   omitted
//
// Modifications:
//
// ****************************************************************************

void
ColorTableAttributes::AddColorTables(const ColorControlPointList &obj)
{
    ColorControlPointList *newColorControlPointList = new ColorControlPointList(obj);
    colorTables.push_back(newColorControlPointList);

    // Indicate that things have changed by selecting it.
    Select(ID_colorTables, (void *)&colorTables);
}

// ****************************************************************************
// Method: ColorTableAttributes::ClearColorTables
//
// Purpose:
//   This class contains the list of colortables.
//
// Note:       Autogenerated by xml2atts.
//
// Programmer: xml2atts
// Creation:   omitted
//
// Modifications:
//
// ****************************************************************************

void
ColorTableAttributes::ClearColorTables()
{
    AttributeGroupVector::iterator pos;

    for(pos = colorTables.begin(); pos != colorTables.end(); ++pos)
        delete *pos;
    colorTables.clear();

    // Indicate that things have changed by selecting the list.
    Select(ID_colorTables, (void *)&colorTables);
}

// ****************************************************************************
// Method: ColorTableAttributes::RemoveColorTables
//
// Purpose:
//   This class contains the list of colortables.
//
// Note:       Autogenerated by xml2atts.
//
// Programmer: xml2atts
// Creation:   omitted
//
// Modifications:
//
// ****************************************************************************

void
ColorTableAttributes::RemoveColorTables(int index)
{
    AttributeGroupVector::iterator pos = colorTables.begin();

    // Iterate through the vector "index" times.
    for(int i = 0; i < index; ++i)
        if(pos != colorTables.end()) ++pos;

    // If pos is still a valid iterator, remove that element.
    if(pos != colorTables.end())
    {
        delete *pos;
        colorTables.erase(pos);
    }

    // Indicate that things have changed by selecting the list.
    Select(ID_colorTables, (void *)&colorTables);
}

// ****************************************************************************
// Method: ColorTableAttributes::GetNumColorTables
//
// Purpose:
//   This class contains the list of colortables.
//
// Note:       Autogenerated by xml2atts.
//
// Programmer: xml2atts
// Creation:   omitted
//
// Modifications:
//
// ****************************************************************************

int
ColorTableAttributes::GetNumColorTables() const
{
    return (int)colorTables.size();
}

// ****************************************************************************
// Method: ColorTableAttributes::GetColorTables
//
// Purpose:
//   This class contains the list of colortables.
//
// Note:       Autogenerated by xml2atts.
//
// Programmer: xml2atts
// Creation:   omitted
//
// Modifications:
//
// ****************************************************************************

ColorControlPointList &
ColorTableAttributes::GetColorTables(int i)
{
    return *((ColorControlPointList *)colorTables[i]);
}

// ****************************************************************************
// Method: ColorTableAttributes::GetColorTables
//
// Purpose:
//   This class contains the list of colortables.
//
// Note:       Autogenerated by xml2atts.
//
// Programmer: xml2atts
// Creation:   omitted
//
// Modifications:
//
// ****************************************************************************

const ColorControlPointList &
ColorTableAttributes::GetColorTables(int i) const
{
    return *((ColorControlPointList *)colorTables[i]);
}

// ****************************************************************************
// Method: ColorTableAttributes::operator []
//
// Purpose:
//   This class contains the list of colortables.
//
// Note:       Autogenerated by xml2atts.
//
// Programmer: xml2atts
// Creation:   omitted
//
// Modifications:
//
// ****************************************************************************

ColorControlPointList &
ColorTableAttributes::operator [] (int i)
{
    return *((ColorControlPointList *)colorTables[i]);
}

// ****************************************************************************
// Method: ColorTableAttributes::operator []
//
// Purpose:
//   This class contains the list of colortables.
//
// Note:       Autogenerated by xml2atts.
//
// Programmer: xml2atts
// Creation:   omitted
//
// Modifications:
//
// ****************************************************************************

const ColorControlPointList &
ColorTableAttributes::operator [] (int i) const
{
    return *((ColorControlPointList *)colorTables[i]);
}

///////////////////////////////////////////////////////////////////////////////
// Keyframing methods
///////////////////////////////////////////////////////////////////////////////

// ****************************************************************************
// Method: ColorTableAttributes::GetFieldName
//
// Purpose:
//   This method returns the name of a field given its index.
//
// Note:       Autogenerated by xml2atts.
//
// Programmer: xml2atts
// Creation:   omitted
//
// Modifications:
//
// ****************************************************************************

std::string
ColorTableAttributes::GetFieldName(int index) const
{
    switch (index)
    {
    case ID_colorTableNames:          return "colorTableNames";
    case ID_colorTableActiveFlags:    return "colorTableActiveFlags";
    case ID_colorTables:              return "colorTables";
    case ID_defaultContinuous:        return "defaultContinuous";
    case ID_defaultDiscrete:          return "defaultDiscrete";
    case ID_tagsMatchAny:             return "tagsMatchAny";
    case ID_tagListNames:             return "tagListNames";
    case ID_tagListActive:            return "tagListActive";
    case ID_tagListNumRefs:           return "tagListNumRefs";
    case ID_tagListTableItemFlag:     return "tagListTableItemFlag";
    case ID_tagChangesTag:            return "tagChangesTag";
    case ID_tagChangesType:           return "tagChangesType";
    case ID_tagChangesCTName:         return "tagChangesCTName";
    case ID_deferredTagChangesTag:    return "deferredTagChangesTag";
    case ID_deferredTagChangesType:   return "deferredTagChangesType";
    case ID_deferredTagChangesCTName: return "deferredTagChangesCTName";
    default:  return "invalid index";
    }
}

// ****************************************************************************
// Method: ColorTableAttributes::GetFieldType
//
// Purpose:
//   This method returns the type of a field given its index.
//
// Note:       Autogenerated by xml2atts.
//
// Programmer: xml2atts
// Creation:   omitted
//
// Modifications:
//
// ****************************************************************************

AttributeGroup::FieldType
ColorTableAttributes::GetFieldType(int index) const
{
    switch (index)
    {
    case ID_colorTableNames:          return FieldType_stringVector;
    case ID_colorTableActiveFlags:    return FieldType_intVector;
    case ID_colorTables:              return FieldType_attVector;
    case ID_defaultContinuous:        return FieldType_string;
    case ID_defaultDiscrete:          return FieldType_string;
    case ID_tagsMatchAny:             return FieldType_bool;
    case ID_tagListNames:             return FieldType_stringVector;
    case ID_tagListActive:            return FieldType_intVector;
    case ID_tagListNumRefs:           return FieldType_intVector;
    case ID_tagListTableItemFlag:     return FieldType_intVector;
    case ID_tagChangesTag:            return FieldType_stringVector;
    case ID_tagChangesType:           return FieldType_intVector;
    case ID_tagChangesCTName:         return FieldType_stringVector;
    case ID_deferredTagChangesTag:    return FieldType_stringVector;
    case ID_deferredTagChangesType:   return FieldType_intVector;
    case ID_deferredTagChangesCTName: return FieldType_stringVector;
    default:  return FieldType_unknown;
    }
}

// ****************************************************************************
// Method: ColorTableAttributes::GetFieldTypeName
//
// Purpose:
//   This method returns the name of a field type given its index.
//
// Note:       Autogenerated by xml2atts.
//
// Programmer: xml2atts
// Creation:   omitted
//
// Modifications:
//
// ****************************************************************************

std::string
ColorTableAttributes::GetFieldTypeName(int index) const
{
    switch (index)
    {
    case ID_colorTableNames:          return "stringVector";
    case ID_colorTableActiveFlags:    return "intVector";
    case ID_colorTables:              return "attVector";
    case ID_defaultContinuous:        return "string";
    case ID_defaultDiscrete:          return "string";
    case ID_tagsMatchAny:             return "bool";
    case ID_tagListNames:             return "stringVector";
    case ID_tagListActive:            return "intVector";
    case ID_tagListNumRefs:           return "intVector";
    case ID_tagListTableItemFlag:     return "intVector";
    case ID_tagChangesTag:            return "stringVector";
    case ID_tagChangesType:           return "intVector";
    case ID_tagChangesCTName:         return "stringVector";
    case ID_deferredTagChangesTag:    return "stringVector";
    case ID_deferredTagChangesType:   return "intVector";
    case ID_deferredTagChangesCTName: return "stringVector";
    default:  return "invalid index";
    }
}

// ****************************************************************************
// Method: ColorTableAttributes::FieldsEqual
//
// Purpose:
//   This method compares two fields and return true if they are equal.
//
// Note:       Autogenerated by xml2atts.
//
// Programmer: xml2atts
// Creation:   omitted
//
// Modifications:
//
// ****************************************************************************

bool
ColorTableAttributes::FieldsEqual(int index_, const AttributeGroup *rhs) const
{
    const ColorTableAttributes &obj = *((const ColorTableAttributes*)rhs);
    bool retval = false;
    switch (index_)
    {
    case ID_colorTableNames:
        {  // new scope
        retval = (colorTableNames == obj.colorTableNames);
        }
        break;
    case ID_colorTableActiveFlags:
        {  // new scope
        retval = (colorTableActiveFlags == obj.colorTableActiveFlags);
        }
        break;
    case ID_colorTables:
        {  // new scope
        bool colorTables_equal = (obj.colorTables.size() == colorTables.size());
        for(size_t i = 0; (i < colorTables.size()) && colorTables_equal; ++i)
        {
            // Make references to ColorControlPointList from AttributeGroup *.
            const ColorControlPointList &colorTables1 = *((const ColorControlPointList *)(colorTables[i]));
            const ColorControlPointList &colorTables2 = *((const ColorControlPointList *)(obj.colorTables[i]));
            colorTables_equal = (colorTables1 == colorTables2);
        }

        retval = colorTables_equal;
        }
        break;
    case ID_defaultContinuous:
        {  // new scope
        retval = (defaultContinuous == obj.defaultContinuous);
        }
        break;
    case ID_defaultDiscrete:
        {  // new scope
        retval = (defaultDiscrete == obj.defaultDiscrete);
        }
        break;
    case ID_tagsMatchAny:
        {  // new scope
        retval = (tagsMatchAny == obj.tagsMatchAny);
        }
        break;
    case ID_tagListNames:
        {  // new scope
        retval = (tagListNames == obj.tagListNames);
        }
        break;
    case ID_tagListActive:
        {  // new scope
        retval = (tagListActive == obj.tagListActive);
        }
        break;
    case ID_tagListNumRefs:
        {  // new scope
        retval = (tagListNumRefs == obj.tagListNumRefs);
        }
        break;
    case ID_tagListTableItemFlag:
        {  // new scope
        retval = (tagListTableItemFlag == obj.tagListTableItemFlag);
        }
        break;
    case ID_tagChangesTag:
        {  // new scope
        retval = (tagChangesTag == obj.tagChangesTag);
        }
        break;
    case ID_tagChangesType:
        {  // new scope
        retval = (tagChangesType == obj.tagChangesType);
        }
        break;
    case ID_tagChangesCTName:
        {  // new scope
        retval = (tagChangesCTName == obj.tagChangesCTName);
        }
        break;
    case ID_deferredTagChangesTag:
        {  // new scope
        retval = (deferredTagChangesTag == obj.deferredTagChangesTag);
        }
        break;
    case ID_deferredTagChangesType:
        {  // new scope
        retval = (deferredTagChangesType == obj.deferredTagChangesType);
        }
        break;
    case ID_deferredTagChangesCTName:
        {  // new scope
        retval = (deferredTagChangesCTName == obj.deferredTagChangesCTName);
        }
        break;
    default: retval = false;
    }

    return retval;
}

///////////////////////////////////////////////////////////////////////////////
// User-defined methods.
///////////////////////////////////////////////////////////////////////////////

// ****************************************************************************
// Method: ColorTableAttributes::GetColorTableIndex
//
// Purpose:
//   Returns the index of the specified color table.
//
// Arguments:
//   name : The name of the color table that we want.
//
// Returns:    The index or -1 if the color table is not in the list.
//
// Note:
//
// Programmer: Brad Whitlock
// Creation:   Sat Jun 16 20:32:23 PST 2001
//
// Modifications:
//   Justin Privitera, Mon Aug 21 15:54:50 PDT 2023
//   Changed ColorTableAttributes `names` to `colorTableNames`.
// ****************************************************************************

int
ColorTableAttributes::GetColorTableIndex(const std::string &name) const
{
    int retval = -1;
    for(size_t i = 0; i < colorTableNames.size(); ++i)
    {
        if(colorTableNames[i] == name)
        {
            retval = i;
            break;
        }
    }

    return retval;
}

// ****************************************************************************
// Method: ColorTableAttributes::GetColorControlPoints
//
// Purpose:
//   Returns a pointer to the color control points for the specified color
//   table index.
//
// Arguments:
//   index : The index for which we want the color control points.
//
// Returns:    The color control points or NULL.
//
// Note:
//
// Programmer: Brad Whitlock
// Creation:   Sat Jun 16 20:33:22 PST 2001
//
// Modifications:
//
// ****************************************************************************

const ColorControlPointList *
ColorTableAttributes::GetColorControlPoints(int index) const
{
    if(index >= 0 && (size_t)index < colorTables.size())
        return ((ColorControlPointList *)colorTables[index]);
    else
        return 0;
}

// ****************************************************************************
// Method: ColorTableAttributes::GetColorControlPoints
//
// Purpose:
//   Returns a pointer to the color control points for the specified color
//   table index.
//
// Arguments:
//   name : The name of the color table for which we want the color control
//          points.
//
// Returns:    The color control points or NULL.
//
// Note:
//
// Programmer: Brad Whitlock
// Creation:   Sat Jun 16 20:33:22 PST 2001
//
// Modifications:
//
// ****************************************************************************

const ColorControlPointList *
ColorTableAttributes::GetColorControlPoints(const std::string &name) const
{
    int index = GetColorTableIndex(name);

    if(index >= 0 && (size_t)index < colorTables.size())
        return ((ColorControlPointList *)colorTables[index]);
    else
        return 0;
}

// ****************************************************************************
// Method: ColorTableAttributes::AddColorTable
//
// Purpose:
//   Adds a new color table to the list.
//
// Arguments:
//   name : The name of the new color table.
//   cpts : The color control points for the new color table.
//
// Programmer: Brad Whitlock
// Creation:   Sat Jun 16 20:35:43 PST 2001
//
// Modifications:
//   Brad Whitlock, Mon Nov 25 12:00:52 PDT 2002
//   I added code to sort the color table using a map.
//
//   Brad Whitlock, Mon Mar 13 17:14:55 PST 2006
//   Make sure that a pre-existing color table with the same name is removed
//   before adding the new color table.
//
//   Hank Childs, Thu Jan 17 16:41:34 PST 2008
//   Preserve the default continuous/discrete status if a color table is
//   being replaced.
//
//  Justin Privitera, Fri May 20 11:02:45 PDT 2022
//  Replaced *active* w/ *default* for everything color tables.
// 
//   Justin Privitera, Thu Jun 16 18:01:49 PDT 2022
//   Added logic for `active` parallel to `names`.
// 
//   Justin Privitera, Wed Aug  3 19:46:13 PDT 2022
//   Do nothing if the color table is built-in.
// 
//   Justin Privitera, Mon Aug 21 15:54:50 PDT 2023
//   Changed ColorTableAttributes `names` to `colorTableNames` and `active` to
//   `colorTableActiveFlags`.
//   Removed some redundant logic with default color tables.
// 
//   Justin Privitera, Mon Aug 28 09:57:59 PDT 2023
//   Made the passed ccpl not const so we can edit its tags.
//   Add the "No Tags" tag here if the ccpl has no tags.
//   Apply deferred tag changes.
//   Fixed a bug where the CTnames and ccpls would get sorted but CTactive
//   flags would not be.
// 
//    Justin Privitera, Tue Sep  5 12:49:42 PDT 2023
//    Create tag list entry on CT addition.
//    Increment tag list entry on CT addition.
//    Filter CT by tags when it is added.
//    Use new selection methods to select CTs and tags lists.
// ****************************************************************************

void
ColorTableAttributes::AddColorTable(const std::string &name,
                                    ColorControlPointList &cpts)
{
    // Remove the color table if it already exists in the list.
    int index = GetColorTableIndex(name);
    if(index != -1)
    {
        // but do nothing if the color table is built-in
        if (GetColorControlPoints(index)->GetBuiltIn())
            return;
        RemoveColorTable(index);
    }

    // if this table doesn't have tags, then add the no-tags tag
    if (cpts.GetNumTags() == 0)
        cpts.AddTag("No Tags");

    // if we had tag changes from a session or config file for this color
    // table BEFORE we had access to the color table, we can add them in now.
    ApplyDeferredTagChanges(name, &cpts);

    // iterate thru each tag in the given color table
    for (const auto & tagname : cpts.GetTagNames())
    {
        // add the tag to the tag list if it is not already there
        CreateTagListEntry(tagname, false, 0, false);
        IncrementTagNumRefs(tagname);
    }

    // Append the color table to the list.
    colorTableNames.push_back(name);
    colorTableActiveFlags.push_back(true);
    AddColorTables(cpts);

    // Store the name, active, and colortable triples into a map.
    std::map<std::string, std::pair<int, AttributeGroup *>> sortMap;
    for(size_t i = 0; i < colorTableNames.size(); ++i)
    {
        sortMap[colorTableNames[i]].first  = colorTableActiveFlags[i];
        sortMap[colorTableNames[i]].second = colorTables[i];
    }

    // Traverse the map, it will be sorted. Store the colorTableNames and color table
    // pointer back into the old vectors.
    std::map<std::string, std::pair<int, AttributeGroup *>>::iterator pos;
    size_t i;
    for(i = 0, pos = sortMap.begin(); pos != sortMap.end(); ++pos, ++i)
    {
        colorTableNames[i] = pos->first;
        auto ctpair = pos->second;
        colorTableActiveFlags[i] = ctpair.first;
        colorTables[i] = ctpair.second;
    }

    // determine if this color table belongs in the current tag filtering selection
    int CTindex = GetColorTableIndex(name);
    if (CTindex >= 0 && CTindex < colorTableActiveFlags.size())
        colorTableActiveFlags[CTindex] = FilterTableByTag(cpts);

    SelectColorTablesList();
    SelectTagList();
}

// ****************************************************************************
// Method: ColorTableAttributes::RemoveColorTable
//
// Purpose:
//   Removes the named color table from the list.
//
// Arguments:
//   name : The name of the color table to remove.
//
// Programmer: Brad Whitlock
// Creation:   Sat Jun 16 20:36:30 PST 2001
//
// Modifications:
//
// ****************************************************************************

void
ColorTableAttributes::RemoveColorTable(const std::string &name)
{
    int index = GetColorTableIndex(name);
    RemoveColorTable(index);
}

// ****************************************************************************
// Method: ColorTableAttributes::RemoveColorTable
//
// Purpose:
//   Removes the color table at the specified list index.
//
// Arguments:
//   index : The index of the color table we want to remove.
//
// Programmer: Brad Whitlock
// Creation:   Sat Jun 16 20:37:03 PST 2001
//
// Modifications:
//   Brad Whitlock, Wed Nov 20 12:08:18 PDT 2002
//   Made it work with the new discrete color tables.
//
//  Justin Privitera, Fri May 20 11:02:45 PDT 2022
//  Replaced *active* w/ *default* for everything color tables.
// 
//   Justin Privitera, Thu Jun 16 18:01:49 PDT 2022
//   Added logic for `active` array parallel to `names`.
// 
//   Justin Privitera, Wed Aug  3 19:46:13 PDT 2022
//   Do nothing if color table is built in.
// 
//   Justin Privitera, Wed Sep 21 16:51:24 PDT 2022
//   Added additional logic to try and find the next appropriate color table
//   if either the default continuous or discrete color tables are deleted.
// 
//    Justin Privitera, Thu Sep 29 17:27:37 PDT 2022
//    Replace auto w/ bool. Add in missing breaks.
// 
//   Justin Privitera, Mon Aug 21 15:54:50 PDT 2023
//   Changed ColorTableAttributes `names` to `colorTableNames` and `active` to
//   `colorTableActiveFlags`.
//   Simplified and reused some logic for default color tables.
//   Renamed iterators to better describe what they iterate over.
// 
//   Justin Privitera, Mon Aug 28 09:57:59 PDT 2023
//   Renamed iterators to reduce ambiguity.
//
//    Justin Privitera, Tue Sep  5 12:49:42 PDT 2023
//    Use the new color tables list selection method.
//    Decrement tag num refs on CT removal.
// 
//   Justin Privitera, Wed Sep  6 11:52:18 PDT 2023
//   Fixed bug where I was saving out the tag changes multiple times.
//   Simplified logic for determining the default color table.
// ****************************************************************************

void
ColorTableAttributes::RemoveColorTable(int index)
{
    if(index >= 0 && (size_t)index < colorTableNames.size())
    {
        // do nothing if the color table is built-in.
        if (GetColorControlPoints(index)->GetBuiltIn())
            return;

        for (const auto & tagname : GetColorControlPoints(index)->GetTagNames())
            DecrementTagNumRefs(tagname);

        // Grab the color table name before we remove anything.
        std::string ctName = colorTableNames[index];

        // Iterate through the vector "index" times.
        auto ctNamesItr  = colorTableNames.begin();
        auto ctActiveItr = colorTableActiveFlags.begin();
        for(int i = 0; i < index; ++i)
        {
            ++ ctNamesItr;
            ++ ctActiveItr;
        }

        // If ctNamesItr is still a valid iterator, remove that element.
        if (ctNamesItr  != colorTableNames.end() &&
            ctActiveItr != colorTableActiveFlags.end())
        {
            colorTableNames.erase(ctNamesItr);
            colorTableActiveFlags.erase(ctActiveItr);
        }

        // Indicate that things have changed by selecting the lists.
        SelectColorTablesList();

        // erase the color table from the vector.
        RemoveColorTables(index);

        auto determineDefaultColorTable = [&](bool discrete)
        {
            if (colorTableNames.size() > 0)
            {
                for (int i = 0; i < colorTableNames.size(); i ++)
                {
                    bool ctDiscrete{GetColorTables(i).GetDiscreteFlag()};
                    if (discrete == ctDiscrete)
                        return colorTableNames[i];
                }
            }
            return std::string("");
        };

        // If it is the default color table that was removed, reset the
        // default color table to the first element.
        if (ctName == defaultContinuous)
            SetDefaultContinuous(determineDefaultColorTable(false));
        else if (ctName == defaultDiscrete)
            SetDefaultDiscrete(determineDefaultColorTable(true));
    }
}

// ****************************************************************************
// Method: ColorTableAttributes::SelectColorTablesList
//
// Purpose:
//   Selects the pieces of the color tables list.
//
// Programmer: Justin Privitera
// Creation:   09/05/23
//
// Modifications:
// 
// ****************************************************************************

void
ColorTableAttributes::SelectColorTablesList()
{
    SelectColorTableNames();
    SelectColorTableActiveFlags();
    SelectColorTables();
}

// ****************************************************************************
// Method: ColorTableAttributes::SetColorTableActiveFlag
//
// Purpose:
//   Sets the color table corresponding to the given index to active or 
//   inactive (appearing in the namelistbox or not) depending on the boolean
//   value passed.
//
// Arguments:
//   index - index of the color table
//   val   - bool to set active or inactive
//
// Programmer: Justin Privitera
// Creation:   Mon Jun  6 17:10:40 PDT 2022
//
// Modifications:
//    Justin Privitera, Wed Jun 29 17:50:24 PDT 2022
//    Added guard to prevent index out of bound errors.
// 
//   Justin Privitera, Mon Aug 21 15:54:50 PDT 2023
//   Changed ColorTableAttributes `active` to `colorTableActiveFlags`.
// ****************************************************************************

void
ColorTableAttributes::SetColorTableActiveFlag(int index, bool val)
{
    if (index >= 0 && index < colorTableActiveFlags.size())
        colorTableActiveFlags[index] = val;
}

// ****************************************************************************
// Method: ColorTableAttributes::GetColorTableActiveFlag
//
// Purpose:
//   Gets the state of a given color table (active or inactive (appearing in
//   the namelistbox or not)).
//
// Arguments:
//   index - index of the tag
//
// Programmer: Justin Privitera
// Creation:   Tue Jun 28 14:04:01 PDT 2022
//
// Modifications:
//   Justin Privitera, Mon Aug 21 15:54:50 PDT 2023
//   Changed ColorTableAttributes `active` to `colorTableActiveFlags` and 
//   added a return statement.
// ****************************************************************************

bool
ColorTableAttributes::GetColorTableActiveFlag(int index)
{
    if (index >= 0 && index < colorTableActiveFlags.size())
        return colorTableActiveFlags[index];
    return false; // the color table can hardly be active if it does not exist
}

// ****************************************************************************
// Method: ColorTableAttributes::MergeTagChanges
//
// Purpose: Takes tag changes vectors from a node and either applies them or 
//    saves them to apply later.
//
// Programmer: Justin Privitera
// Creation:   Thu Aug 25 15:04:55 PDT 2022
//
// Modifications:
//    Justin Privitera, Wed Sep 21 16:51:24 PDT 2022
//    Reversed how tag changes are represented (internally and when saved to
//    files) so now tag names are mapped to sets of pairs of color table names
//    and integers.
// 
//    Justin Privitera, Thu Sep 29 17:27:37 PDT 2022
//    Replace braces with parens for auto.
// 
//    Justin Privitera, Mon Aug 28 09:57:59 PDT 2023
//    Moved this function from QvisColorTableWindow. Removed the functionality
//    for digesting strings that represent tag changes as that is no longer
//    necessary. Renamed the function to reflect this. Rewrote the function to 
//    take advantage of the new taglist API.
//
// ****************************************************************************
void
ColorTableAttributes::MergeTagChanges(const stringVector tagChangesTagFromNode,
                                      const intVector tagChangesTypeFromNode,
                                      const stringVector tagChangesCTNameFromNode)
{
    // if all three are the same size
    if (tagChangesTagFromNode.size() == tagChangesTypeFromNode.size() &&
        tagChangesTagFromNode.size() == tagChangesCTNameFromNode.size())
    {
        for (size_t changeId = 0; changeId < tagChangesTagFromNode.size(); changeId ++)
        {
            const std::string tagName = tagChangesTagFromNode[changeId];
            const int changeType      = tagChangesTypeFromNode[changeId];
            const std::string ctName  = tagChangesCTNameFromNode[changeId];

            auto *ccpl(const_cast<ColorControlPointList *>(GetColorControlPoints(ctName)));
            if (ccpl)
            {
                ApplyTagChange(tagName, changeType, ctName, ccpl);
            }
            else
            {
                // The ccpl is not valid. The most likely scenario is that it
                // is a user-defined CT and so it has not been added to visit
                // at this stage of start up. We will keep note of its tag 
                // changes and defer them until later when we encounter it.
                CreateDeferredTagChangesEntry(tagName, changeType, ctName);
            }
        }
    }
    else
    {
        debug1 << "ColorTableAttributes WARNING: "
               << "The color table tag changes lists "
               << "should be the same size. "
               << "Has your config/session file been corrupted?";
    }
}

// ****************************************************************************
// Method: ColorTableAttributes::addTagToColorTable
//
// Purpose:
//    Helper function for adding tags to color tables... carefully!
// 
// Programmer: Justin Privitera
// Creation:   Thu Aug 25 15:04:55 PDT 2022
//
// Modifications:
//    Justin Privitera, Wed Sep 21 16:51:24 PDT 2022
//    Merged tagList and tagChanges data structures.
//    Kept track of tag refcount on deletion.
// 
//    Justin Privitera, Thu Sep 29 17:27:37 PDT 2022
//    Replace braces with parens for auto.
// 
//    Justin Privitera, Mon Aug 28 09:57:59 PDT 2023
//    Moved this function from QvisColorTableWindow. Rewrote the function to 
//    take advantage of the new taglist API.
//
// ****************************************************************************
void
ColorTableAttributes::addTagToColorTable(const std::string ctName, 
                                         const std::string tagName,
                                         ColorControlPointList* ccpl)
{
    // First we will check if the opposite action of the one we 
    // are trying to take has already happened. If so, we can just
    // delete the opposite action.

    bool found_opposite = false;
    for (size_t changeId = 0; changeId < tagChangesTag.size(); changeId ++)
    {
        // the tag that is being added or removed
        std::string actionTag = tagChangesTag[changeId];
        // whether or not that tag is being added or removed
        int actionType = tagChangesType[changeId];
        // the color table that the change is happening to
        std::string actionCT = tagChangesCTName[changeId];

        // opposite day
        if (actionTag == tagName &&
            actionType == REMOVETAG &&
            actionCT == ctName)
        {
            found_opposite = true;
            RemoveTagChangesEntry(changeId);
            break;
        }
    }
    // if the opposite tag change was not in the list of tag changes
    if (! found_opposite)
    {
        CreateTagChangesEntry(tagName, ADDTAG, ctName);
    }
    ccpl->AddTag(tagName);

    // will create an entry for this tag if there is not one already
    CreateTagListEntry(tagName, false, 0, false);

    IncrementTagNumRefs(tagName);

    SelectTagChanges();
}

// ****************************************************************************
// Method: ColorTableAttributes::removeTagFromColorTable
//
// Purpose:
//    Helper function for removing tags from color tables... carefully!
//
// Programmer: Justin Privitera
// Creation:   Thu Aug 25 15:04:55 PDT 2022
//
// Modifications:
//    Justin Privitera, Wed Sep 21 16:51:24 PDT 2022
//    Merged tagList and tagChanges data structures.
//    Kept track of tag refcount on deletion.
// 
//    Justin Privitera, Thu Sep 29 17:27:37 PDT 2022
//    Replace braces with parens for auto.
// 
//    Justin Privitera, Mon Aug 28 09:57:59 PDT 2023
//    Moved this function from QvisColorTableWindow. Added a return value so 
//    this function can return success and an error message. Rewrote the 
//    function to take advantage of the new taglist API.
//
// ****************************************************************************
std::pair<bool, std::string>
ColorTableAttributes::removeTagFromColorTable(const std::string ctName, 
                                              const std::string tagName,
                                              ColorControlPointList* ccpl)
{
    bool success{true};
    std::string outstr{""};

    // First we will check if the opposite action of the one we 
    // are trying to take has already happened. If so, we can just
    // delete the opposite action.

    bool found_opposite = false;
    for (size_t changeId = 0; changeId < tagChangesTag.size(); changeId ++)
    {
        // the tag that is being added or removed
        std::string actionTag = tagChangesTag[changeId];
        // whether or not that tag is being added or removed
        int actionType = tagChangesType[changeId];
        // the color table that the change is happening to
        std::string actionCT = tagChangesCTName[changeId];

        // opposite day
        if (actionTag == tagName &&
            actionType == ADDTAG &&
            actionCT == ctName)
        {
            found_opposite = true;
            RemoveTagChangesEntry(changeId);
            ccpl->RemoveTag(tagName);
            DecrementTagNumRefs(tagName);
            break;
        }
    }
    // if the opposite tag change was not in the list of tag changes
    if (! found_opposite)
    {
        // if this is a built in color table, you can only remove a 
        // tag if the add tag action is already there (the previous case)
        if (ccpl->GetBuiltIn())
        {
            success = false;
            outstr = "The color table \"" + ctName + "\" is built-in. You "
                     "cannot delete a default tag from a built-in color table.";
        }
        else
        {
            CreateTagChangesEntry(tagName, REMOVETAG, ctName);
            ccpl->RemoveTag(tagName);
            DecrementTagNumRefs(tagName);
        }
    }

    SelectTagChanges();

    return std::make_pair(success, outstr);
}

// ****************************************************************************
// Method: ColorTableAttributes::AllTagsSelected
//
// Purpose:
//    Returns whether or not all tags are currently selected/enabled.
//
// Programmer: Justin Privitera
// Creation:   06/27/23
//
// Modifications:
//
// ****************************************************************************
bool
ColorTableAttributes::AllTagsSelected()
{
    return std::all_of(tagListActive.begin(), tagListActive.end(),
        [](const int tagListActiveEntry) { return tagListActiveEntry; });
}

// ****************************************************************************
// Method: ColorTableAttributes::EnableDisableAllTags
//
// Purpose:
//    Enables or disables all tags based on the provided argument.
//
// Programmer: Justin Privitera
// Creation:   06/27/23
//
// Modifications:
//
// ****************************************************************************
void
ColorTableAttributes::EnableDisableAllTags(bool enable)
{
    // we trust that the caller will check or uncheck all tag table items
    for (auto &tagListActiveEntry : tagListActive)
        tagListActiveEntry = enable;
    SelectTagList();
}

// ****************************************************************************
// Method: ColorTableAttributes::GetIndexOfTag
//
// Purpose:
//    Returns the index of a tag with the name tagname, or returns -1.
//
// Programmer: Justin Privitera
// Creation:   08/10/23
//
// Modifications:
//
// ****************************************************************************
int
ColorTableAttributes::GetIndexOfTag(const std::string tagname)
{
    for (size_t i = 0; i < tagListNames.size(); i ++)
    {
        if (tagListNames[i] == tagname)
            return i;
    }
    return -1;
}

// ****************************************************************************
// Method: ColorTableAttributes::CreateTagListEntry
//
// Purpose:
//    Creates an entry in the tag list by adding the constituent pieces to 
//    the 4 parallel vectors.
//
// Programmer: Justin Privitera
// Creation:   08/10/23
//
// Modifications:
//
// ****************************************************************************
void
ColorTableAttributes::CreateTagListEntry(const std::string tagname, 
                                         const bool active,
                                         const int numrefs,
                                         const bool tagTableItemExists)
{
    if (! CheckTagInTagList(tagname))
    {
        tagListNames.push_back(tagname);
        tagListActive.push_back(active);
        tagListNumRefs.push_back(numrefs);
        tagListTableItemFlag.push_back(tagTableItemExists);

        SelectTagList();
    }
}

// ****************************************************************************
// Method: ColorTableAttributes::RemoveTagListEntry
//
// Purpose:
//    Removes an entry in the tag list by index. Must remove from all four
//    parallel vectors.
//
// Programmer: Justin Privitera
// Creation:   08/10/23
//
// Modifications:
//
// ****************************************************************************
void
ColorTableAttributes::RemoveTagListEntry(const int index)
{
    if (index >= 0 && index < tagListNames.size())
    {
        auto namesItr = tagListNames.begin();
        auto activeItr = tagListActive.begin();
        auto numrefsItr = tagListNumRefs.begin();
        auto tableitemflagItr = tagListTableItemFlag.begin();

        for (int i = 0; i < index; i ++)
        {
            namesItr ++;
            activeItr ++;
            numrefsItr ++;
            tableitemflagItr ++;
        }

        if (namesItr != tagListNames.end())
        {
            tagListNames.erase(namesItr);
            tagListActive.erase(activeItr);
            tagListNumRefs.erase(numrefsItr);
            tagListTableItemFlag.erase(tableitemflagItr);
        }

        SelectTagList();
    }
}

// ****************************************************************************
// Method: ColorTableAttributes::SelectTagList
//
// Purpose:
//    Selects the pieces of the tag list data structure.
//
// Programmer: Justin Privitera
// Creation:   08/11/23
//
// Modifications:
//
// ****************************************************************************
void
ColorTableAttributes::SelectTagList()
{
    SelectTagListNames();
    SelectTagListActive();
    SelectTagListNumRefs();
    SelectTagListTableItemFlag();
}

// ****************************************************************************
// Method: ColorTableAttributes::CreateTagChangesEntry
//
// Purpose:
//    Creates an entry in the tag changes list by adding the constituent pieces
//    to the 3 parallel vectors.
//
// Programmer: Justin Privitera
// Creation:   08/10/23
//
// Modifications:
//    Justin Privitera, Tue Sep  5 12:49:42 PDT 2023
//    Check before adding to this list.
// ****************************************************************************
void
ColorTableAttributes::CreateTagChangesEntry(const std::string tagname, 
                                            const int addOrRemove,
                                            const std::string ctName)
{
    if (! CheckTagChangesEntryInTagChanges(tagname, addOrRemove, ctName))
    {
        tagChangesTag.push_back(tagname);
        tagChangesType.push_back(addOrRemove);
        tagChangesCTName.push_back(ctName);

        SelectTagChanges();
    }
}

// ****************************************************************************
// Method: ColorTableAttributes::RemoveTagChangesEntry
//
// Purpose:
//    Removes an entry in the tag changes list by index. Must remove from all 
//    three parallel vectors.
//
// Programmer: Justin Privitera
// Creation:   08/11/23
//
// Modifications:
//
// ****************************************************************************
void
ColorTableAttributes::RemoveTagChangesEntry(const int index)
{
    if (index >= 0 && index < tagChangesTag.size())
    {
        auto tagItr = tagChangesTag.begin();
        auto typeItr = tagChangesType.begin();
        auto ctnameItr = tagChangesCTName.begin();

        for (int i = 0; i < index; i ++)
        {
            tagItr ++;
            typeItr ++;
            ctnameItr ++;
        }

        if (tagItr != tagChangesTag.end())
        {
            tagChangesTag.erase(tagItr);
            tagChangesType.erase(typeItr);
            tagChangesCTName.erase(ctnameItr);
        }

        SelectTagChanges();
    }
}

// ****************************************************************************
// Method: ColorTableAttributes::GetIndexOfTagChangesEntry
//
// Purpose:
//    Returns the index of a tag changes value, or -1 if the value
//    is not present in the data structure.
//
// Programmer: Justin Privitera
// Creation:   08/31/23
//
// Modifications:
//
// ****************************************************************************
int
ColorTableAttributes::GetIndexOfTagChangesEntry(const std::string tagName, 
                                                const int addOrRemove,
                                                const std::string ctName)
{
    for (size_t i = 0; i < tagChangesTag.size(); i ++)
    {
        if (tagChangesTag[i] == tagName &&
            tagChangesType[i] == addOrRemove &&
            tagChangesCTName[i] == ctName)
        {
            return i;
        }
    }
    return -1;
}

// ****************************************************************************
// Method: ColorTableAttributes::CheckTagChangesEntryInTagChanges
//
// Purpose:
//    Returns true if the tag is in the tag list, and false otherwise.
//
// Programmer: Justin Privitera
// Creation:   08/27/23
//
// Modifications:
//
// ****************************************************************************
bool
ColorTableAttributes::CheckTagChangesEntryInTagChanges(const std::string tagName, 
                                                       const int addOrRemove,
                                                       const std::string ctName)
{
    return GetIndexOfTagChangesEntry(tagName, addOrRemove, ctName) >= 0;
}

// ****************************************************************************
// Method: ColorTableAttributes::CreateDeferredTagChangesEntry
//
// Purpose:
//    Creates an entry in the deferred tag changes list by adding the 
//    constituent pieces to the 3 parallel vectors.
//
// Programmer: Justin Privitera
// Creation:   08/24/23
//
// Modifications:
//    Justin Privitera, Tue Sep  5 12:49:42 PDT 2023
//    Check before adding an entry to this list, and select the list when done.
// ****************************************************************************
void
ColorTableAttributes::CreateDeferredTagChangesEntry(const std::string tagname, 
                                                    const int addOrRemove,
                                                    const std::string ctName)
{
    if (! CheckTagChangesEntryInDeferredTagChanges(tagname, addOrRemove, ctName))
    {
        deferredTagChangesTag.push_back(tagname);
        deferredTagChangesType.push_back(addOrRemove);
        deferredTagChangesCTName.push_back(ctName);

        SelectDeferredTagChanges();
    }
}

// ****************************************************************************
// Method: ColorTableAttributes::RemoveDeferredTagChangesEntry
//
// Purpose:
//    Removes an entry in the deferred tag changes list by index. Must remove 
//    from all three parallel vectors.
//
// Programmer: Justin Privitera
// Creation:   08/24/23
//
// Modifications:
//    Justin Privitera, Tue Sep  5 12:49:42 PDT 2023
//    Select deferred tag changes if we are removing.
// ****************************************************************************
void
ColorTableAttributes::RemoveDeferredTagChangesEntry(const int index)
{
    if (index >= 0 && index < deferredTagChangesTag.size())
    {
        auto tagItr = deferredTagChangesTag.begin();
        auto typeItr = deferredTagChangesType.begin();
        auto ctnameItr = deferredTagChangesCTName.begin();

        for (int i = 0; i < index; i ++)
        {
            tagItr ++;
            typeItr ++;
            ctnameItr ++;
        }

        if (tagItr != deferredTagChangesTag.end())
        {
            deferredTagChangesTag.erase(tagItr);
            deferredTagChangesType.erase(typeItr);
            deferredTagChangesCTName.erase(ctnameItr);
        }

        SelectDeferredTagChanges();
    }
}

// ****************************************************************************
// Method: ColorTableAttributes::RemoveDeferredTagChangesEntry
//
// Purpose:
//    Removes an entry in the deferred tag changes list by value.
//
// Programmer: Justin Privitera
// Creation:   08/24/23
//
// Modifications:
//
// ****************************************************************************
void
ColorTableAttributes::RemoveDeferredTagChangesEntry(const std::string tagName,
                                                    const int addOrRemove,
                                                    const std::string ctName)
{
    RemoveDeferredTagChangesEntry(GetIndexOfDeferredTagChangesEntry(tagName, addOrRemove, ctName));
}

// ****************************************************************************
// Method: ColorTableAttributes::SelectDeferredTagChanges
//
// Purpose:
//    Selects the pieces of the deferred tag changes list data structure.
//
// Programmer: Justin Privitera
// Creation:   08/31/23
//
// Modifications:
//
// ****************************************************************************
void
ColorTableAttributes::SelectDeferredTagChanges()
{
    SelectDeferredTagChangesTag();
    SelectDeferredTagChangesType();
    SelectDeferredTagChangesCTName();
}

// ****************************************************************************
// Method: ColorTableAttributes::GetIndexOfDeferredTagChangesEntry
//
// Purpose:
//    Returns the index of a deferred tag changes value, or -1 if the value
//    is not present in the data structure.
//
// Programmer: Justin Privitera
// Creation:   08/24/23
//
// Modifications:
//
// ****************************************************************************
int
ColorTableAttributes::GetIndexOfDeferredTagChangesEntry(const std::string tagName, 
                                                        const int addOrRemove,
                                                        const std::string ctName)
{
    for (size_t i = 0; i < deferredTagChangesTag.size(); i ++)
    {
        if (deferredTagChangesTag[i] == tagName &&
            deferredTagChangesType[i] == addOrRemove &&
            deferredTagChangesCTName[i] == ctName)
        {
            return i;
        }
    }
    return -1;
}

// ****************************************************************************
// Method: ColorTableAttributes::CheckTagChangesEntryInDeferredTagChanges
//
// Purpose:
//    Returns true if the tag is in the tag list, and false otherwise.
//
// Programmer: Justin Privitera
// Creation:   08/27/23
//
// Modifications:
//
// ****************************************************************************
bool
ColorTableAttributes::CheckTagChangesEntryInDeferredTagChanges(const std::string tagName, 
                                                               const int addOrRemove,
                                                               const std::string ctName)
{
    return GetIndexOfDeferredTagChangesEntry(tagName, addOrRemove, ctName) >= 0;
}

// ****************************************************************************
// Method: ColorTableAttributes::ApplyDeferredTagChanges
//
// Purpose:
//    Applies deferred tag changes and removes them, if applicable.
//
// Programmer: Justin Privitera
// Creation:   08/24/23
//
// Modifications:
//
// ****************************************************************************
void
ColorTableAttributes::ApplyDeferredTagChanges(const std::string newCTName,
                                              ColorControlPointList *cpts)
{
    for (size_t defChangeId = 0; defChangeId < deferredTagChangesTag.size(); )
    {
        const std::string tagName = deferredTagChangesTag[defChangeId];
        const int changeType      = deferredTagChangesType[defChangeId];
        const std::string ctName  = deferredTagChangesCTName[defChangeId];

        if (ctName == newCTName)
        {
            ApplyTagChange(tagName, changeType, ctName, cpts);
            RemoveDeferredTagChangesEntry(defChangeId);
        }
        else
        {
            defChangeId ++;
        }
    }
}

// ****************************************************************************
// Method: ColorTableAttributes::ApplyTagChange
//
// Purpose: Takes the elements of a tag change and applies that change to a 
//          CCPL.
//
// Programmer: Justin Privitera
// Creation:   08/24/23
//
// Modifications:
// 
// ****************************************************************************
void
ColorTableAttributes::ApplyTagChange(const std::string tagName,
                                     const int changeType,
                                     const std::string ctName,
                                     ColorControlPointList *ccpl)
{
    auto result(ccpl->ValidateTag(tagName));
    if (result.first)
    {
        if (changeType == ADDTAG && ! ccpl->HasTag(tagName))
        {
            addTagToColorTable(ctName, tagName, ccpl);
        }
        else if (changeType == REMOVETAG && ccpl->HasTag(tagName))
        {
            auto result2(removeTagFromColorTable(ctName, tagName, ccpl));
            if (!result2.first)
            {
                debug1 << "Tag Editing WARNING: " << result2.second;
            }
        }
    }
    else
    {
        debug1 << "ColorTableAttributes WARNING: " << result.second;
    }
}

// ****************************************************************************
// Method: ColorTableAttributes::SelectTagChanges
//
// Purpose:
//    Selects the pieces of the tag changes list data structure.
//
// Programmer: Justin Privitera
// Creation:   08/11/23
//
// Modifications:
//
// ****************************************************************************
void
ColorTableAttributes::SelectTagChanges()
{
    SelectTagChangesTag();
    SelectTagChangesType();
    SelectTagChangesCTName();
}

// ****************************************************************************
// Method: ColorTableAttributes::SetTagActive
//
// Purpose:
//    Sets a particular tag in the tag list to active by name.
//
// Programmer: Justin Privitera
// Creation:   06/27/23
//
// Modifications:
//
// ****************************************************************************
void
ColorTableAttributes::SetTagActive(const std::string tagname, const bool active)
{
    SetTagActive(GetIndexOfTag(tagname), active);
}

// ****************************************************************************
// Method: ColorTableAttributes::SetTagActive
//
// Purpose:
//    Sets a particular tag in the tag list to active by index.
//
// Programmer: Justin Privitera
// Creation:   08/10/23
//
// Modifications:
//
// ****************************************************************************
void
ColorTableAttributes::SetTagActive(const int index, const bool active)
{
    if (index >= 0 && index < tagListActive.size())
        tagListActive[index] = active;
    SelectTagList();
}

// ****************************************************************************
// Method: ColorTableAttributes::GetTagActive
//
// Purpose:
//    Returns whether a particular tag in the tag list is active.
//
// Programmer: Justin Privitera
// Creation:   06/27/23
//
// Modifications:
//
// ****************************************************************************
bool
ColorTableAttributes::GetTagActive(const std::string tagname)
{
    int index = GetIndexOfTag(tagname);
    if (index >= 0 && index < tagListActive.size())
        return tagListActive[index];
    return false; // the tag certainly isn't active if it doesn't exist
}

// ****************************************************************************
// Method: ColorTableAttributes::IncrementTagNumRefs
//
// Purpose:
//    Increments the number of references to a given tag in the tag list by
//    name.
//
// Programmer: Justin Privitera
// Creation:   08/10/23
//
// Modifications:
//
// ****************************************************************************
void
ColorTableAttributes::IncrementTagNumRefs(const std::string tagname)
{
    IncrementTagNumRefs(GetIndexOfTag(tagname));
}

// ****************************************************************************
// Method: ColorTableAttributes::IncrementTagNumRefs
//
// Purpose:
//    Increments the number of references to a given tag in the tag list by
//    index.
//
// Programmer: Justin Privitera
// Creation:   08/10/23
//
// Modifications:
//
// ****************************************************************************
void
ColorTableAttributes::IncrementTagNumRefs(const int index)
{
    if (index >= 0 && index < tagListNumRefs.size())
        tagListNumRefs[index] ++;
    SelectTagList();
}

// ****************************************************************************
// Method: ColorTableAttributes::DecrementTagNumRefs
//
// Purpose:
//    Decrements the number of references to a given tag in the tag list by
//    name.
//
// Programmer: Justin Privitera
// Creation:   08/10/23
//
// Modifications:
//
// ****************************************************************************
void
ColorTableAttributes::DecrementTagNumRefs(const std::string tagname)
{
    DecrementTagNumRefs(GetIndexOfTag(tagname));
}

// ****************************************************************************
// Method: ColorTableAttributes::DecrementTagNumRefs
//
// Purpose:
//    Decrements the number of references to a given tag in the tag list by
//    index.
//
// Programmer: Justin Privitera
// Creation:   08/10/23
//
// Modifications:
//
// ****************************************************************************
void
ColorTableAttributes::DecrementTagNumRefs(const int index)
{
    if (index >= 0 && index < tagListNumRefs.size())
        tagListNumRefs[index] --;
    SelectTagList();
}

// ****************************************************************************
// Method: ColorTableAttributes::GetTagNumRefs
//
// Purpose:
//    Returns how many color tables have this tag (numrefs).
//
// Programmer: Justin Privitera
// Creation:   06/27/23
//
// Modifications:
//
// ****************************************************************************
int
ColorTableAttributes::GetTagNumRefs(const std::string tagname)
{
    int index = GetIndexOfTag(tagname);
    if (index >= 0 && index < tagListNumRefs.size())
        return tagListNumRefs[index];
    return 0; // the tag has no references if it doesn't exist
}

// ****************************************************************************
// Method: ColorTableAttributes::SetTagTableItemFlag
//
// Purpose:
//    Sets a particular tag's tag table item flag by name.
//
// Programmer: Justin Privitera
// Creation:   06/27/23
//
// Modifications:
//
// ****************************************************************************
void
ColorTableAttributes::SetTagTableItemFlag(const std::string tagname,
                                          const bool tagTableItemExists)
{
    SetTagTableItemFlag(GetIndexOfTag(tagname), tagTableItemExists);
}

// ****************************************************************************
// Method: ColorTableAttributes::SetTagTableItemFlag
//
// Purpose:
//    Sets a particular tag's tag table item flag by index.
//
// Programmer: Justin Privitera
// Creation:   06/27/23
//
// Modifications:
//
// ****************************************************************************
void
ColorTableAttributes::SetTagTableItemFlag(const int index,
                                          const bool tagTableItemExists)
{
    if (index >= 0 && index < tagListTableItemFlag.size())
        tagListTableItemFlag[index] = tagTableItemExists;
    SelectTagList();
}

// ****************************************************************************
// Method: ColorTableAttributes::GetTagTableItemFlag
//
// Purpose:
//    Returns whether or not the provided tag has a tag table item.
//
// Programmer: Justin Privitera
// Creation:   06/27/23
//
// Modifications:
//
// ****************************************************************************
bool
ColorTableAttributes::GetTagTableItemFlag(const std::string tagname)
{
    int index = GetIndexOfTag(tagname);
    if (index >= 0 && index < tagListTableItemFlag.size())
        return tagListTableItemFlag[index];
    // the tag is unlikely to have a tag table item if it doesn't exist
    return false;
}

// ****************************************************************************
// Method: ColorTableAttributes::CheckTagInTagList
//
// Purpose:
//    Returns true if the tag is in the tag list, and false otherwise.
//
// Programmer: Justin Privitera
// Creation:   06/27/23
//
// Modifications:
//
// ****************************************************************************
bool
ColorTableAttributes::CheckTagInTagList(const std::string tagname)
{
    return GetIndexOfTag(tagname) >= 0;
}

// ****************************************************************************
// Method: ColorTableAttributes::RemoveUnusedTagsFromTagTable
//
// Purpose:
//    Removes tags and their metadata from the tag list if their refcount is 
//    less than or equal to 0. Collects a list of tag names that represent tags
//    that have been removed and have tag table items, so that the caller can
//    delete the tag table items.
//
// Programmer: Justin Privitera
// Creation:   06/27/23
//
// Modifications:
//
// ****************************************************************************
std::vector<std::string>
ColorTableAttributes::RemoveUnusedTagsFromTagTable()
{
    std::vector<std::string> removedTags;

    auto namesItr = tagListNames.begin();
    auto activeItr = tagListActive.begin();
    auto numrefsItr = tagListNumRefs.begin();
    auto tableitemflagItr = tagListTableItemFlag.begin();

    // these four vectors are all the same length so we can get away with this
    while (namesItr != tagListNames.end())
    {
        if (*numrefsItr <= 0)
        {
            // if this tag list item has an associated tag table entry
            if (*tableitemflagItr)
                removedTags.push_back(*namesItr);
            // else - there is no tag table entry to delete so we can continue

            // remove this entry from the tag list
            namesItr = tagListNames.erase(namesItr);
            activeItr = tagListActive.erase(activeItr);
            numrefsItr = tagListNumRefs.erase(numrefsItr);
            tableitemflagItr = tagListTableItemFlag.erase(tableitemflagItr);
        }
        else
        {
            namesItr ++;
            activeItr ++;
            numrefsItr ++;
            tableitemflagItr ++;
        }
    }

    return removedTags;
}

// ****************************************************************************
// Method: ColorTableAttributes::GetNewTagNames
//
// Purpose:
//    Gets the names of tags that should be added to the tag table.
//
// Programmer: Justin Privitera
// Creation:   06/27/23
//
// Modifications:
//    Justin Privitera, Tue Sep  5 12:49:42 PDT 2023
//    Greatly simplified function; uses internal bookkeeping to see if tags
//    need to be added to the tag table.
// ****************************************************************************
std::vector<std::string>
ColorTableAttributes::GetNewTagNames()
{
    std::vector<std::string> tagsToAdd;
    for (size_t i = 0; i < tagListNames.size(); i ++)
    {
        // if this tag has no tag table item
        if (! tagListTableItemFlag[i])
            tagsToAdd.emplace_back(tagListNames[i]);
    }
    return tagsToAdd;
}

// ****************************************************************************
// Method: ColorTableAttributes::FilterTableByTag
//
// Purpose:
//    Determines whether or not a given color table falls within the current
//    tag filtering selection.
//
// Programmer: Justin Privitera
// Creation:   07/13/23
//
// Modifications:
//
// ****************************************************************************
bool
ColorTableAttributes::FilterTableByTag(const ColorControlPointList &ccpl)
{
    bool tagFound = false;
    // go thru global tags
    for (size_t i = 0; i < tagListNames.size(); i ++)
    {
        // if the global tag is active
        if (tagListActive[i])
        {
            tagFound = false;
            // go thru local tags
            for (int localTagId = 0; localTagId < ccpl.GetNumTags(); localTagId ++)
            {
                // if the current global tag is the same as our local tag
                if (tagListNames[i] == ccpl.GetTag(localTagId))
                {
                    tagFound = true;
                    break;
                }
            }
            if (tagFound == GetTagsMatchAny())
            // If both are true, that means...
            // 1) tagsMatchAny is true so we only need one tag from 
            //    the global tag list to be present in the local tag
            //    list, AND
            // 2) tagFound is true, so there is no need to keep 
            //    searching for a tag that is in both the local and
            //    global tag lists. Thus we can end iteration early.
            // If both are false, that means...
            // 1) tagsMatchAny is false so we need every tag from the
            //    global tag list to be present in the local tag list, AND
            // 2) tagFound is false, so there exists a global tag that
            //    is not in the local tag list, hence we can give up 
            //    early because we know that this color table does not
            //    have every tag in the global tag list.
                break;
        }
    }
    // we mark the color table as active or inactive
    return tagFound;
}

// ****************************************************************************
// Method: ColorTableAttributes::FilterTablesByTag
//
// Purpose:
//    Filters color tables by tag, setting tables to active if they are within
//    the current tag filtering selection.
//
// Programmer: Justin Privitera
// Creation:   06/27/23
//
// Modifications:
//    Justin Privitera, Tue Sep  5 12:49:42 PDT 2023
//    Select the CT list after we have filtered.
// ****************************************************************************
void
ColorTableAttributes::FilterTablesByTag()
{
    // for each color table
    for (int i = 0; i < GetNumColorTables(); i ++)
    {
        // we mark the color table as active or inactive
        if (i >= 0 && i < colorTableActiveFlags.size())
            colorTableActiveFlags[i] = FilterTableByTag(GetColorTables(i));;
    }
    SelectColorTablesList();
}

// ****************************************************************************
// Method: ColorTableAttributes::PrintTagList
//
// Purpose:
//    Prints the tag list. Useful for debugging purposes. This function is not
//    used anywhere.
//
// Programmer: Justin Privitera
// Creation:   06/27/23
//
// Modifications:
//
// ****************************************************************************
void
ColorTableAttributes::PrintTagList()
{
    auto namesItr = tagListNames.begin();
    auto activeItr = tagListActive.begin();
    auto numrefsItr = tagListNumRefs.begin();
    auto tableitemflagItr = tagListTableItemFlag.begin();

    // these four vectors are all the same length so we can get away with this
    while (namesItr != tagListNames.end())
    {
        std::cout << *namesItr << ":" << std::endl;
        std::cout << "   active: " << *activeItr << std::endl;
        std::cout << "   numrefs: " << *numrefsItr << std::endl;
        std::cout << "   tagTableItem valid: " << (*tableitemflagItr ? "yes" : "no") << std::endl;

        namesItr ++;
        activeItr ++;
        numrefsItr ++;
        tableitemflagItr ++;
    }
}

// ****************************************************************************
// Method: ColorTableAttributes::ProcessOldVersions
//
// Purpose:
//   This method allows handling of older config/session files that may
//   contain fields that are no longer present or have been modified/renamed.
//
// Programmer: Justin Privitera
// Creation:   May 26 2022
//
// Modifications:
//   Justin Privitera, Wed Feb  1 14:43:55 PST 2023
//   Add logic for `taggingFlag`, removed in 3.4.
//
// ****************************************************************************
#include <visit-config.h>
#ifdef VIEWER
#include <avtCallback.h>
#endif

void
ColorTableAttributes::ProcessOldVersions(DataNode *parentNode,
                                         const char *configVersion)
{
#if VISIT_OBSOLETE_AT_VERSION(3,5,0)
#error This code is obsolete in this version. Please remove it.
#else
    if(parentNode == 0)
        return;

    DataNode *searchNode = parentNode->GetNode("ColorTableAttributes");
    if(searchNode == 0)
        return;

    if (VersionLessThan(configVersion, "3.3.0"))
    {
        DataNode *k = 0;
        if ((k = searchNode->GetNode("activeContinuous")) != 0)
        {
#ifdef VIEWER
            avtCallback::IssueWarning(DeprecationMessage("activeContinuous", "3.5.0"));
#endif
            searchNode->AddNode(new DataNode("defaultContinuous", k->AsString()));
            searchNode->RemoveNode(k);
        }
        if ((k = searchNode->GetNode("activeDiscrete")) != 0)
        {
#ifdef VIEWER
            avtCallback::IssueWarning(DeprecationMessage("activeDiscrete", "3.5.0"));
#endif
            searchNode->AddNode(new DataNode("defaultDiscrete", k->AsString()));
            searchNode->RemoveNode(k);
        }
    }
#endif

#if VISIT_OBSOLETE_AT_VERSION(3,6,0)
#error This code is obsolete in this version. Please remove it.
#else
    if(parentNode == 0)
        return;

    DataNode *searchNode2 = parentNode->GetNode("ColorTableAttributes");
    if(searchNode2 == 0)
        return;

    if (VersionLessThan(configVersion, "3.4.0"))
    {
        DataNode *k = 0;
        if ((k = searchNode2->GetNode("taggingFlag")) != 0)
        {
#ifdef VIEWER
            avtCallback::IssueWarning(DeprecationMessage("taggingFlag", "3.6.0"));
#endif
            searchNode2->RemoveNode(k);
        }
    }
#endif
}

