// 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 <ColorControlPointList.h>
#include <DataNode.h>
#include <DebugStream.h>
#include <algorithm>
#include <cctype>
#include <string.h>
#include <ColorControlPoint.h>

//
// Enum conversion methods for ColorControlPointList::SmoothingMethod
//

static const char *SmoothingMethod_strings[] = {
"None", "Linear", "CubicSpline"
};

std::string
ColorControlPointList::SmoothingMethod_ToString(ColorControlPointList::SmoothingMethod t)
{
    int index = int(t);
    if(index < 0 || index >= 3) index = 0;
    return SmoothingMethod_strings[index];
}

std::string
ColorControlPointList::SmoothingMethod_ToString(int t)
{
    int index = (t < 0 || t >= 3) ? 0 : t;
    return SmoothingMethod_strings[index];
}

bool
ColorControlPointList::SmoothingMethod_FromString(const std::string &s, ColorControlPointList::SmoothingMethod &val)
{
    val = ColorControlPointList::None;
    for(int i = 0; i < 3; ++i)
    {
        if(s == SmoothingMethod_strings[i])
        {
            val = (SmoothingMethod)i;
            return true;
        }
    }
    return false;
}

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

void ColorControlPointList::Init()
{
    smoothing = Linear;
    equalSpacingFlag = false;
    discreteFlag = false;
    externalFlag = false;
    builtIn = true;

    ColorControlPointList::SelectAll();
}

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

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

    // *** Copy the controlPoints field ***
    // Delete the AttributeGroup objects and clear the vector.
    for(pos = controlPoints.begin(); pos != controlPoints.end(); ++pos)
        delete *pos;
    controlPoints.clear();
    if(obj.controlPoints.size() > 0)
        controlPoints.reserve(obj.controlPoints.size());
    // Duplicate the controlPoints from obj.
    for(pos = obj.controlPoints.begin(); pos != obj.controlPoints.end(); ++pos)
    {
        ColorControlPoint *oldColorControlPoint = (ColorControlPoint *)(*pos);
        ColorControlPoint *newColorControlPoint = new ColorControlPoint(*oldColorControlPoint);
        controlPoints.push_back(newColorControlPoint);
    }

    smoothing = obj.smoothing;
    equalSpacingFlag = obj.equalSpacingFlag;
    discreteFlag = obj.discreteFlag;
    externalFlag = obj.externalFlag;
    tagNames = obj.tagNames;
    builtIn = obj.builtIn;

    ColorControlPointList::SelectAll();
}

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


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

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

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

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

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

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

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

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

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

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

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

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

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

    ColorControlPointList::Copy(obj);

    return *this;
}

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

bool
ColorControlPointList::operator == (const ColorControlPointList &obj) const
{
    bool controlPoints_equal = (obj.controlPoints.size() == controlPoints.size());
    for(size_t i = 0; (i < controlPoints.size()) && controlPoints_equal; ++i)
    {
        // Make references to ColorControlPoint from AttributeGroup *.
        const ColorControlPoint &controlPoints1 = *((const ColorControlPoint *)(controlPoints[i]));
        const ColorControlPoint &controlPoints2 = *((const ColorControlPoint *)(obj.controlPoints[i]));
        controlPoints_equal = (controlPoints1 == controlPoints2);
    }

    // Create the return value
    return (controlPoints_equal &&
            (smoothing == obj.smoothing) &&
            (equalSpacingFlag == obj.equalSpacingFlag) &&
            (discreteFlag == obj.discreteFlag) &&
            (externalFlag == obj.externalFlag) &&
            (tagNames == obj.tagNames) &&
            (builtIn == obj.builtIn));
}

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

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

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

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

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

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

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

    return true;
}

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

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

    return retval;
}

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

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

    return retval;
}

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

void
ColorControlPointList::SelectAll()
{
    Select(ID_controlPoints,    (void *)&controlPoints);
    Select(ID_smoothing,        (void *)&smoothing);
    Select(ID_equalSpacingFlag, (void *)&equalSpacingFlag);
    Select(ID_discreteFlag,     (void *)&discreteFlag);
    Select(ID_externalFlag,     (void *)&externalFlag);
    Select(ID_tagNames,         (void *)&tagNames);
    Select(ID_builtIn,          (void *)&builtIn);
}

// ****************************************************************************
// Method: ColorControlPointList::CreateSubAttributeGroup
//
// Purpose:
//   This class contains a list of ColorControlPoint objects.
//
// Note:       Autogenerated by xml2atts.
//
// Programmer: xml2atts
// Creation:   omitted
//
// Modifications:
//
// ****************************************************************************

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

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

// ****************************************************************************
// Method: ColorControlPointList::CreateNode
//
// Purpose:
//   This method creates a DataNode representation of the object so it can be
//   saved to a config file.
//
// Note:  differs from the autogenerated version in that 'equalSpacingFlag',
//        'discreteFlag', 'externalFlag' & 'categoryName' are shortened.
//
// Programmer: xml2atts
// Creation:   omitted
//
// Modifications:
//   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
//   Added logic for "builtin" attribute.
//
// ****************************************************************************

bool
ColorControlPointList::CreateNode(DataNode *parentNode, bool completeSave, bool forceAdd)
{
    if(parentNode == 0)
        return false;

    ColorControlPointList defaultObject;
    bool addToParent = false;
    // Create a node for ColorControlPointList.
    DataNode *node = new DataNode("ColorControlPointList");

    if(completeSave || !FieldsEqual(ID_controlPoints, &defaultObject))
    {
        addToParent = true;
        for(size_t i = 0; i < controlPoints.size(); ++i)
            controlPoints[i]->CreateNode(node, completeSave, true);
    }

    if(completeSave || !FieldsEqual(ID_smoothing, &defaultObject))
    {
        addToParent = true;
        node->AddNode(new DataNode("smoothing", SmoothingMethod_ToString(smoothing)));
    }

    if(completeSave || !FieldsEqual(ID_equalSpacingFlag, &defaultObject))
    {
        addToParent = true;
        node->AddNode(new DataNode("equal", equalSpacingFlag));
    }

    if(completeSave || !FieldsEqual(ID_discreteFlag, &defaultObject))
    {
        addToParent = true;
        node->AddNode(new DataNode("discrete", discreteFlag));
    }

    if(completeSave || !FieldsEqual(ID_tagNames, &defaultObject))
    {
        addToParent = true;
        node->AddNode(new DataNode("tags", tagNames));
    }

    if(completeSave || !FieldsEqual(ID_builtIn, &defaultObject))
    {
        addToParent = true;
        node->AddNode(new DataNode("builtin", builtIn));
    }

    // Add the node to the parent node.
    if(addToParent || forceAdd)
        parentNode->AddNode(node);
    else
        delete node;

    return (addToParent || forceAdd);
}
// ****************************************************************************
// Method: ColorControlPointList::SetFromNode
//
// Purpose:
//   This method sets attributes in this object from values in a DataNode representation of the object.
//
// Note:       Autogenerated by xml2atts.
//
// Programmer: xml2atts
// Creation:   Wed Jul 23 11:29:47 PDT 2003
//
// Modifications:
//   Brad Whitlock, Tue Oct 21 16:22:31 PST 2003
//   I added support for reading in colors and positions in their compact form.
//
//   Brad Whitlock, Fri Apr 27 11:26:48 PDT 2012
//   Support different smoothings.
//
//   Kathleen Biagas, Thu Jul 31 09:28:48 PDT 2014
//   Modified to reflect how 'equalSpacingFlag' and 'discreteFlag' are
//   actually saved: as 'equal' and 'discrete'.  Add category.
// 
//   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
//   Added logic for "builtin" attribute.
//
// ****************************************************************************

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

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

    DataNode *node;
    DataNode **children;
    // Clear all the ColorControlPoints.
    ClearControlPoints();

    //
    // Try setting the colors from the compact color and position vectors.
    //
    bool colorsAreSet = false;
    DataNode *compactColorNode = searchNode->GetNode("compactColors");
    DataNode *compactPositionNode = searchNode->GetNode("compactPositions");
    if(compactColorNode != 0 && compactPositionNode != 0)
    {
        const unsignedCharVector &colors = compactColorNode->AsUnsignedCharVector();
        const floatVector &positions = compactPositionNode->AsFloatVector();
        size_t npts = colors.size() / 4;
        if(npts > positions.size())
            npts = positions.size();
        if(npts > 0)
        {
            for(size_t i = 0; i < npts; ++i)
            {
                size_t index = i << 2;
                AddControlPoints(ColorControlPoint(positions[i],
                    colors[index],
                    colors[index+1],
                    colors[index+2],
                    colors[index+3]));
            }

            colorsAreSet = true;
        }
    }

    if(!colorsAreSet)
    {
        // Go through all of the children and construct a new
        // ColorControlPoint for each one of them.
        children = searchNode->GetChildren();
        for(int i = 0; i < searchNode->GetNumChildren(); ++i)
        {
            if(children[i]->GetKey() == std::string("ColorControlPoint"))
            {
                ColorControlPoint temp;
                temp.SetFromNode(children[i]);
                AddControlPoints(temp);
            }
        }
    }

    // Legacy support.
    if((node = searchNode->GetNode("smoothingFlag")) != 0)
        SetSmoothing(node->AsBool()?Linear:None);

    if((node = searchNode->GetNode("smoothing")) != 0)
    {
        // Allow enums to be int or string in the config file
        if(node->GetNodeType() == INT_NODE)
        {
            int ival = node->AsInt();
            if(ival >= 0 && ival < 3)
                SetSmoothing(SmoothingMethod(ival));
        }
        else if(node->GetNodeType() == STRING_NODE)
        {
            SmoothingMethod value;
            if(SmoothingMethod_FromString(node->AsString(), value))
                SetSmoothing(value);
        }
    }
    if((node = searchNode->GetNode("equal")) != 0)
        SetEqualSpacingFlag(node->AsBool());
    else if((node = searchNode->GetNode("equalSpacingFlag")) != 0)
        SetEqualSpacingFlag(node->AsBool());
    if((node = searchNode->GetNode("discrete")) != 0)
        SetDiscreteFlag(node->AsBool());
    else if((node = searchNode->GetNode("discreteFlag")) != 0)
        SetDiscreteFlag(node->AsBool());
    if((node = searchNode->GetNode("external")) != 0)
        SetExternalFlag(node->AsBool());
    else if((node = searchNode->GetNode("externalFlag")) != 0)
        SetExternalFlag(node->AsBool());
    if((node = searchNode->GetNode("tags")) != 0)
        SetTagNames(node->AsStringVector());
    if((node = searchNode->GetNode("builtin")) != 0)
        SetBuiltIn(node->AsBool());
}
///////////////////////////////////////////////////////////////////////////////
// Set property methods
///////////////////////////////////////////////////////////////////////////////

void
ColorControlPointList::SetSmoothing(ColorControlPointList::SmoothingMethod smoothing_)
{
    smoothing = smoothing_;
    Select(ID_smoothing, (void *)&smoothing);
}

void
ColorControlPointList::SetEqualSpacingFlag(bool equalSpacingFlag_)
{
    equalSpacingFlag = equalSpacingFlag_;
    Select(ID_equalSpacingFlag, (void *)&equalSpacingFlag);
}

void
ColorControlPointList::SetDiscreteFlag(bool discreteFlag_)
{
    discreteFlag = discreteFlag_;
    Select(ID_discreteFlag, (void *)&discreteFlag);
}

void
ColorControlPointList::SetExternalFlag(bool externalFlag_)
{
    externalFlag = externalFlag_;
    Select(ID_externalFlag, (void *)&externalFlag);
}

// ****************************************************************************
// Method: ColorControlPointList::SetTagNames
//
// Purpose:
//   Setter for tagNames.
//
// Note:       We need a custom setter to vet the tag names before they are
//             added to the tagNames vector.
//
// Programmer: Justin Privitera
// Creation:   Wed Jun 29 16:38:18 PDT 2022
//
// Modifications:
//     Justin Privitera, Thu Aug 25 15:04:55 PDT 2022
//     Validate tags before adding them to the list.
// 
//    Justin Privitera, Thu Sep 29 17:27:37 PDT 2022
//    Replace braces with parens for auto.
//
// ****************************************************************************

void
ColorControlPointList::SetTagNames(const stringVector &tagNames_)
{
    std::for_each(tagNames_.begin(), tagNames_.end(),
        [this, tagNames_](std::string currtag)
        {
            auto result(ValidateTag(currtag));
            if (result.first)
                AddTag(currtag);
            else
                debug1 << "ColorControlPointList WARNING: " << result.second;
        });
    Select(ID_tagNames, (void *)&tagNames);
}

void
ColorControlPointList::SetBuiltIn(bool builtIn_)
{
    builtIn = builtIn_;
    Select(ID_builtIn, (void *)&builtIn);
}

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

const AttributeGroupVector &
ColorControlPointList::GetControlPoints() const
{
    return controlPoints;
}

AttributeGroupVector &
ColorControlPointList::GetControlPoints()
{
    return controlPoints;
}

ColorControlPointList::SmoothingMethod
ColorControlPointList::GetSmoothing() const
{
    return SmoothingMethod(smoothing);
}

bool
ColorControlPointList::GetEqualSpacingFlag() const
{
    return equalSpacingFlag;
}

bool
ColorControlPointList::GetDiscreteFlag() const
{
    return discreteFlag;
}

bool
ColorControlPointList::GetExternalFlag() const
{
    return externalFlag;
}

const stringVector &
ColorControlPointList::GetTagNames() const
{
    return tagNames;
}

stringVector &
ColorControlPointList::GetTagNames()
{
    return tagNames;
}

bool
ColorControlPointList::GetBuiltIn() const
{
    return builtIn;
}

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

void
ColorControlPointList::SelectControlPoints()
{
    Select(ID_controlPoints, (void *)&controlPoints);
}

void
ColorControlPointList::SelectTagNames()
{
    Select(ID_tagNames, (void *)&tagNames);
}

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

// ****************************************************************************
// Method: ColorControlPointList::AddControlPoints
//
// Purpose:
//   This class contains a list of ColorControlPoint objects.
//
// Note:       Autogenerated by xml2atts.
//
// Programmer: xml2atts
// Creation:   omitted
//
// Modifications:
//
// ****************************************************************************

void
ColorControlPointList::AddControlPoints(const ColorControlPoint &obj)
{
    ColorControlPoint *newColorControlPoint = new ColorControlPoint(obj);
    controlPoints.push_back(newColorControlPoint);

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

// ****************************************************************************
// Method: ColorControlPointList::ClearControlPoints
//
// Purpose:
//   This class contains a list of ColorControlPoint objects.
//
// Note:       Autogenerated by xml2atts.
//
// Programmer: xml2atts
// Creation:   omitted
//
// Modifications:
//
// ****************************************************************************

void
ColorControlPointList::ClearControlPoints()
{
    AttributeGroupVector::iterator pos;

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

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

// ****************************************************************************
// Method: ColorControlPointList::RemoveControlPoints
//
// Purpose:
//   This class contains a list of ColorControlPoint objects.
//
// Note:       Autogenerated by xml2atts.
//
// Programmer: xml2atts
// Creation:   omitted
//
// Modifications:
//
// ****************************************************************************

void
ColorControlPointList::RemoveControlPoints(int index)
{
    AttributeGroupVector::iterator pos = controlPoints.begin();

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

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

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

// ****************************************************************************
// Method: ColorControlPointList::GetNumControlPoints
//
// Purpose:
//   This class contains a list of ColorControlPoint objects.
//
// Note:       Autogenerated by xml2atts.
//
// Programmer: xml2atts
// Creation:   omitted
//
// Modifications:
//
// ****************************************************************************

int
ColorControlPointList::GetNumControlPoints() const
{
    return (int)controlPoints.size();
}

// ****************************************************************************
// Method: ColorControlPointList::GetControlPoints
//
// Purpose:
//   This class contains a list of ColorControlPoint objects.
//
// Note:       Autogenerated by xml2atts.
//
// Programmer: xml2atts
// Creation:   omitted
//
// Modifications:
//
// ****************************************************************************

ColorControlPoint &
ColorControlPointList::GetControlPoints(int i)
{
    return *((ColorControlPoint *)controlPoints[i]);
}

// ****************************************************************************
// Method: ColorControlPointList::GetControlPoints
//
// Purpose:
//   This class contains a list of ColorControlPoint objects.
//
// Note:       Autogenerated by xml2atts.
//
// Programmer: xml2atts
// Creation:   omitted
//
// Modifications:
//
// ****************************************************************************

const ColorControlPoint &
ColorControlPointList::GetControlPoints(int i) const
{
    return *((ColorControlPoint *)controlPoints[i]);
}

// ****************************************************************************
// Method: ColorControlPointList::operator []
//
// Purpose:
//   This class contains a list of ColorControlPoint objects.
//
// Note:       Autogenerated by xml2atts.
//
// Programmer: xml2atts
// Creation:   omitted
//
// Modifications:
//
// ****************************************************************************

ColorControlPoint &
ColorControlPointList::operator [] (int i)
{
    return *((ColorControlPoint *)controlPoints[i]);
}

// ****************************************************************************
// Method: ColorControlPointList::operator []
//
// Purpose:
//   This class contains a list of ColorControlPoint objects.
//
// Note:       Autogenerated by xml2atts.
//
// Programmer: xml2atts
// Creation:   omitted
//
// Modifications:
//
// ****************************************************************************

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

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

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

std::string
ColorControlPointList::GetFieldName(int index) const
{
    switch (index)
    {
    case ID_controlPoints:    return "controlPoints";
    case ID_smoothing:        return "smoothing";
    case ID_equalSpacingFlag: return "equalSpacingFlag";
    case ID_discreteFlag:     return "discreteFlag";
    case ID_externalFlag:     return "externalFlag";
    case ID_tagNames:         return "tagNames";
    case ID_builtIn:          return "builtIn";
    default:  return "invalid index";
    }
}

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

AttributeGroup::FieldType
ColorControlPointList::GetFieldType(int index) const
{
    switch (index)
    {
    case ID_controlPoints:    return FieldType_attVector;
    case ID_smoothing:        return FieldType_enum;
    case ID_equalSpacingFlag: return FieldType_bool;
    case ID_discreteFlag:     return FieldType_bool;
    case ID_externalFlag:     return FieldType_bool;
    case ID_tagNames:         return FieldType_stringVector;
    case ID_builtIn:          return FieldType_bool;
    default:  return FieldType_unknown;
    }
}

// ****************************************************************************
// Method: ColorControlPointList::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
ColorControlPointList::GetFieldTypeName(int index) const
{
    switch (index)
    {
    case ID_controlPoints:    return "attVector";
    case ID_smoothing:        return "enum";
    case ID_equalSpacingFlag: return "bool";
    case ID_discreteFlag:     return "bool";
    case ID_externalFlag:     return "bool";
    case ID_tagNames:         return "stringVector";
    case ID_builtIn:          return "bool";
    default:  return "invalid index";
    }
}

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

bool
ColorControlPointList::FieldsEqual(int index_, const AttributeGroup *rhs) const
{
    const ColorControlPointList &obj = *((const ColorControlPointList*)rhs);
    bool retval = false;
    switch (index_)
    {
    case ID_controlPoints:
        {  // new scope
        bool controlPoints_equal = (obj.controlPoints.size() == controlPoints.size());
        for(size_t i = 0; (i < controlPoints.size()) && controlPoints_equal; ++i)
        {
            // Make references to ColorControlPoint from AttributeGroup *.
            const ColorControlPoint &controlPoints1 = *((const ColorControlPoint *)(controlPoints[i]));
            const ColorControlPoint &controlPoints2 = *((const ColorControlPoint *)(obj.controlPoints[i]));
            controlPoints_equal = (controlPoints1 == controlPoints2);
        }

        retval = controlPoints_equal;
        }
        break;
    case ID_smoothing:
        {  // new scope
        retval = (smoothing == obj.smoothing);
        }
        break;
    case ID_equalSpacingFlag:
        {  // new scope
        retval = (equalSpacingFlag == obj.equalSpacingFlag);
        }
        break;
    case ID_discreteFlag:
        {  // new scope
        retval = (discreteFlag == obj.discreteFlag);
        }
        break;
    case ID_externalFlag:
        {  // new scope
        retval = (externalFlag == obj.externalFlag);
        }
        break;
    case ID_tagNames:
        {  // new scope
        retval = (tagNames == obj.tagNames);
        }
        break;
    case ID_builtIn:
        {  // new scope
        retval = (builtIn == obj.builtIn);
        }
        break;
    default: retval = false;
    }

    return retval;
}

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

// ****************************************************************************
// Method: ColorControlPointList::EvalCubicSpline
//
// Purpose:
//   Interpolates color control points using cubic spline.
//
// Arguments:
//   t    : The x value along the curve.
//   allX : All of the x values for the points that define the curve.
//   allY : All of the y values for the points that define the curve.
//   n    : The number of points.
//
// Returns:    The new y value.
//
// Note:
//    This function is very similar to QvisSpectrumBar::evalCubicSpline.
//    It would be wise to make changes in both places.
//
// Programmer: Brad Whitlock
// Creation:   Fri Apr 27 14:02:56 PDT 2012
//
// Modifications:
//
// ****************************************************************************


float
ColorControlPointList::EvalCubicSpline(float t, const float *allX, const float *allY, int n) const
{
    // If before first sample, return first sample value.
    if (t < allX[0])
        return allY[0];

    // If after last sample, return last sample value.
    if (t > allX[n-1])
        return allY[n-1];

    // Search and bracket the indices where t is closest to but less than an x.
    int i = 0;
    for(i = 0; i < n; ++i)
        if(allX[i] >= t)
            break;

    // Identify the 4 indices, 2 before and 1 after, around the one seleced.
    int idx[4];
    idx[0] = std::max(i-2, 0);
    idx[1] = std::max(i-1, 0);
    idx[2] = i;
    idx[3] = std::min(i+1, n-1);

    // Obtain the x and y values for these 4 points.
    float X[4], Y[4];
    for(int j = 0; j < 4; ++j)
    {
        X[j] = allX[idx[j]];
        Y[j] = allY[idx[j]];
    }

    // Do the cubic spline interpolation on these 4 points.
    // Which interpolation is this? Natural, Not-a-knot, periodic?
    float dx = (X[2] - X[1]);
    float invdx = 1. / dx;
    float dy1   = (Y[2] + (Y[0] * -1.)) * (1. / (X[2] - X[0]));
    float dy2   = (Y[2] + (Y[1] * -1.)) * invdx;
    float dy3   = (Y[3] + (Y[1] * -1.)) * (1. / (X[3] - X[1]));
    float ddy2  = (dy2 + (dy1 * -1)) * invdx;
    float ddy3  = (dy3 + (dy2 * -1)) * invdx;
    float dddy3 = (ddy3 + (ddy2 * -1)) * invdx;
    float u = (t - X[1]);
    return (Y[1] + dy1*u + ddy2*u*u + dddy3*u*u*(u-dx));
}

// ****************************************************************************
// Method: ColorControlPointList::GetColorsCubicSpline
//
// Purpose:
//   Gets the colors using cubic spline interpolation.
//
// Arguments:
//
// Returns:
//
// Note:
//    This function is very similar to QvisSpectrumBar::getColorsCubicSpline.
//    It would be wise to make changes in both places.
//
// Programmer: Brad Whitlock
// Creation:   Fri Apr 27 14:02:56 PDT 2012
//
// Modifications:
//
// ****************************************************************************

inline unsigned char
ClampColor(float val)
{
    if (val < 0.f)
        return 0;
    if (val > 1.f)
        return 255;
    return static_cast<unsigned char>((val * 255.f) + 0.5f);
}

void
ColorControlPointList::GetColorsCubicSpline(unsigned char *rgb,
                                            int ncolors,
                                            unsigned char *alpha) const
{
    int npoints = GetNumControlPoints();
    float *x = new float[npoints];
    float *r = new float[npoints];
    float *g = new float[npoints];
    float *b = new float[npoints];
    float *a = new float[npoints];

    // This first loop just converts r,g,b,a values in color control point data
    // from unsigned char to float. It also computes the x-values for either the
    // equal spacing case or uses whetever the position value is for each color
    // Note that this loop is over npoints...the number of color control points.
    for(int i = 0; i < npoints; ++i)
    {
        const ColorControlPoint &cpt = this->operator[](i);
        if(GetEqualSpacingFlag())
        {
            float t = float(i) / float(npoints-1);
            x[i] = t;
        }
        else
            x[i] = cpt.GetPosition();

        r[i] = float(cpt.GetColors()[0]) / 255.f;
        g[i] = float(cpt.GetColors()[1]) / 255.f;
        b[i] = float(cpt.GetColors()[2]) / 255.f;
        a[i] = float(cpt.GetColors()[3]) / 255.f;
    }

    // From the above, we effectively have 4 "curves" of r(x), g(x), b(x) and a(x).

    // In the loop below, we use each of these curves to compute ncolors samples
    // equally spaced in x of each curve. Each sample is converted back from float
    // to unsigned char and then stored in its respective position in the returned
    // rgb array.
    int idx = 0;
    for(int i = 0; i < ncolors; ++i, idx += 3)
    {
        float t = float(i) / float(ncolors-1);
        rgb[idx  ] = ClampColor(EvalCubicSpline(t, x, r, npoints));
        rgb[idx+1] = ClampColor(EvalCubicSpline(t, x, g, npoints));
        rgb[idx+2] = ClampColor(EvalCubicSpline(t, x, b, npoints));
        if(alpha != NULL)
            alpha[i] = ClampColor(EvalCubicSpline(t, x, a, npoints));
    }

    delete [] x;
    delete [] r;
    delete [] g;
    delete [] b;
    delete [] a;
}

// ****************************************************************************
// Method: ColorControlPointList::GetColors
//
// Purpose:
//   This method samples the color control points and fills an array of
//   colors.
//
// Arguments:
//   rgb     : The array in which to return the sampled colors.
//   ncolors : The number of colors we expect to return in the array.
//   alpha   : An optional array in which to return the sampled colors' alphas.
//
// Programmer: Brad Whitlock
// Creation:   Thu Nov 21 14:58:48 PST 2002
//
// Modifications:
//
//   Hank Childs, Sat Jan 27 12:03:56 PST 2007
//   Fix indexing bug that can come up with huge numbers of colors.  This
//   bug ultimately led to memory overwrites and a crash.
//
//   Jeremy Meredith, Fri Feb 20 15:06:36 EST 2009
//   Added optional alpha channel support (can set to NULL if not wanted).
//
//   Brad Whitlock, Fri Apr 27 14:04:13 PDT 2012
//   Added cubic spline method.
//
// ****************************************************************************

void
ColorControlPointList::GetColors(unsigned char *rgb,
                                 int ncolors,
                                 unsigned char *alpha) const
{
    if(GetSmoothing() == CubicSpline && !GetDiscreteFlag())
    {
        GetColorsCubicSpline(rgb, ncolors, alpha);
        return;
    }

    int i, ci, c = 0;
    float *newPts_pos = NULL;
    float *newPts_r = NULL;
    float *newPts_g = NULL;
    float *newPts_b = NULL;
    float *newPts_a = NULL;

    float *oldPts_pos = NULL;
    float *oldPts_r = NULL;
    float *oldPts_g = NULL;
    float *oldPts_b = NULL;
    float *oldPts_a = NULL;

    float *c1_pos = NULL;
    float *c1_r = NULL;
    float *c1_g = NULL;
    float *c1_b = NULL;
    float *c1_a = NULL;

    float *c2_pos = NULL;
    float *c2_r = NULL;
    float *c2_g = NULL;
    float *c2_b = NULL;
    float *c2_a = NULL;

    /*******************************************
     * Phase I -- Get some values from the color
     *            control point list and put into
     *            local vars.
     *******************************************/
    int npoints = GetNumControlPoints();
    bool equal  = GetEqualSpacingFlag();
    bool smooth = GetSmoothing() == Linear;
    bool discrete = GetDiscreteFlag();

    /*******************************************
     * Phase II -- Determine the number of
     *             control points needed and
     *             allocate storage.
     ******************************************/
    if(equal || !smooth || discrete)
    {
        oldPts_pos = new float[npoints + 1];
        oldPts_r   = new float[npoints + 1];
        oldPts_g   = new float[npoints + 1];
        oldPts_b   = new float[npoints + 1];
        oldPts_a   = new float[npoints + 1];
    }
    else
    {
        oldPts_pos = new float[npoints];
        oldPts_r   = new float[npoints];
        oldPts_g   = new float[npoints];
        oldPts_b   = new float[npoints];
        oldPts_a   = new float[npoints];
    }

    for(i = 0; i < npoints; ++i)
    {
        const ColorControlPoint &cpt = this->operator[](i);
        oldPts_pos[i] = cpt.GetPosition();
        oldPts_r[i] = float(cpt.GetColors()[0]) / 255.;
        oldPts_g[i] = float(cpt.GetColors()[1]) / 255.;
        oldPts_b[i] = float(cpt.GetColors()[2]) / 255.;
        oldPts_a[i] = float(cpt.GetColors()[3]) / 255.;
    }

    /*******************************************
     * Phase III -- Process the control points.
     ******************************************/
    if(equal || !smooth || discrete)
    {
        ++npoints;
        newPts_pos = new float[npoints];
        newPts_r = new float[npoints];
        newPts_g = new float[npoints];
        newPts_b = new float[npoints];
        newPts_a = new float[npoints];

        if(equal || discrete)
        {
            // Do the equal spacing case.
            for(i = 0; i < npoints; ++i)
            {
                ci = (i < (npoints - 2)) ? i : (npoints - 2);
                newPts_pos[i] = (float)i / (float)(npoints - 1);

                if(!smooth)
                {
                    newPts_r[i] = oldPts_r[ci];
                    newPts_g[i] = oldPts_g[ci];
                    newPts_b[i] = oldPts_b[ci];
                    newPts_a[i] = oldPts_a[ci];
                }
                else
                {
                    if(i < 1 || i >= npoints - 1)
                    {
                        newPts_r[i] = oldPts_r[ci];
                        newPts_g[i] = oldPts_g[ci];
                        newPts_b[i] = oldPts_b[ci];
                        newPts_a[i] = oldPts_a[ci];
                    }
                    else
                    {
                        newPts_r[i] = (oldPts_r[i] + oldPts_r[i-1])*0.5;
                        newPts_g[i] = (oldPts_g[i] + oldPts_g[i-1])*0.5;
                        newPts_b[i] = (oldPts_b[i] + oldPts_b[i-1])*0.5;
                        newPts_a[i] = (oldPts_a[i] + oldPts_a[i-1])*0.5;
                    }
                }
            } // end for
        } // end if equal spacing
        else
        {
            // Do non-equal, non-smooth case.
            newPts_pos[0] = oldPts_pos[0];
            newPts_r[0] = oldPts_r[0];
            newPts_g[0] = oldPts_g[0];
            newPts_b[0] = oldPts_b[0];
            newPts_a[0] = oldPts_a[0];
            for(i = 1; i < npoints - 1; i++)
            {
                newPts_pos[i] = oldPts_pos[i-1] +
                    ((oldPts_pos[i] - oldPts_pos[i-1]) * 0.5);
                newPts_r[i] = oldPts_r[i];
                newPts_g[i] = oldPts_g[i];
                newPts_b[i] = oldPts_b[i];
                newPts_a[i] = oldPts_a[i];
            }
            newPts_pos[npoints-1] = oldPts_pos[npoints-2];
            newPts_r[npoints-1] = oldPts_r[npoints-2];
            newPts_g[npoints-1] = oldPts_g[npoints-2];
            newPts_b[npoints-1] = oldPts_b[npoints-2];
            newPts_a[npoints-1] = oldPts_a[npoints-2];
        }
        c1_pos = newPts_pos;
        c1_r = newPts_r;
        c1_g = newPts_g;
        c1_b = newPts_b;
        c1_a = newPts_a;
    }
    else
    {
        c1_pos = oldPts_pos;
        c1_r = oldPts_r;
        c1_g = oldPts_g;
        c1_b = oldPts_b;
        c1_a = oldPts_a;
    }

    /********************************************
     * Phase IV -- Determine if we need to post sample
     ********************************************/
    const int sampleMultiple = 5;
    bool postSample = (ncolors < (npoints * sampleMultiple));
    int oldNColors = ncolors;
    unsigned char *dest = rgb;
    unsigned char *dest_a = alpha;
    if(postSample)
    {
        ncolors = npoints * sampleMultiple;
        dest = new unsigned char[3 * ncolors];
        if (dest_a)
            dest_a = new unsigned char[ncolors];
    }

    /********************************************
     * Phase V -- Figure the colors for a row.
     ********************************************/
    c2_pos = c1_pos;
    c2_r = c1_r;
    c2_g = c1_g;
    c2_b = c1_b;
    c2_a = c1_a;

    for(ci = 0; ci < npoints - 1; ci++)
    {
        float delta_r, delta_g, delta_b, delta_a;
        float r_sum, g_sum, b_sum, a_sum;
        int   color_start_i, color_end_i, color_range;

        // Initialize some variables.
        c2_pos++;
        c2_r++;
        c2_g++;
        c2_b++;
        c2_a++;
        color_start_i = int(c1_pos[0] * float(ncolors - 1));
        color_end_i = int(c2_pos[0] * float(ncolors - 1));
        color_range = color_end_i - color_start_i;

        if(color_range > 1)
        {
            if(ci == 0 && color_start_i != 0)
            {
                for(i = 0; i < color_start_i; i++)
                {
                    dest[3*i+0] = (unsigned char)(c1_r[0] * 255);
                    dest[3*i+1] = (unsigned char)(c1_g[0] * 255);
                    dest[3*i+2] = (unsigned char)(c1_b[0] * 255);
                    if (dest_a)
                        dest_a[i] = (unsigned char)(c1_a[0] * 255);
                }
            }

            // Figure out some deltas.
            if(smooth)
            {
                delta_r = (float)(c2_r[0] - c1_r[0])/(float)(color_range-1);
                delta_g = (float)(c2_g[0] - c1_g[0])/(float)(color_range-1);
                delta_b = (float)(c2_b[0] - c1_b[0])/(float)(color_range-1);
                delta_a = (float)(c2_a[0] - c1_a[0])/(float)(color_range-1);
            }
            else
                delta_r = delta_g = delta_b = delta_a = 0.;

            // Initialize sums.
            r_sum = c1_r[0];
            g_sum = c1_g[0];
            b_sum = c1_b[0];
            a_sum = c1_a[0];

            // Interpolate color1 to color2.
            for(i = color_start_i; i < color_end_i; i++)
            {
                // Store the colors as 24 bit rgb.
                dest[3*i+0] = (unsigned char)(r_sum * 255);
                dest[3*i+1] = (unsigned char)(g_sum * 255);
                dest[3*i+2] = (unsigned char)(b_sum * 255);
                if (dest_a)
                    dest_a[i] = (unsigned char)(a_sum * 255);

                // Add the color deltas.
                r_sum += delta_r;
                g_sum += delta_g;
                b_sum += delta_b;
                a_sum += delta_a;
            }

            if(ci == npoints - 2 && color_end_i != ncolors)
            {
                for(i = color_end_i; i < ncolors; i++)
                {
                    dest[3*i+0] = (unsigned char)(c2_r[0] * 255);
                    dest[3*i+1] = (unsigned char)(c2_g[0] * 255);
                    dest[3*i+2] = (unsigned char)(c2_b[0] * 255);
                    if (dest_a)
                        dest_a[i] = (unsigned char)(c2_a[0] * 255);
                }
            }
        }
        else
        {
            dest[3*color_start_i+0] = (unsigned char)(c1_r[0] * 255);
            dest[3*color_start_i+1] = (unsigned char)(c1_g[0] * 255);
            dest[3*color_start_i+2] = (unsigned char)(c1_b[0] * 255);
            if (dest_a)
                dest_a[color_start_i] = (unsigned char)(c1_a[0] * 255);
        }

        c1_pos++;
        c1_r++;
        c1_g++;
        c1_b++;
        c1_a++;
    }

    /********************************************
     * Phase VI -- Postsample colors if needed.
     ********************************************/
    if(postSample)
    {
        c = 0;
        int ca = 0;
        for(i = 0; i < oldNColors; ++i)
        {
            float t;
            if(oldNColors > 1)
                t = float(i) / float(oldNColors - 1);
            else
                t = 0.f;
            int index = int(t * (ncolors - 1));
            rgb[c++] = dest[index*3];
            rgb[c++] = dest[index*3+1];
            rgb[c++] = dest[index*3+2];
            if (alpha)
                alpha[ca++] = dest_a[index];
        }

        delete [] dest;
        if (dest_a)
            delete [] dest_a;
    }

    // Free unneeded memory.
    delete [] oldPts_pos;
    delete [] oldPts_r;
    delete [] oldPts_g;
    delete [] oldPts_b;
    delete [] oldPts_a;

    delete [] newPts_pos;
    delete [] newPts_r;
    delete [] newPts_g;
    delete [] newPts_b;
    delete [] newPts_a;
}

// ****************************************************************************
// Method: ColorControlPointList::CompactCreateNode
//
// Purpose:
//   This method creates a compact DataNode representation of the object so
//   it can be saved to a config file.
//
// Note:       Autogenerated by xml2atts.
//
// Programmer: Brad Whitlock
// Creation:   Tue Oct 21 16:13:44 PST 2003
//
// Modifications:
//   Brad Whitlock, Wed Dec 17 12:23:35 PDT 2003
//   I added the completeSave argument.
//
//   Brad Whitlock, Fri Apr 27 11:28:11 PDT 2012
//   Change smoothingFlag to smoothing and make it an enum.
// 
//   Justin Privitera, Thu Jun 16 18:01:49 PDT 2022
//   Added tags and removed categories.
//
// ****************************************************************************

bool
ColorControlPointList::CompactCreateNode(DataNode *parentNode, bool completeSave, bool forceAdd)
{
    if(parentNode == 0)
        return false;

    ColorControlPointList defaultObject;
    bool addToParent = false;
    // Create a node for ColorControlPointList.
    DataNode *node = new DataNode("ColorControlPointList");

    //
    // Save out the color control points as vectors of uchar and float.
    //
    int npts = GetNumControlPoints();
    if(npts > 0)
    {
        unsignedCharVector compactColors;
        floatVector        compactPositions;
        compactColors.reserve(npts << 2);
        compactPositions.reserve(npts);
        for(int i = 0; i < npts; ++i)
        {
            const ColorControlPoint &cpt = GetControlPoints(i);
            const unsigned char *c = cpt.GetColors();
            compactColors.push_back(c[0]);
            compactColors.push_back(c[1]);
            compactColors.push_back(c[2]);
            compactColors.push_back(c[3]);
            compactPositions.push_back(cpt.GetPosition());
        }

        node->AddNode(new DataNode("compactColors", compactColors));
        node->AddNode(new DataNode("compactPositions", compactPositions));
        addToParent = true;
    }

    if(completeSave || !FieldsEqual(ID_smoothing, &defaultObject))
    {
        addToParent = true;
        node->AddNode(new DataNode("smoothing", SmoothingMethod_ToString(smoothing)));
    }

    if(completeSave || !FieldsEqual(ID_equalSpacingFlag, &defaultObject))
    {
        addToParent = true;
        node->AddNode(new DataNode("equal", equalSpacingFlag));
    }

    if(completeSave || !FieldsEqual(ID_discreteFlag, &defaultObject))
    {
        addToParent = true;
        node->AddNode(new DataNode("discrete", discreteFlag));
    }

    if(completeSave || !FieldsEqual(ID_tagNames, &defaultObject))
    {
        addToParent = true;
        node->AddNode(new DataNode("tags", tagNames));
    }

    // Add the node to the parent node.
    if(addToParent || forceAdd)
        parentNode->AddNode(node);
    else
        delete node;

    return (addToParent || forceAdd);
}

// ****************************************************************************
// Method: ColorControlPointList::AddTag
//
// Purpose:
//   Add the provided tag to the list of local tags.
//
// Programmer: Justin Privitera
// Creation:   Fri Jun  3 11:27:43 PDT 2022
// 
// Note: We assume the following: the tag is not an empty string AND the tag 
//   contains only alphanumeric characters and characters in " -=<>". In other
//   words, we assume that the result of `ValidateTag` is true.
//
// Modifications:
//    Justin Privitera, Wed Jun 29 17:50:24 PDT 2022
//    Set tagChangesMade to true every time a tag is added.
// 
//    Justin Privitera, Thu Aug 25 15:04:55 PDT 2022
//    Now takes a const string and uses `emplace_back`.
// 
//    Justin Privitera, Tue Sep  5 12:49:42 PDT 2023
//    Use select instead of a bool.
//
// ****************************************************************************

void
ColorControlPointList::AddTag(const std::string newtag)
{
    // If the tag is already in the tag list then we will do nothing.
    if (!HasTag(newtag))
    {
        tagNames.emplace_back(newtag);
        Select(ID_tagNames, (void *)&tagNames);
    }
}

// ****************************************************************************
// Method: ColorControlPointList::RemoveTag
//
// Purpose:
//   Remove the tag from the list of local tags.
//
// Programmer: Justin Privitera
// Creation:   Wed Aug 10 15:35:58 PDT 2022
//
// Modifications:
//    Justin Privitera, Thu Aug 25 15:04:55 PDT 2022
//    Made the "tag" arg const.
// 
//    Justin Privitera, Tue Sep  5 12:49:42 PDT 2023
//    Use select instead of a bool.
//
// ****************************************************************************

void
ColorControlPointList::RemoveTag(const std::string tag)
{
    // If the tag is not in the tag list then we will do nothing.
    int index = GetTagIndex(tag);
    if (index != -1)
    {
        stringVector::iterator pos = tagNames.begin();
        for (int i = 0; i < index; i ++) 
            pos ++;
        if(pos != tagNames.end()) 
            tagNames.erase(pos);
        Select(ID_tagNames, (void *)&tagNames);
    }
}

// ****************************************************************************
// Method: ColorControlPointList::ClearTags
//
// Purpose:
//   Removes all tags from the ccpl.
//
// Programmer: Justin Privitera
// Creation:   Fri Jun  3 11:27:43 PDT 2022
//
// Modifications:
//    Justin Privitera, Wed Jun 29 17:50:24 PDT 2022
//    Set tagChangesMade to true each time the tags are cleared.
// 
//    Justin Privitera, Tue Sep  5 12:49:42 PDT 2023
//    Use select instead of a bool.
//
// ****************************************************************************

void
ColorControlPointList::ClearTags()
{
    tagNames.clear();
    Select(ID_tagNames, (void *)&tagNames);
}

// ****************************************************************************
// Method: ColorControlPointList::GetTag
//
// Purpose:
//   Gets the name of the tag at the specified index.
//
// Programmer: Justin Privitera
// Creation:   Fri Jun  3 11:27:43 PDT 2022
//
// Modifications:
//    Justin Privitera, Thu Aug 25 15:04:55 PDT 2022
//    Made it a const function that takes a const int.

//
// ****************************************************************************

std::string
ColorControlPointList::GetTag(const int index) const
{
    if (index >= 0 && index < tagNames.size())
        return tagNames[index];
    return "";
}

// ****************************************************************************
// Method: ColorControlPointList::GetNumTags
//
// Purpose:
//   Gets the number of tags associated with this ccpl.
//
// Programmer: Justin Privitera
// Creation:   Fri Jun  3 11:27:43 PDT 2022
//
// Modifications:
//    Justin Privitera, Thu Aug 25 15:04:55 PDT 2022
//    Made it a const function.
//
// ****************************************************************************

int
ColorControlPointList::GetNumTags() const
{
    return tagNames.size();
}

// ****************************************************************************
// Method: ColorControlPointList::GetTagsAsString
//
// Purpose:
//   Formats the tags as a string for use in the tag line edit box in the gui.
//
// Programmer: Justin Privitera
// Creation:   Fri Jun  3 11:27:43 PDT 2022
//
// Modifications:
//    Justin Privitera, Thu Aug 25 15:04:55 PDT 2022
//    Made it a const function.
// 
//    Justin Privitera, Wed Sep 21 16:51:24 PDT 2022
//    Added whitespace between the tag names in the tag bar.
//
// ****************************************************************************

std::string
ColorControlPointList::GetTagsAsString() const
{
    int numtags = tagNames.size();
    if (numtags == 0)
        return "";
    if (numtags == 1)
        return tagNames[0];
    std::string tags = "";
    for (int i = 0; i < tagNames.size() - 1; i ++)
    {
        tags += tagNames[i] + ", ";
    }
    tags += tagNames[tagNames.size() - 1];
    return tags;
}

// ****************************************************************************
// Method: ColorControlPointList::HasTag
//
// Purpose:
//   Determines if the ccpl has the given tag associated with it.
//
// Programmer: Justin Privitera
// Creation:   Wed Jun  8 11:46:21 PDT 2022
//
// Modifications:
//    Justin Privitera, Thu Aug 25 15:04:55 PDT 2022
//    Made it a const function that takes a const arg.
// 
//    Justin Privitera, Wed Aug 30 15:30:58 PDT 2023
//    Rewritten to use range-based for loop.
//
// ****************************************************************************

bool
ColorControlPointList::HasTag(const std::string tag) const
{
    for (const auto & currtag : tagNames)
        if (currtag == tag)
            return true;
    return false;
}

// ****************************************************************************
// Method: ColorControlPointList::GetTagIndex
//
// Purpose:
//   Gets the index in the tag names vector of a given tag.
//
// Programmer: Justin Privitera
// Creation:   Wed Aug 10 15:35:58 PDT 2022
//
// Modifications:
//
// ****************************************************************************

int
ColorControlPointList::GetTagIndex(const std::string tag) const
{
    for (int i = 0; i < tagNames.size(); i ++)
        if (tagNames[i] == tag)
            return i;
    return -1;
}

// ****************************************************************************
// Method: ColorControlPointList::ValidateTag
//
// Purpose: Make sure a tag conforms to rules. Returns a pair, where the first 
//      element is a flag to say if the tag is good or not, and the second
//      element is a string that contains an error message for use as needed.
//
// Programmer: Justin Privitera
// Creation:   Thu Aug 25 10:05:45 PDT 2022
//
// Modifications:
//    Justin Privitera, Thu Sep 29 17:27:37 PDT 2022
//    Replace auto with bool.
//
// ****************************************************************************
std::pair<bool, std::string>
ColorControlPointList::ValidateTag(const std::string currtag) const
{
    bool success{false};
    std::string outstr;
    if (currtag == "")
    {
        outstr = "A tag for a color table "
            "is an empty string. This tag will be discarded.\n";
    }
    // Check that the tag is alphanumeric or contains one of the allowed
    // special characters.
    else if (! std::all_of(currtag.begin(), currtag.end(), 
             [](char const &c){return std::isalnum(c) || strchr(" -=<>", c);}))
    {
        outstr = "The tag name \""
            + currtag + "\" is not valid. Tag names must contain "
            "only alphanumeric characters accompanied by the "
            "following 5 characters: \" \", \"-\", \"=\", \"<\", "
            "and \">\". This tag will be discarded.\n";
    }
    else
    {
        success = true;
        outstr = "";
    }
    return std::make_pair(success, outstr);
}

// ****************************************************************************
// Method: ColorControlPointList::SetNumControlPoints
//
// Purpose:
//   Resize the controlpoints vector, adding or removing points as needed.
//
// Note:       Generated by xml2atts from code file.
//
// Programmer: Kathleen Biagas
// Creation:   May 18, 2022
// 
// Modifications:
//
// ****************************************************************************

void ColorControlPointList::SetNumControlPoints(const int n)
{
    int currentSize = GetNumControlPoints();

    if (n == currentSize)
    {
        return;
    }
    else if (n == 0)
    {
        // ClearControlPoints calls Select so there is no need to progress to
        // the bottom of this method to repeat the Select.
        ClearControlPoints();
        return;
    }
    else if (n < currentSize)
    {
        AttributeGroupVector::iterator pos = controlPoints.end();
        pos--;
        for (int i = 0; i < (currentSize-n); ++i, --pos)
        {
            delete *pos;
            controlPoints.erase(pos);
        }
    }
    else
    {
        const unsigned char colors[8][4] = {
            {255, 0, 0, 255},
            {255, 255, 0, 255},
            {0, 255, 0, 255},
            {0, 255, 255, 255},
            {0, 0, 255, 255},
            {255, 0, 255, 255},
            {0, 0, 0, 255},
            {255, 255, 255, 255}};

        // re-align current points so new points added below can have reasonable positions
        float delta = 1.f/float(n-1);
        float pos = 0.f;
        for (int i = 0; i < currentSize; ++i, pos += delta)
        {
            ((ColorControlPoint*)(controlPoints[i]))->SetPosition(pos);
        }
        // set the new size and add new points
        controlPoints.resize(n);
        for(int i = currentSize; i < n; ++i, pos+=delta)
        {
            ColorControlPoint *cpt = new ColorControlPoint();
            cpt->SetPosition(pos);
            cpt->SetColors(colors[i%8]);
            controlPoints[i] = cpt;
        }
    }

    Select(ID_controlPoints, (void*)&controlPoints);
}

// ****************************************************************************
// Method: ColorControlPointList::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:   June 27 2022
//
// Modifications:
//
// ****************************************************************************
#include <visit-config.h>
#ifdef VIEWER
#include <avtCallback.h>
#endif

void
ColorControlPointList::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("ColorControlPointList");
    if(searchNode == 0)
        return;

    if (VersionLessThan(configVersion, "3.3.0"))
    {
        DataNode *k = 0;
        if ((k = searchNode->GetNode("categoryName")) != 0)
        {
#ifdef VIEWER
            avtCallback::IssueWarning(DeprecationMessage("categoryName", "3.5.0"));
#endif
            searchNode->RemoveNode(k);
        }
    }
#endif
}

