/*
Copyright (c) 2003-2010 Sony Pictures Imageworks Inc., et al.
All Rights Reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
  notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
  notice, this list of conditions and the following disclaimer in the
  documentation and/or other materials provided with the distribution.
* Neither the name of Sony Pictures Imageworks nor the names of its
  contributors may be used to endorse or promote products derived from
  this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/


#include <cstdlib>
#include <cstring>
#include <set>
#include <sstream>
#include <fstream>
#include <utility>
#include <vector>

#include <OpenColorIO/OpenColorIO.h>

#include "HashUtils.h"
#include "Logging.h"
#include "LookParse.h"
#include "Display.h"
#include "MathUtils.h"
#include "Mutex.h"
#include "OpBuilders.h"
#include "PathUtils.h"
#include "ParseUtils.h"
#include "Processor.h"
#include "PrivateTypes.h"
#include "pystring/pystring.h"
#include "OCIOYaml.h"
#include "Platform.h"

OCIO_NAMESPACE_ENTER
{
    namespace
    {
        const char * OCIO_CONFIG_ENVVAR = "OCIO";
        const char * OCIO_ACTIVE_DISPLAYS_ENVVAR = "OCIO_ACTIVE_DISPLAYS";
        const char * OCIO_ACTIVE_VIEWS_ENVVAR = "OCIO_ACTIVE_VIEWS";
        
        enum Sanity
        {
            SANITY_UNKNOWN = 0,
            SANITY_SANE,
            SANITY_INSANE
        };
        
        // These are the 709 primaries specified by the ASC.
        const float DEFAULT_LUMA_COEFF_R = 0.2126f;
        const float DEFAULT_LUMA_COEFF_G = 0.7152f;
        const float DEFAULT_LUMA_COEFF_B = 0.0722f;
        
        const char * INTERNAL_RAW_PROFILE = 
        "ocio_profile_version: 1\n"
        "strictparsing: false\n"
        "roles:\n"
        "  default: raw\n"
        "displays:\n"
        "  sRGB:\n"
        "  - !<View> {name: Raw, colorspace: raw}\n"
        "colorspaces:\n"
        "  - !<ColorSpace>\n"
        "      name: raw\n"
        "      family: raw\n"
        "      equalitygroup:\n"
        "      bitdepth: 32f\n"
        "      isdata: true\n"
        "      allocation: uniform\n"
        "      description: 'A raw color space. Conversions to and from this space are no-ops.'\n";
    }
    
    
    ///////////////////////////////////////////////////////////////////////////
    
    const char * GetVersion()
    {
        return OCIO_VERSION;
    }
    
    int GetVersionHex()
    {
        return OCIO_VERSION_HEX;
    }
    
    namespace
    {
        ConstConfigRcPtr g_currentConfig;
        Mutex g_currentConfigLock;
    }
    
    ConstConfigRcPtr GetCurrentConfig()
    {
        AutoMutex lock(g_currentConfigLock);
        
        if(!g_currentConfig)
        {
            g_currentConfig = Config::CreateFromEnv();
        }
        
        return g_currentConfig;
    }
    
    void SetCurrentConfig(const ConstConfigRcPtr & config)
    {
        AutoMutex lock(g_currentConfigLock);
        
        g_currentConfig = config->createEditableCopy();
    }
    
    namespace
    {
    
    // Environment
    const char* LookupEnvironment(const StringMap & env,
                                        const std::string & name)
    {
        StringMap::const_iterator iter = env.find(name);
        if(iter == env.end()) return "";
        return iter->second.c_str();
    }
    
    // Roles
    // (lower case role name: colorspace name)
    const char* LookupRole(const StringMap & roles, const std::string & rolename)
    {
        StringMap::const_iterator iter = roles.find(pystring::lower(rolename));
        if(iter == roles.end()) return "";
        return iter->second.c_str();
    }
    
    
    void GetFileReferences(std::set<std::string> & files,
                           const ConstTransformRcPtr & transform)
    {
        if(!transform) return;
        
        if(ConstGroupTransformRcPtr groupTransform = \
            DynamicPtrCast<const GroupTransform>(transform))
        {
            for(int i=0; i<groupTransform->size(); ++i)
            {
                GetFileReferences(files, groupTransform->getTransform(i));
            }
        }
        else if(ConstFileTransformRcPtr fileTransform = \
            DynamicPtrCast<const FileTransform>(transform))
        {
            files.insert(fileTransform->getSrc());
        }
    }
    
    void GetColorSpaceReferences(std::set<std::string> & colorSpaceNames,
                                 const ConstTransformRcPtr & transform)
    {
        if(!transform) return;
        
        if(ConstGroupTransformRcPtr groupTransform = \
            DynamicPtrCast<const GroupTransform>(transform))
        {
            for(int i=0; i<groupTransform->size(); ++i)
            {
                GetColorSpaceReferences(colorSpaceNames, groupTransform->getTransform(i));
            }
        }
        else if(ConstColorSpaceTransformRcPtr colorSpaceTransform = \
            DynamicPtrCast<const ColorSpaceTransform>(transform))
        {
            colorSpaceNames.insert(colorSpaceTransform->getSrc());
            colorSpaceNames.insert(colorSpaceTransform->getDst());
        }
        else if(ConstDisplayTransformRcPtr displayTransform = \
            DynamicPtrCast<const DisplayTransform>(transform))
        {
            colorSpaceNames.insert(displayTransform->getInputColorSpaceName());
        }
        else if(ConstLookTransformRcPtr lookTransform = \
            DynamicPtrCast<const LookTransform>(transform))
        {
            colorSpaceNames.insert(colorSpaceTransform->getSrc());
            colorSpaceNames.insert(colorSpaceTransform->getDst());
        }
    }
    
    
    bool FindColorSpaceIndex(int * index,
                             const ColorSpaceVec & colorspaces,
                             const std::string & csname)
    {
        if(csname.empty()) return false;
        
        std::string csnamelower = pystring::lower(csname);
        for(unsigned int i = 0; i < colorspaces.size(); ++i)
        {
            if(csnamelower == pystring::lower(colorspaces[i]->getName()))
            {
                if(index) *index = i;
                return true;
            }
        }
        
        return false;
    }
        
    } // namespace
    
    class Config::Impl
    {
    public:
        StringMap env_;
        ContextRcPtr context_;
        std::string description_;
        ColorSpaceVec colorspaces_;
        StringMap roles_;
        LookVec looksList_;
        
        DisplayMap displays_;
        StringVec activeDisplays_;
        StringVec activeDisplaysEnvOverride_;
        StringVec activeViews_;
        StringVec activeViewsEnvOverride_;
        
        mutable std::string activeDisplaysStr_;
        mutable std::string activeViewsStr_;
        mutable StringVec displayCache_;
        
        // Misc
        std::vector<float> defaultLumaCoefs_;
        bool strictParsing_;
        
        mutable Sanity sanity_;
        mutable std::string sanitytext_;
        
        mutable Mutex cacheidMutex_;
        mutable StringMap cacheids_;
        mutable std::string cacheidnocontext_;
        
        OCIOYaml io_;
        
        Impl() : 
            context_(Context::Create()),
            strictParsing_(true),
            sanity_(SANITY_UNKNOWN)
        {
            std::string activeDisplays;
            Platform::getenv(OCIO_ACTIVE_DISPLAYS_ENVVAR, activeDisplays);
            activeDisplays = pystring::strip(activeDisplays);
            if (!activeDisplays.empty()) {
                SplitStringEnvStyle(activeDisplaysEnvOverride_, activeDisplays.c_str());
            }
            
            std::string activeViews;
            Platform::getenv(OCIO_ACTIVE_VIEWS_ENVVAR, activeViews);
            activeViews = pystring::strip(activeViews);
            if (!activeViews.empty()) {
                SplitStringEnvStyle(activeViewsEnvOverride_, activeViews.c_str());
            }
            
            defaultLumaCoefs_.resize(3);
            defaultLumaCoefs_[0] = DEFAULT_LUMA_COEFF_R;
            defaultLumaCoefs_[1] = DEFAULT_LUMA_COEFF_G;
            defaultLumaCoefs_[2] = DEFAULT_LUMA_COEFF_B;
        }
        
        ~Impl()
        {
        
        }
        
        Impl& operator= (const Impl & rhs)
        {
            if(this!=&rhs)
            {
                env_ = rhs.env_;
                context_ = rhs.context_->createEditableCopy();
                description_ = rhs.description_;
                
                // Deep copy the colorspaces
                colorspaces_.clear();
                colorspaces_.reserve(rhs.colorspaces_.size());
                for(unsigned int i=0; i<rhs.colorspaces_.size(); ++i)
                {
                    colorspaces_.push_back(rhs.colorspaces_[i]->createEditableCopy());
                }
                
                // Deep copy the looks
                looksList_.clear();
                looksList_.reserve(rhs.looksList_.size());
                for(unsigned int i=0; i<rhs.looksList_.size(); ++i)
                {
                    looksList_.push_back(rhs.looksList_[i]->createEditableCopy());
                }
                
                // Assignment operator will suffice for these
                roles_ = rhs.roles_;
                
                displays_ = rhs.displays_;
                activeDisplays_ = rhs.activeDisplays_;
                activeViews_ = rhs.activeViews_;
                activeViewsEnvOverride_ = rhs.activeViewsEnvOverride_;
                activeDisplaysEnvOverride_ = rhs.activeDisplaysEnvOverride_;
                activeDisplaysStr_ = rhs.activeDisplaysStr_;
                displayCache_ = rhs.displayCache_;
                
                defaultLumaCoefs_ = rhs.defaultLumaCoefs_;
                strictParsing_ = rhs.strictParsing_;
                
                sanity_ = rhs.sanity_;
                sanitytext_ = rhs.sanitytext_;
                
                cacheids_ = rhs.cacheids_;
                cacheidnocontext_ = cacheidnocontext_;
            }
            return *this;
        }

        // Any time you modify the state of the config, you must call this
        // to reset internal cache states.  You also should do this in a
        // thread safe manner by acquiring the cacheidMutex_;
        void resetCacheIDs();
        
        // Get all internal transforms (to generate cacheIDs, validation, etc).
        // This currently crawls colorspaces + looks
        void getAllIntenalTransforms(ConstTransformVec & transformVec) const;
    };
    
    
    ///////////////////////////////////////////////////////////////////////////
    
    ConfigRcPtr Config::Create()
    {
        return ConfigRcPtr(new Config(), &deleter);
    }
    
    void Config::deleter(Config* c)
    {
        delete c;
    }
    
    ConstConfigRcPtr Config::CreateFromEnv()
    {
        std::string file;
        Platform::getenv(OCIO_CONFIG_ENVVAR, file);
        if(!file.empty()) return CreateFromFile(file.c_str());
        
        std::ostringstream os;
        os << "Color management disabled. ";
        os << "(Specify the $OCIO environment variable to enable.)";
        LogInfo(os.str());
        
        std::istringstream istream;
        istream.str(INTERNAL_RAW_PROFILE);
        
        ConfigRcPtr config = Config::Create();
        config->getImpl()->io_.open(istream, config);
        return config;
    }
    
    ConstConfigRcPtr Config::CreateFromFile(const char * filename)
    {
        std::ifstream istream(filename);
        if(istream.fail()) {
            std::ostringstream os;
            os << "Error could not read '" << filename;
            os << "' OCIO profile.";
            throw Exception (os.str().c_str());
        }
        
        ConfigRcPtr config = Config::Create();
        config->getImpl()->io_.open(istream, config, filename);
        return config;
    }
    
    ConstConfigRcPtr Config::CreateFromStream(std::istream & istream)
    {
        ConfigRcPtr config = Config::Create();
        config->getImpl()->io_.open(istream, config);
        return config;
    }
    
    ///////////////////////////////////////////////////////////////////////////
    
    
    
    Config::Config()
    : m_impl(new Config::Impl)
    {
    }
    
    Config::~Config()
    {
        delete m_impl;
        m_impl = NULL;
    }
    
    ConfigRcPtr Config::createEditableCopy() const
    {
        ConfigRcPtr config = Config::Create();
        *config->m_impl = *m_impl;
        return config;
    }
    
    void Config::sanityCheck() const
    {
        if(getImpl()->sanity_ == SANITY_SANE) return;
        if(getImpl()->sanity_ == SANITY_INSANE)
        {
            throw Exception(getImpl()->sanitytext_.c_str());
        }
        
        getImpl()->sanity_ = SANITY_INSANE;
        getImpl()->sanitytext_ = "";
        
        
        ///// COLORSPACES
        StringSet existingColorSpaces;
        
        // Confirm all ColorSpaces are valid
        for(unsigned int i=0; i<getImpl()->colorspaces_.size(); ++i)
        {
            if(!getImpl()->colorspaces_[i])
            {
                std::ostringstream os;
                os << "Config failed sanitycheck. ";
                os << "The colorspace at index " << i << " is null.";
                getImpl()->sanitytext_ = os.str();
                throw Exception(getImpl()->sanitytext_.c_str());
            }
            
            const char * name = getImpl()->colorspaces_[i]->getName();
            if(!name || strlen(name) == 0)
            {
                std::ostringstream os;
                os << "Config failed sanitycheck. ";
                os << "The colorspace at index " << i << " is not named.";
                getImpl()->sanitytext_ = os.str();
                throw Exception(getImpl()->sanitytext_.c_str());
            }
            
            std::string namelower = pystring::lower(name);
            StringSet::const_iterator it = existingColorSpaces.find(namelower);
            if(it != existingColorSpaces.end())
            {
                std::ostringstream os;
                os << "Config failed sanitycheck. ";
                os << "Two colorspaces are defined with the same name, '";
                os << namelower << "'.";
                getImpl()->sanitytext_ = os.str();
                throw Exception(getImpl()->sanitytext_.c_str());
            }
            
            existingColorSpaces.insert(namelower);
        }
        
        // Confirm all roles are valid
        {
            for(StringMap::const_iterator iter = getImpl()->roles_.begin(),
                end = getImpl()->roles_.end(); iter!=end; ++iter)
            {
                int csindex = -1;
                if(!FindColorSpaceIndex(&csindex, getImpl()->colorspaces_, iter->second))
                {
                    std::ostringstream os;
                    os << "Config failed sanitycheck. ";
                    os << "The role '" << iter->first << "' ";
                    os << "refers to a colorspace, '" << iter->second << "', ";
                    os << "which is not defined.";
                    getImpl()->sanitytext_ = os.str();
                    throw Exception(getImpl()->sanitytext_.c_str());
                }
                
                // Confirm no name conflicts between colorspaces and roles
                if(FindColorSpaceIndex(&csindex, getImpl()->colorspaces_, iter->first))
                {
                    std::ostringstream os;
                    os << "Config failed sanitycheck. ";
                    os << "The role '" << iter->first << "' ";
                    os << " is in conflict with a colorspace of the same name.";
                    getImpl()->sanitytext_ = os.str();
                    throw Exception(getImpl()->sanitytext_.c_str());
                }
            }
        }
        
        ///// DISPLAYS
        
        int numviews = 0;
        
        // Confirm all Displays transforms refer to colorspaces that exit
        for(DisplayMap::const_iterator iter = getImpl()->displays_.begin();
            iter != getImpl()->displays_.end();
            ++iter)
        {
            std::string display = iter->first;
            const ViewVec & views = iter->second;
            if(views.empty())
            {
                std::ostringstream os;
                os << "Config failed sanitycheck. ";
                os << "The display '" << display << "' ";
                os << "does not define any views.";
                getImpl()->sanitytext_ = os.str();
                throw Exception(getImpl()->sanitytext_.c_str());
            }
            
            for(unsigned int i=0; i<views.size(); ++i)
            {
                if(views[i].name.empty() || views[i].colorspace.empty())
                {
                    std::ostringstream os;
                    os << "Config failed sanitycheck. ";
                    os << "The display '" << display << "' ";
                    os << "defines a view with an empty name and/or colorspace.";
                    getImpl()->sanitytext_ = os.str();
                    throw Exception(getImpl()->sanitytext_.c_str());
                }
                
                int csindex = -1;
                if(!FindColorSpaceIndex(&csindex, getImpl()->colorspaces_, views[i].colorspace))
                {
                    std::ostringstream os;
                    os << "Config failed sanitycheck. ";
                    os << "The display '" << display << "' ";
                    os << "refers to a colorspace, '" << views[i].colorspace << "', ";
                    os << "which is not defined.";
                    getImpl()->sanitytext_ = os.str();
                    throw Exception(getImpl()->sanitytext_.c_str());
                }
                
                // Confirm looks references exist
                LookParseResult looks;
                const LookParseResult::Options & options = looks.parse(views[i].looks);
                
                for(unsigned int optionindex=0;
                    optionindex<options.size();
                    ++optionindex)
                {
                    for(unsigned int tokenindex=0;
                        tokenindex<options[optionindex].size();
                        ++tokenindex)
                    {
                        std::string look = options[optionindex][tokenindex].name;
                        
                        if(!look.empty() && !getLook(look.c_str()))
                        {
                            std::ostringstream os;
                            os << "Config failed sanitycheck. ";
                            os << "The display '" << display << "' ";
                            os << "refers to a look, '" << look << "', ";
                            os << "which is not defined.";
                            getImpl()->sanitytext_ = os.str();
                            throw Exception(getImpl()->sanitytext_.c_str());
                        }
                    }
                }
                
                ++numviews;
            }
        }
        
        // Confirm at least one display entry exists.
        if(numviews == 0)
        {
            std::ostringstream os;
            os << "Config failed sanitycheck. ";
            os << "No displays are specified.";
            getImpl()->sanitytext_ = os.str();
            throw Exception(getImpl()->sanitytext_.c_str());
        }
        
        // Confirm for all Transforms that reference internal colorspaces,
        // the named space exists
        {
            ConstTransformVec allTransforms;
            getImpl()->getAllIntenalTransforms(allTransforms);
            
            std::set<std::string> colorSpaceNames;
            for(unsigned int i=0; i<colorSpaceNames.size(); ++i)
            {
                GetColorSpaceReferences(colorSpaceNames, allTransforms[i]);
            }
            
            for(std::set<std::string>::iterator iter = colorSpaceNames.begin();
                iter != colorSpaceNames.end(); ++iter)
            {
                int csindex = -1;
                if(!FindColorSpaceIndex(&csindex, getImpl()->colorspaces_, *iter))
                {
                    std::ostringstream os;
                    os << "Config failed sanitycheck. ";
                    os << "This config references a ColorSpace, '" << *iter << "', ";
                    os << "which is not defined.";
                    getImpl()->sanitytext_ = os.str();
                    throw Exception(getImpl()->sanitytext_.c_str());
                }
            }
        }
        
        ///// LOOKS
        
        // For all looks, confirm the process space exists and the look is named
        for(unsigned int i=0; i<getImpl()->looksList_.size(); ++i)
        {
            std::string name = getImpl()->looksList_[i]->getName();
            if(name.empty())
            {
                std::ostringstream os;
                os << "Config failed sanitycheck. ";
                os << "The look at index '" << i << "' ";
                os << "does not specify a name.";
                getImpl()->sanitytext_ = os.str();
                throw Exception(getImpl()->sanitytext_.c_str());
            }
            
            std::string processSpace = getImpl()->looksList_[i]->getProcessSpace();
            if(processSpace.empty())
            {
                std::ostringstream os;
                os << "Config failed sanitycheck. ";
                os << "The look '" << name << "' ";
                os << "does not specify a process space.";
                getImpl()->sanitytext_ = os.str();
                throw Exception(getImpl()->sanitytext_.c_str());
            }
            
            int csindex=0;
            if(!FindColorSpaceIndex(&csindex, getImpl()->colorspaces_, processSpace))
            {
                std::ostringstream os;
                os << "Config failed sanitycheck. ";
                os << "The look '" << name << "' ";
                os << "specifies a process color space, '";
                os << processSpace << "', which is not defined.";
                getImpl()->sanitytext_ = os.str();
                throw Exception(getImpl()->sanitytext_.c_str());
            }
        }
        
        
        
        // Everything is groovy.
        getImpl()->sanity_ = SANITY_SANE;
    }
    
    ///////////////////////////////////////////////////////////////////////////
    
    const char * Config::getDescription() const
    {
        return getImpl()->description_.c_str();
    }
    
    void Config::setDescription(const char * description)
    {
        getImpl()->description_ = description;
        
        AutoMutex lock(getImpl()->cacheidMutex_);
        getImpl()->resetCacheIDs();
    }
    
    
    // RESOURCES //////////////////////////////////////////////////////////////
    
    ConstContextRcPtr Config::getCurrentContext() const
    {
        return getImpl()->context_;
    }
    
    void Config::addEnvironmentVar(const char * name, const char * defaultValue)
    {
        if(defaultValue)
        {
            getImpl()->env_[std::string(name)] = std::string(defaultValue);
            getImpl()->context_->setStringVar(name, defaultValue);
        }
        else
        {
            StringMap::iterator iter = getImpl()->env_.find(std::string(name));
            if(iter != getImpl()->env_.end()) getImpl()->env_.erase(iter);
        }
        
        AutoMutex lock(getImpl()->cacheidMutex_);
        getImpl()->resetCacheIDs();
    }
    
    int Config::getNumEnvironmentVars() const
    {
        return static_cast<int>(getImpl()->env_.size());
    }
    
    const char * Config::getEnvironmentVarNameByIndex(int index) const
    {
        if(index < 0 || index >= (int)getImpl()->env_.size()) return "";
        StringMap::const_iterator iter = getImpl()->env_.begin();
        for(int i = 0; i < index; ++i) ++iter;
        return iter->first.c_str();
    }
    
    const char * Config::getEnvironmentVarDefault(const char * name) const
    {
        return LookupEnvironment(getImpl()->env_, name);
    }
    
    void Config::clearEnvironmentVars()
    {
        getImpl()->env_.clear();
        getImpl()->context_->clearStringVars();
        
        AutoMutex lock(getImpl()->cacheidMutex_);
        getImpl()->resetCacheIDs();
    }
    
    void Config::setEnvironmentMode(EnvironmentMode mode)
    {
        getImpl()->context_->setEnvironmentMode(mode);
        
        AutoMutex lock(getImpl()->cacheidMutex_);
        getImpl()->resetCacheIDs();
    }
    
    EnvironmentMode Config::getEnvironmentMode() const
    {
        return getImpl()->context_->getEnvironmentMode();
    }
    
    void Config::loadEnvironment()
    {
        getImpl()->context_->loadEnvironment();
        
        AutoMutex lock(getImpl()->cacheidMutex_);
        getImpl()->resetCacheIDs();
    }
    
    const char * Config::getSearchPath() const
    {
        return getImpl()->context_->getSearchPath();
    }
    
    void Config::setSearchPath(const char * path)
    {
        getImpl()->context_->setSearchPath(path);
        
        AutoMutex lock(getImpl()->cacheidMutex_);
        getImpl()->resetCacheIDs();
    }
    
    const char * Config::getWorkingDir() const
    {
        return getImpl()->context_->getWorkingDir();
    }
    
    void Config::setWorkingDir(const char * dirname)
    {
        getImpl()->context_->setWorkingDir(dirname);
        
        AutoMutex lock(getImpl()->cacheidMutex_);
        getImpl()->resetCacheIDs();
    }
    
    
    ///////////////////////////////////////////////////////////////////////////
    
    int Config::getNumColorSpaces() const
    {
        return static_cast<int>(getImpl()->colorspaces_.size());
    }
    
    const char * Config::getColorSpaceNameByIndex(int index) const
    {
        if(index<0 || index >= (int)getImpl()->colorspaces_.size())
        {
            return "";
        }
        
        return getImpl()->colorspaces_[index]->getName();
    }
    
    ConstColorSpaceRcPtr Config::getColorSpace(const char * name) const
    {
        int index = getIndexForColorSpace(name);
        if(index<0 || index >= (int)getImpl()->colorspaces_.size())
        {
            return ColorSpaceRcPtr();
        }
        
        return getImpl()->colorspaces_[index];
    }
    
    int Config::getIndexForColorSpace(const char * name) const
    {
        int csindex = -1;

        // Check to see if the name is a color space
        if( FindColorSpaceIndex(&csindex, getImpl()->colorspaces_, name) )
        {
            return csindex;
        }
        
        // Check to see if the name is a role
        const char* csname = LookupRole(getImpl()->roles_, name);
        if( FindColorSpaceIndex(&csindex, getImpl()->colorspaces_, csname) )
        {
            return csindex;
        }
        
        // Is a default role defined?
        // (And, are we allowed to use it)
        if(!getImpl()->strictParsing_)
        {
            csname = LookupRole(getImpl()->roles_, ROLE_DEFAULT);
            if( FindColorSpaceIndex(&csindex, getImpl()->colorspaces_, csname) )
            {
                return csindex;
            }
        }
        
        return -1;
    }
    
    void Config::addColorSpace(const ConstColorSpaceRcPtr & original)
    {
        ColorSpaceRcPtr cs = original->createEditableCopy();
        
        std::string name = cs->getName();
        if(name.empty())
            throw Exception("Cannot addColorSpace with an empty name.");
        
        // Check to see if the colorspace already exists
        int csindex = -1;
        if( FindColorSpaceIndex(&csindex, getImpl()->colorspaces_, name) )
        {
            getImpl()->colorspaces_[csindex] = cs;
        }
        else
        {
            // Otherwise, add it
            getImpl()->colorspaces_.push_back( cs );
        }
        
        AutoMutex lock(getImpl()->cacheidMutex_);
        getImpl()->resetCacheIDs();
    }
    
    void Config::clearColorSpaces()
    {
        getImpl()->colorspaces_.clear();
    }
    
    
    
    
    
    
    const char * Config::parseColorSpaceFromString(const char * str) const
    {
        if(!str) return "";
        
        // Search the entire filePath, including directory name (if provided)
        // convert the filename to lowercase.
        std::string fullstr = pystring::lower(std::string(str));
        
        // See if it matches a lut name.
        // This is the position of the RIGHT end of the colorspace substring, not the left
        int rightMostColorPos=-1;
        std::string rightMostColorspace = "";
        int rightMostColorSpaceIndex = -1;
        
        // Find the right-most occcurance within the string for each colorspace.
        for (unsigned int i=0; i<getImpl()->colorspaces_.size(); ++i)
        {
            std::string csname = pystring::lower(getImpl()->colorspaces_[i]->getName());
            
            // find right-most extension matched in filename
            int colorspacePos = pystring::rfind(fullstr, csname);
            if(colorspacePos < 0)
                continue;
            
            // If we have found a match, move the pointer over to the right end of the substring
            // This will allow us to find the longest name that matches the rightmost colorspace
            colorspacePos += (int)csname.size();
            
            if ( (colorspacePos > rightMostColorPos) ||
                 ((colorspacePos == rightMostColorPos) && (csname.size() > rightMostColorspace.size()))
                )
            {
                rightMostColorPos = colorspacePos;
                rightMostColorspace = csname;
                rightMostColorSpaceIndex = i;
            }
        }
        
        if(rightMostColorSpaceIndex>=0)
        {
            return getImpl()->colorspaces_[rightMostColorSpaceIndex]->getName();
        }
        
        if(!getImpl()->strictParsing_)
        {
            // Is a default role defined?
            const char* csname = LookupRole(getImpl()->roles_, ROLE_DEFAULT);
            if(csname && *csname)
            {
                int csindex = -1;
                if( FindColorSpaceIndex(&csindex, getImpl()->colorspaces_, csname) )
                {
                    // This is necessary to not return a reference to
                    // a local variable.
                    return getImpl()->colorspaces_[csindex]->getName();
                }
            }
        }
        
        return "";
    }
    
    bool Config::isStrictParsingEnabled() const
    {
        return getImpl()->strictParsing_;
    }
    
    void Config::setStrictParsingEnabled(bool enabled)
    {
        getImpl()->strictParsing_ = enabled;
        
        AutoMutex lock(getImpl()->cacheidMutex_);
        getImpl()->resetCacheIDs();
    }
    
    // Roles
    void Config::setRole(const char * role, const char * colorSpaceName)
    {
        // Set the role
        if(colorSpaceName)
        {
            getImpl()->roles_[pystring::lower(role)] = std::string(colorSpaceName);
        }
        // Unset the role
        else
        {
            StringMap::iterator iter = getImpl()->roles_.find(pystring::lower(role));
            if(iter != getImpl()->roles_.end())
            {
                getImpl()->roles_.erase(iter);
            }
        }
        
        AutoMutex lock(getImpl()->cacheidMutex_);
        getImpl()->resetCacheIDs();
    }
    
    int Config::getNumRoles() const
    {
        return static_cast<int>(getImpl()->roles_.size());
    }
    
    bool Config::hasRole(const char * role) const
    {
        const char* rname = LookupRole(getImpl()->roles_, role);
        return  rname && *rname;
    }
    
    const char * Config::getRoleName(int index) const
    {
        if(index < 0 || index >= (int)getImpl()->roles_.size()) return "";
        StringMap::const_iterator iter = getImpl()->roles_.begin();
        for(int i = 0; i < index; ++i) ++iter;
        return iter->first.c_str();
    }
    
    ///////////////////////////////////////////////////////////////////////////
    //
    // Display/View Registration
    
    
    const char * Config::getDefaultDisplay() const
    {
        if(getImpl()->displayCache_.empty())
        {
            ComputeDisplays(getImpl()->displayCache_,
                            getImpl()->displays_,
                            getImpl()->activeDisplays_,
                            getImpl()->activeDisplaysEnvOverride_);
        }
        
        int index = -1;
        
        if(!getImpl()->activeDisplaysEnvOverride_.empty())
        {
            StringVec orderedDisplays = IntersectStringVecsCaseIgnore(getImpl()->activeDisplaysEnvOverride_,
                                                           getImpl()->displayCache_);
            if(!orderedDisplays.empty())
            {
                index = FindInStringVecCaseIgnore(getImpl()->displayCache_, orderedDisplays[0]);
            }
        }
        else if(!getImpl()->activeDisplays_.empty())
        {
            StringVec orderedDisplays = IntersectStringVecsCaseIgnore(getImpl()->activeDisplays_,
                                                           getImpl()->displayCache_);
            if(!orderedDisplays.empty())
            {
                index = FindInStringVecCaseIgnore(getImpl()->displayCache_, orderedDisplays[0]);
            }
        }
        
        if(index >= 0)
        {
            return getImpl()->displayCache_[index].c_str();
        }
        
        if(!getImpl()->displayCache_.empty())
        {
            return getImpl()->displayCache_[0].c_str();
        }
        
        return "";
    }


    int Config::getNumDisplays() const
    {
        if(getImpl()->displayCache_.empty())
        {
            ComputeDisplays(getImpl()->displayCache_,
                            getImpl()->displays_,
                            getImpl()->activeDisplays_,
                            getImpl()->activeDisplaysEnvOverride_);
        }
        
        return static_cast<int>(getImpl()->displayCache_.size());
    }

    const char * Config::getDisplay(int index) const
    {
        if(getImpl()->displayCache_.empty())
        {
            ComputeDisplays(getImpl()->displayCache_,
                            getImpl()->displays_,
                            getImpl()->activeDisplays_,
                            getImpl()->activeDisplaysEnvOverride_);
        }
        
        if(index>=0 || index < static_cast<int>(getImpl()->displayCache_.size()))
        {
            return getImpl()->displayCache_[index].c_str();
        }
        
        return "";
    }
    
    const char * Config::getDefaultView(const char * display) const
    {
        if(getImpl()->displayCache_.empty())
        {
            ComputeDisplays(getImpl()->displayCache_,
                            getImpl()->displays_,
                            getImpl()->activeDisplays_,
                            getImpl()->activeDisplaysEnvOverride_);
        }
        
        if(!display) return "";
        
        DisplayMap::const_iterator iter = find_display_const(getImpl()->displays_, display);
        if(iter == getImpl()->displays_.end()) return "";
        
        const ViewVec & views = iter->second;
        
        StringVec masterViews;
        for(unsigned int i=0; i<views.size(); ++i)
        {
            masterViews.push_back(views[i].name);
        }
        
        int index = -1;
        
        if(!getImpl()->activeViewsEnvOverride_.empty())
        {
            StringVec orderedViews = IntersectStringVecsCaseIgnore(getImpl()->activeViewsEnvOverride_,
                                                           masterViews);
            if(!orderedViews.empty())
            {
                index = FindInStringVecCaseIgnore(masterViews, orderedViews[0]);
            }
        }
        else if(!getImpl()->activeViews_.empty())
        {
            StringVec orderedViews = IntersectStringVecsCaseIgnore(getImpl()->activeViews_,
                                                           masterViews);
            if(!orderedViews.empty())
            {
                index = FindInStringVecCaseIgnore(masterViews, orderedViews[0]);
            }
        }
        
        if(index >= 0)
        {
            return views[index].name.c_str();
        }
        
        if(!views.empty())
        {
            return views[0].name.c_str();
        }
        
        return "";
    }

    int Config::getNumViews(const char * display) const
    {
        if(getImpl()->displayCache_.empty())
        {
            ComputeDisplays(getImpl()->displayCache_,
                            getImpl()->displays_,
                            getImpl()->activeDisplays_,
                            getImpl()->activeDisplaysEnvOverride_);
        }
        
        if(!display) return 0;
        
        DisplayMap::const_iterator iter = find_display_const(getImpl()->displays_, display);
        if(iter == getImpl()->displays_.end()) return 0;
        
        const ViewVec & views = iter->second;
        return static_cast<int>(views.size());
    }

    const char * Config::getView(const char * display, int index) const
    {
        if(getImpl()->displayCache_.empty())
        {
            ComputeDisplays(getImpl()->displayCache_,
                            getImpl()->displays_,
                            getImpl()->activeDisplays_,
                            getImpl()->activeDisplaysEnvOverride_);
        }
        
        if(!display) return "";
        
        DisplayMap::const_iterator iter = find_display_const(getImpl()->displays_, display);
        if(iter == getImpl()->displays_.end()) return "";
        
        const ViewVec & views = iter->second;
        return views[index].name.c_str();
    }

    const char * Config::getDisplayColorSpaceName(const char * display, const char * view) const
    {
        if(!display || !view) return "";
        
        DisplayMap::const_iterator iter = find_display_const(getImpl()->displays_, display);
        if(iter == getImpl()->displays_.end()) return "";
        
        const ViewVec & views = iter->second;
        int index = find_view(views, view);
        if(index<0) return "";
        
        return views[index].colorspace.c_str();
    }
    
    const char * Config::getDisplayLooks(const char * display, const char * view) const
    {
        if(!display || !view) return "";
        
        DisplayMap::const_iterator iter = find_display_const(getImpl()->displays_, display);
        if(iter == getImpl()->displays_.end()) return "";
        
        const ViewVec & views = iter->second;
        int index = find_view(views, view);
        if(index<0) return "";
        
        return views[index].looks.c_str();
    }
    
    void Config::addDisplay(const char * display, const char * view,
                            const char * colorSpaceName, const char * lookName)
    {
        
        if(!display || !view || !colorSpaceName || !lookName) return;
        
        AddDisplay(getImpl()->displays_,
                   display, view, colorSpaceName, lookName);
        getImpl()->displayCache_.clear();
        
        AutoMutex lock(getImpl()->cacheidMutex_);
        getImpl()->resetCacheIDs();
    }
    
    void Config::clearDisplays()
    {
        getImpl()->displays_.clear();
        getImpl()->displayCache_.clear();
        
        AutoMutex lock(getImpl()->cacheidMutex_);
        getImpl()->resetCacheIDs();
    }
    
    void Config::setActiveDisplays(const char * displays)
    {
        getImpl()->activeDisplays_.clear();
        SplitStringEnvStyle(getImpl()->activeDisplays_, displays);
        
        getImpl()->displayCache_.clear();
        
        AutoMutex lock(getImpl()->cacheidMutex_);
        getImpl()->resetCacheIDs();
    }

    const char * Config::getActiveDisplays() const
    {
        getImpl()->activeDisplaysStr_ = JoinStringEnvStyle(getImpl()->activeDisplays_);
        return getImpl()->activeDisplaysStr_.c_str();
    }
    
    void Config::setActiveViews(const char * views)
    {
        getImpl()->activeViews_.clear();
        SplitStringEnvStyle(getImpl()->activeViews_, views);
        
        getImpl()->displayCache_.clear();
        
        AutoMutex lock(getImpl()->cacheidMutex_);
        getImpl()->resetCacheIDs();
    }

    const char * Config::getActiveViews() const
    {
        getImpl()->activeViewsStr_ = JoinStringEnvStyle(getImpl()->activeViews_);
        return getImpl()->activeViewsStr_.c_str();
    }
    
    ///////////////////////////////////////////////////////////////////////////
    
    
    void Config::getDefaultLumaCoefs(float * c3) const
    {
        memcpy(c3, &getImpl()->defaultLumaCoefs_[0], 3*sizeof(float));
    }
    
    void Config::setDefaultLumaCoefs(const float * c3)
    {
        memcpy(&getImpl()->defaultLumaCoefs_[0], c3, 3*sizeof(float));
        
        AutoMutex lock(getImpl()->cacheidMutex_);
        getImpl()->resetCacheIDs();
    }
    
    
    
    
    ///////////////////////////////////////////////////////////////////////////
    
    
    
    
    ConstLookRcPtr Config::getLook(const char * name) const
    {
        std::string namelower = pystring::lower(name);
        
        for(unsigned int i=0; i<getImpl()->looksList_.size(); ++i)
        {
            if(pystring::lower(getImpl()->looksList_[i]->getName()) == namelower)
            {
                return getImpl()->looksList_[i];
            }
        }
        
        return ConstLookRcPtr();
    }
    
    int Config::getNumLooks() const
    {
        return static_cast<int>(getImpl()->looksList_.size());
    }
    
    const char * Config::getLookNameByIndex(int index) const
    {
        if(index<0 || index>=static_cast<int>(getImpl()->looksList_.size()))
        {
            return "";
        }
        
        return getImpl()->looksList_[index]->getName();
    }
    
    void Config::addLook(const ConstLookRcPtr & look)
    {
        std::string name = look->getName();
        if(name.empty())
            throw Exception("Cannot addLook with an empty name.");
        
        std::string namelower = pystring::lower(name);
        
        // If the look exists, replace it
        for(unsigned int i=0; i<getImpl()->looksList_.size(); ++i)
        {
            if(pystring::lower(getImpl()->looksList_[i]->getName()) == namelower)
            {
                getImpl()->looksList_[i] = look->createEditableCopy();
                return;
            }
        }
        
        // Otherwise, add it
        getImpl()->looksList_.push_back(look->createEditableCopy());
        
        AutoMutex lock(getImpl()->cacheidMutex_);
        getImpl()->resetCacheIDs();
    }
    
    void Config::clearLooks()
    {
        getImpl()->looksList_.clear();
        
        AutoMutex lock(getImpl()->cacheidMutex_);
        getImpl()->resetCacheIDs();
    }
    
    ///////////////////////////////////////////////////////////////////////////
    
    
    
    ConstProcessorRcPtr Config::getProcessor(const ConstColorSpaceRcPtr & src,
                                             const ConstColorSpaceRcPtr & dst) const
    {
        ConstContextRcPtr context = getCurrentContext();
        return getProcessor(context, src, dst);
    }
    
    ConstProcessorRcPtr Config::getProcessor(const ConstContextRcPtr & context,
                                             const ConstColorSpaceRcPtr & src,
                                             const ConstColorSpaceRcPtr & dst) const
    {
        if(!src)
        {
            throw Exception("Config::GetProcessor failed. Source colorspace is null.");
        }
        if(!dst)
        {
            throw Exception("Config::GetProcessor failed. Destination colorspace is null.");
        }
        
        ProcessorRcPtr processor = Processor::Create();
        processor->getImpl()->addColorSpaceConversion(*this, context, src, dst);
        processor->getImpl()->finalize();
        return processor;
    }
    
    ConstProcessorRcPtr Config::getProcessor(const char * srcName,
                                             const char * dstName) const
    {
        ConstContextRcPtr context = getCurrentContext();
        return getProcessor(context, srcName, dstName);
    }
    
    //! Names can be colorspace name or role name
    ConstProcessorRcPtr Config::getProcessor(const ConstContextRcPtr & context,
                                             const char * srcName,
                                             const char * dstName) const
    {
        ConstColorSpaceRcPtr src = getColorSpace(srcName);
        if(!src)
        {
            std::ostringstream os;
            os << "Could not find colorspace '" << srcName << "'.";
            throw Exception(os.str().c_str());
        }
        
        ConstColorSpaceRcPtr dst = getColorSpace(dstName);
        if(!dst)
        {
            std::ostringstream os;
            os << "Could not find colorspace '" << dstName << "'.";
            throw Exception(os.str().c_str());
        }
        
        return getProcessor(context, src, dst);
    }
    
    
    ConstProcessorRcPtr Config::getProcessor(const ConstTransformRcPtr& transform) const
    {
        return getProcessor(transform, TRANSFORM_DIR_FORWARD);
    }
    
    
    ConstProcessorRcPtr Config::getProcessor(const ConstTransformRcPtr& transform,
                                             TransformDirection direction) const
    {
        ConstContextRcPtr context = getCurrentContext();
        return getProcessor(context, transform, direction);
    }
    
    ConstProcessorRcPtr Config::getProcessor(const ConstContextRcPtr & context,
                                             const ConstTransformRcPtr& transform,
                                             TransformDirection direction) const
    {
        ProcessorRcPtr processor = Processor::Create();
        processor->getImpl()->addTransform(*this, context, transform, direction);
        processor->getImpl()->finalize();
        return processor;
    }
    
    std::ostream& operator<< (std::ostream& os, const Config& config)
    {
        config.serialize(os);
        return os;
    }
    
    ///////////////////////////////////////////////////////////////////////////
    //  CacheID
    
    const char * Config::getCacheID() const
    {
        return getCacheID(getCurrentContext());
    }
    
    const char * Config::getCacheID(const ConstContextRcPtr & context) const
    {
        AutoMutex lock(getImpl()->cacheidMutex_);
        
        // A null context will use the empty cacheid
        std::string contextcacheid = "";
        if(context) contextcacheid = context->getCacheID();
        
        StringMap::const_iterator cacheiditer = getImpl()->cacheids_.find(contextcacheid);
        if(cacheiditer != getImpl()->cacheids_.end())
        {
            return cacheiditer->second.c_str();
        }
        
        // Include the hash of the yaml config serialization
        if(getImpl()->cacheidnocontext_.empty())
        {
            std::stringstream cacheid;
            serialize(cacheid);
            std::string fullstr = cacheid.str();
            getImpl()->cacheidnocontext_ = CacheIDHash(fullstr.c_str(), (int)fullstr.size());
        }
        
        // Also include all file references, using the context (if specified)
        std::string fileReferencesFashHash = "";
        if(context)
        {
            std::ostringstream filehash;
            
            ConstTransformVec allTransforms;
            getImpl()->getAllIntenalTransforms(allTransforms);
            
            std::set<std::string> files;
            for(unsigned int i=0; i<allTransforms.size(); ++i)
            {
                GetFileReferences(files, allTransforms[i]);
            }
            
            for(std::set<std::string>::iterator iter = files.begin();
                iter != files.end(); ++iter)
            {
                if(iter->empty()) continue;
                filehash << *iter << "=";
                
                try
                {
                    std::string resolvedLocation = context->resolveFileLocation(iter->c_str());
                    filehash << GetFastFileHash(resolvedLocation) << " ";
                }
                catch(...)
                {
                    filehash << "? ";
                    continue;
                }
            }
            
            std::string fullstr = filehash.str();
            fileReferencesFashHash = CacheIDHash(fullstr.c_str(), (int)fullstr.size());
        }
        
        getImpl()->cacheids_[contextcacheid] = getImpl()->cacheidnocontext_ + ":" + fileReferencesFashHash;
        return getImpl()->cacheids_[contextcacheid].c_str();
    }
    
    
    ///////////////////////////////////////////////////////////////////////////
    //  Serialization
    
    void Config::serialize(std::ostream& os) const
    {
        try
        {
            getImpl()->io_.write(os, this);
        }
        catch( const std::exception & e)
        {
            std::ostringstream error;
            error << "Error building YAML: " << e.what();
            throw Exception(error.str().c_str());
        }
    }
    
    void Config::Impl::resetCacheIDs()
    {
        cacheids_.clear();
        cacheidnocontext_ = "";
        sanity_ = SANITY_UNKNOWN;
        sanitytext_ = "";
    }
    
    void Config::Impl::getAllIntenalTransforms(ConstTransformVec & transformVec) const
    {
        // Grab all transforms from the ColorSpaces
        for(unsigned int i=0; i<colorspaces_.size(); ++i)
        {
            if(colorspaces_[i]->getTransform(COLORSPACE_DIR_TO_REFERENCE))
                transformVec.push_back(colorspaces_[i]->getTransform(COLORSPACE_DIR_TO_REFERENCE));
            if(colorspaces_[i]->getTransform(COLORSPACE_DIR_FROM_REFERENCE))
                transformVec.push_back(colorspaces_[i]->getTransform(COLORSPACE_DIR_FROM_REFERENCE));
        }
        
        // Grab all transforms from the Looks
        for(unsigned int i=0; i<looksList_.size(); ++i)
        {
            if(looksList_[i]->getTransform())
                transformVec.push_back(looksList_[i]->getTransform());
            if(looksList_[i]->getInverseTransform())
                transformVec.push_back(looksList_[i]->getInverseTransform());
        }
    
    }
}
OCIO_NAMESPACE_EXIT

///////////////////////////////////////////////////////////////////////////////

#ifdef OCIO_UNIT_TEST

namespace OCIO = OCIO_NAMESPACE;
#include "UnitTest.h"

#include <sys/stat.h>
#include "pystring/pystring.h"

#if 0
OIIO_ADD_TEST(Config, test_searchpath_filesystem)
{
    
    OCIO::EnvMap env = OCIO::GetEnvMap();
    std::string OCIO_TEST_AREA("$OCIO_TEST_AREA");
    EnvExpand(&OCIO_TEST_AREA, &env);
    
    OCIO::ConfigRcPtr config = OCIO::Config::Create();
    
    // basic get/set/expand
    config->setSearchPath("."
                          ":$OCIO_TEST1"
                          ":/$OCIO_JOB/${OCIO_SEQ}/$OCIO_SHOT/ocio");
    
    OIIO_CHECK_ASSERT(strcmp(config->getSearchPath(),
        ".:$OCIO_TEST1:/$OCIO_JOB/${OCIO_SEQ}/$OCIO_SHOT/ocio") == 0);
    OIIO_CHECK_ASSERT(strcmp(config->getSearchPath(true),
        ".:foobar:/meatballs/cheesecake/mb-cc-001/ocio") == 0);
    
    // find some files
    config->setSearchPath(".."
                          ":$OCIO_TEST1"
                          ":${OCIO_TEST_AREA}/test_search/one"
                          ":$OCIO_TEST_AREA/test_search/two");
    
    // setup for search test
    std::string base_dir("$OCIO_TEST_AREA/test_search/");
    EnvExpand(&base_dir, &env);
    mkdir(base_dir.c_str(), 0777);
    
    std::string one_dir("$OCIO_TEST_AREA/test_search/one/");
    EnvExpand(&one_dir, &env);
    mkdir(one_dir.c_str(), 0777);
    
    std::string two_dir("$OCIO_TEST_AREA/test_search/two/");
    EnvExpand(&two_dir, &env);
    mkdir(two_dir.c_str(), 0777);
    
    std::string lut1(one_dir+"somelut1.lut");
    std::ofstream somelut1(lut1.c_str());
    somelut1.close();
    
    std::string lut2(two_dir+"somelut2.lut");
    std::ofstream somelut2(lut2.c_str());
    somelut2.close();
    
    std::string lut3(two_dir+"somelut3.lut");
    std::ofstream somelut3(lut3.c_str());
    somelut3.close();
    
    std::string lutdotdot(OCIO_TEST_AREA+"/lutdotdot.lut");
    std::ofstream somelutdotdot(lutdotdot.c_str());
    somelutdotdot.close();
    
    // basic search test
    OIIO_CHECK_ASSERT(strcmp(config->findFile("somelut1.lut"),
        lut1.c_str()) == 0);
    OIIO_CHECK_ASSERT(strcmp(config->findFile("somelut2.lut"),
        lut2.c_str()) == 0);
    OIIO_CHECK_ASSERT(strcmp(config->findFile("somelut3.lut"),
        lut3.c_str()) == 0);
    OIIO_CHECK_ASSERT(strcmp(config->findFile("lutdotdot.lut"),
        lutdotdot.c_str()) == 0);
    
}
#endif

OIIO_ADD_TEST(Config, InternalRawProfile)
{
    std::istringstream is;
    is.str(OCIO::INTERNAL_RAW_PROFILE);
    OIIO_CHECK_NO_THROW(OCIO::ConstConfigRcPtr config = OCIO::Config::CreateFromStream(is));
}

OIIO_ADD_TEST(Config, SimpleConfig)
{
    
    std::string SIMPLE_PROFILE =
    "ocio_profile_version: 1\n"
    "resource_path: luts\n"
    "strictparsing: false\n"
    "luma: [0.2126, 0.7152, 0.0722]\n"
    "roles:\n"
    "  compositing_log: lgh\n"
    "  default: raw\n"
    "  scene_linear: lnh\n"
    "displays:\n"
    "  sRGB:\n"
    "  - !<View> {name: Film1D, colorspace: vd8}\n"
    "  - !<View> {name: Log, colorspace: lg10}\n"
    "  - !<View> {name: Raw, colorspace: raw}\n"
    "colorspaces:\n"
    "  - !<ColorSpace>\n"
    "      name: raw\n"
    "      family: raw\n"
    "      equalitygroup: \n"
    "      bitdepth: 32f\n"
    "      description: |\n"
    "        A raw color space. Conversions to and from this space are no-ops.\n"
    "      isdata: true\n"
    "      allocation: uniform\n"
    "  - !<ColorSpace>\n"
    "      name: lnh\n"
    "      family: ln\n"
    "      equalitygroup: \n"
    "      bitdepth: 16f\n"
    "      description: |\n"
    "        The show reference space. This is a sensor referred linear\n"
    "        representation of the scene with primaries that correspond to\n"
    "        scanned film. 0.18 in this space corresponds to a properly\n"
    "        exposed 18% grey card.\n"
    "      isdata: false\n"
    "      allocation: lg2\n"
    "  - !<ColorSpace>\n"
    "      name: loads_of_transforms\n"
    "      family: vd8\n"
    "      equalitygroup: \n"
    "      bitdepth: 8ui\n"
    "      description: 'how many transforms can we use?'\n"
    "      isdata: false\n"
    "      allocation: uniform\n"
    "      to_reference: !<GroupTransform>\n"
    "        direction: forward\n"
    "        children:\n"
    "          - !<FileTransform>\n"
    "            src: diffusemult.spimtx\n"
    "            interpolation: unknown\n"
    "          - !<ColorSpaceTransform>\n"
    "            src: vd8\n"
    "            dst: lnh\n"
    "          - !<ExponentTransform>\n"
    "            value: [2.2, 2.2, 2.2, 1]\n"
    "          - !<MatrixTransform>\n"
    "            matrix: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]\n"
    "            offset: [0, 0, 0, 0]\n"
    "          - !<CDLTransform>\n"
    "            slope: [1, 1, 1]\n"
    "            offset: [0, 0, 0]\n"
    "            power: [1, 1, 1]\n"
    "            saturation: 1\n"
    "\n";
    
    std::istringstream is;
    is.str(SIMPLE_PROFILE);
    OCIO::ConstConfigRcPtr config;
    OIIO_CHECK_NO_THROW(config = OCIO::Config::CreateFromStream(is));
}

OIIO_ADD_TEST(Config, Roles)
{
    
    std::string SIMPLE_PROFILE =
    "ocio_profile_version: 1\n"
    "strictparsing: false\n"
    "roles:\n"
    "  compositing_log: lgh\n"
    "  default: raw\n"
    "  scene_linear: lnh\n"
    "colorspaces:\n"
    "  - !<ColorSpace>\n"
    "      name: raw\n"
    "  - !<ColorSpace>\n"
    "      name: lnh\n"
    "  - !<ColorSpace>\n"
    "      name: lgh\n"
    "\n";
    
    std::istringstream is;
    is.str(SIMPLE_PROFILE);
    OCIO::ConstConfigRcPtr config;
    OIIO_CHECK_NO_THROW(config = OCIO::Config::CreateFromStream(is));
    
    OIIO_CHECK_EQUAL(config->getNumRoles(), 3);
    
    OIIO_CHECK_ASSERT(config->hasRole("compositing_log") == true);
    OIIO_CHECK_ASSERT(config->hasRole("cheese") == false);
    OIIO_CHECK_ASSERT(config->hasRole("") == false);
    
    OIIO_CHECK_ASSERT(strcmp(config->getRoleName(2), "scene_linear") == 0);
    OIIO_CHECK_ASSERT(strcmp(config->getRoleName(0), "compositing_log") == 0);
    OIIO_CHECK_ASSERT(strcmp(config->getRoleName(1), "default") == 0);
    OIIO_CHECK_ASSERT(strcmp(config->getRoleName(10), "") == 0);
    OIIO_CHECK_ASSERT(strcmp(config->getRoleName(-4), "") == 0);
    
}

OIIO_ADD_TEST(Config, Serialize)
{
    
    OCIO::ConfigRcPtr config = OCIO::Config::Create();
    {
        OCIO::ColorSpaceRcPtr cs = OCIO::ColorSpace::Create();
        cs->setName("testing");
        cs->setFamily("test");
        OCIO::FileTransformRcPtr transform1 = \
            OCIO::FileTransform::Create();
        OCIO::GroupTransformRcPtr groupTransform = OCIO::GroupTransform::Create();
        groupTransform->push_back(transform1);
        cs->setTransform(groupTransform, OCIO::COLORSPACE_DIR_TO_REFERENCE);
        config->addColorSpace(cs);
        config->setRole( OCIO::ROLE_COMPOSITING_LOG, cs->getName() );
    }
    {
        OCIO::ColorSpaceRcPtr cs = OCIO::ColorSpace::Create();
        cs->setName("testing2");
        cs->setFamily("test");
        OCIO::ExponentTransformRcPtr transform1 = \
            OCIO::ExponentTransform::Create();
        OCIO::GroupTransformRcPtr groupTransform = OCIO::GroupTransform::Create();
        groupTransform->push_back(transform1);
        cs->setTransform(groupTransform, OCIO::COLORSPACE_DIR_TO_REFERENCE);
        config->addColorSpace(cs);
        config->setRole( OCIO::ROLE_COMPOSITING_LOG, cs->getName() );
    }
    
    // for testing
    //std::ofstream outfile("/tmp/test.ocio");
    //config->serialize(outfile);
    //outfile.close();
    
    std::ostringstream os;
    config->serialize(os);
    
    std::string PROFILE_OUT =
    "ocio_profile_version: 1\n"
    "\n"
    "search_path: \"\"\n"
    "strictparsing: true\n"
    "luma: [0.2126, 0.7152, 0.0722]\n"
    "\n"
    "roles:\n"
    "  compositing_log: testing2\n"
    "\n"
    "displays:\n"
    "  {}\n"
    "\n"
    "active_displays: []\n"
    "active_views: []\n"
    "\n"
    "colorspaces:\n"
    "  - !<ColorSpace>\n"
    "    name: testing\n"
    "    family: test\n"
    "    equalitygroup: \"\"\n"
    "    bitdepth: unknown\n"
    "    isdata: false\n"
    "    allocation: uniform\n"
    "    to_reference: !<GroupTransform>\n"
    "      children:\n"
    "        - !<FileTransform> {src: \"\", interpolation: unknown}\n"
    "\n"
    "  - !<ColorSpace>\n"
    "    name: testing2\n"
    "    family: test\n"
    "    equalitygroup: \"\"\n"
    "    bitdepth: unknown\n"
    "    isdata: false\n"
    "    allocation: uniform\n"
    "    to_reference: !<GroupTransform>\n"
    "      children:\n"
    "        - !<ExponentTransform> {value: [1, 1, 1, 1]}\n";
    
    std::vector<std::string> osvec;
    OCIO::pystring::splitlines(os.str(), osvec);
    std::vector<std::string> PROFILE_OUTvec;
    OCIO::pystring::splitlines(PROFILE_OUT, PROFILE_OUTvec);
    
    OIIO_CHECK_EQUAL(osvec.size(), PROFILE_OUTvec.size());
    for(unsigned int i = 0; i < PROFILE_OUTvec.size(); ++i)
        OIIO_CHECK_EQUAL(osvec[i], PROFILE_OUTvec[i]);
}


OIIO_ADD_TEST(Config, SanityCheck)
{
    {
    std::string SIMPLE_PROFILE =
    "ocio_profile_version: 1\n"
    "colorspaces:\n"
    "  - !<ColorSpace>\n"
    "      name: raw\n"
    "  - !<ColorSpace>\n"
    "      name: raw\n"
    "strictparsing: false\n"
    "roles:\n"
    "  default: raw\n"
    "displays:\n"
    "  sRGB:\n"
    "  - !<View> {name: Raw, colorspace: raw}\n"
    "\n";
    
    std::istringstream is;
    is.str(SIMPLE_PROFILE);
    OCIO::ConstConfigRcPtr config;
    OIIO_CHECK_THROW(config = OCIO::Config::CreateFromStream(is), OCIO::Exception);
    }
    
    {
    std::string SIMPLE_PROFILE =
    "ocio_profile_version: 1\n"
    "colorspaces:\n"
    "  - !<ColorSpace>\n"
    "      name: raw\n"
    "strictparsing: false\n"
    "roles:\n"
    "  default: raw\n"
    "displays:\n"
    "  sRGB:\n"
    "  - !<View> {name: Raw, colorspace: raw}\n"
    "\n";
    
    std::istringstream is;
    is.str(SIMPLE_PROFILE);
    OCIO::ConstConfigRcPtr config;
    OIIO_CHECK_NO_THROW(config = OCIO::Config::CreateFromStream(is));
    
    OIIO_CHECK_NO_THROW(config->sanityCheck());
    }
}


OIIO_ADD_TEST(Config, EnvCheck)
{
    {
    std::string SIMPLE_PROFILE =
    "ocio_profile_version: 1\n"
    "environment:\n"
    "  SHOW: super\n"
    "  SHOT: test\n"
    "  SEQ: foo\n"
    "  test: bar${cheese}\n"
    "  cheese: chedder\n"
    "colorspaces:\n"
    "  - !<ColorSpace>\n"
    "      name: raw\n"
    "strictparsing: false\n"
    "roles:\n"
    "  default: raw\n"
    "displays:\n"
    "  sRGB:\n"
    "  - !<View> {name: Raw, colorspace: raw}\n"
    "\n";
    
    std::string SIMPLE_PROFILE2 =
    "ocio_profile_version: 1\n"
    "colorspaces:\n"
    "  - !<ColorSpace>\n"
    "      name: raw\n"
    "strictparsing: false\n"
    "roles:\n"
    "  default: raw\n"
    "displays:\n"
    "  sRGB:\n"
    "  - !<View> {name: Raw, colorspace: raw}\n"
    "\n";
    
    
    std::string test("SHOW=bar");
    putenv((char *)test.c_str());
    std::string test2("TASK=lighting");
    putenv((char *)test2.c_str());
    
    std::istringstream is;
    is.str(SIMPLE_PROFILE);
    OCIO::ConstConfigRcPtr config;
    OIIO_CHECK_NO_THROW(config = OCIO::Config::CreateFromStream(is));
    OIIO_CHECK_EQUAL(config->getNumEnvironmentVars(), 5);
    OIIO_CHECK_ASSERT(strcmp(config->getCurrentContext()->resolveStringVar("test${test}"),
        "testbarchedder") == 0);
    OIIO_CHECK_ASSERT(strcmp(config->getCurrentContext()->resolveStringVar("${SHOW}"),
        "bar") == 0);
    OIIO_CHECK_ASSERT(strcmp(config->getEnvironmentVarDefault("SHOW"), "super") == 0);
    
    OCIO::ConfigRcPtr edit = config->createEditableCopy();
    edit->clearEnvironmentVars();
    OIIO_CHECK_EQUAL(edit->getNumEnvironmentVars(), 0);
    
    edit->addEnvironmentVar("testing", "dupvar");
    edit->addEnvironmentVar("testing", "dupvar");
    edit->addEnvironmentVar("foobar", "testing");
    edit->addEnvironmentVar("blank", "");
    edit->addEnvironmentVar("dontadd", NULL);
    OIIO_CHECK_EQUAL(edit->getNumEnvironmentVars(), 3);
    edit->addEnvironmentVar("foobar", NULL); // remove
    OIIO_CHECK_EQUAL(edit->getNumEnvironmentVars(), 2);
    edit->clearEnvironmentVars();
    
    edit->addEnvironmentVar("SHOW", "super");
    edit->addEnvironmentVar("SHOT", "test");
    edit->addEnvironmentVar("SEQ", "foo");
    edit->addEnvironmentVar("test", "bar${cheese}");
    edit->addEnvironmentVar("cheese", "chedder");
    
    //Test
    OCIO::LoggingLevel loglevel = OCIO::GetLoggingLevel();
    OCIO::SetLoggingLevel(OCIO::LOGGING_LEVEL_DEBUG);
    is.str(SIMPLE_PROFILE2);
    OCIO::ConstConfigRcPtr noenv;
    OIIO_CHECK_NO_THROW(noenv = OCIO::Config::CreateFromStream(is));
    OIIO_CHECK_ASSERT(strcmp(noenv->getCurrentContext()->resolveStringVar("${TASK}"),
        "lighting") == 0);
    OCIO::SetLoggingLevel(loglevel);
    
    OIIO_CHECK_EQUAL(edit->getEnvironmentMode(), OCIO::ENV_ENVIRONMENT_LOAD_PREDEFINED);
    edit->setEnvironmentMode(OCIO::ENV_ENVIRONMENT_LOAD_ALL);
    OIIO_CHECK_EQUAL(edit->getEnvironmentMode(), OCIO::ENV_ENVIRONMENT_LOAD_ALL);
    
    }
}

OIIO_ADD_TEST(Config, RoleWithoutColorSpace)
{
    OCIO::ConfigRcPtr config = OCIO::Config::Create()->createEditableCopy();
    config->setRole("reference", "UnknownColorSpace");

    std::ostringstream os;
    OIIO_CHECK_THROW(config->serialize(os), OCIO::Exception);
}

OIIO_ADD_TEST(Config, Env_colorspace_name)
{
    const std::string MY_OCIO_CONFIG =
        "ocio_profile_version: 1\n"
        "\n"
        "search_path: luts\n"
        "strictparsing: true\n"
        "luma: [0.2126, 0.7152, 0.0722]\n"
        "\n"
        "roles:\n"
        "  compositing_log: lgh\n"
        "  default: raw\n"
        "  scene_linear: lnh\n"
        "\n"
        "displays:\n"
        "  sRGB:\n"
        "    - !<View> {name: Raw, colorspace: raw}\n"
        "\n"
        "active_displays: []\n"
        "active_views: []\n"
        "\n"
        "colorspaces:\n"
        "  - !<ColorSpace>\n"
        "    name: raw\n"
        "    family: \"\"\n"
        "    equalitygroup: \"\"\n"
        "    bitdepth: unknown\n"
        "    isdata: false\n"
        "    allocation: uniform\n"
        "\n"
        "  - !<ColorSpace>\n"
        "    name: lnh\n"
        "    family: \"\"\n"
        "    equalitygroup: \"\"\n"
        "    bitdepth: unknown\n"
        "    isdata: false\n"
        "    allocation: uniform\n"
        "\n"
        "  - !<ColorSpace>\n"
        "    name: lgh\n"
        "    family: \"\"\n"
        "    equalitygroup: \"\"\n"
        "    bitdepth: unknown\n"
        "    isdata: false\n"
        "    allocation: uniform\n"
        "    allocationvars: [-0.125, 1.125]\n"
        "    from_reference: !<ColorSpaceTransform> {src: raw, dst: $CAMERARAW}\n";


    {
        // Test when the env. variable is missing

        std::istringstream is;
        is.str(MY_OCIO_CONFIG);

        OCIO::ConstConfigRcPtr config;
        OIIO_CHECK_NO_THROW(config = OCIO::Config::CreateFromStream(is));
        OIIO_CHECK_NO_THROW(config->sanityCheck());
        OIIO_CHECK_THROW(config->getProcessor("raw", "lgh"), OCIO::Exception);
    }

    {
        char * env = (char *)"CAMERARAW=lnh";
        putenv(env);

        std::istringstream is;
        is.str(MY_OCIO_CONFIG);

        OCIO::ConstConfigRcPtr config;
        OIIO_CHECK_NO_THROW(config = OCIO::Config::CreateFromStream(is));
        OIIO_CHECK_NO_THROW(config->sanityCheck());
        OIIO_CHECK_NO_THROW(config->getProcessor("raw", "lgh"));
    }

    {
        // Test when the env. variable content is wrong

        char * env = (char *)"CAMERARAW=FaultyColorSpaceName";
        putenv(env);

        std::istringstream is;
        is.str(MY_OCIO_CONFIG);

        OCIO::ConstConfigRcPtr config;
        OIIO_CHECK_NO_THROW(config = OCIO::Config::CreateFromStream(is));
        OIIO_CHECK_NO_THROW(config->sanityCheck());
        OIIO_CHECK_THROW(config->getProcessor("raw", "lgh"), OCIO::Exception);
    }

    {
        // Check that the serialization preserves the env. variable

        std::istringstream is;
        is.str(MY_OCIO_CONFIG);

        OCIO::ConstConfigRcPtr config;
        OIIO_CHECK_NO_THROW(config = OCIO::Config::CreateFromStream(is));
        OIIO_CHECK_NO_THROW(config->sanityCheck());

        std::stringstream ss;
        ss << *config.get();
        OIIO_CHECK_EQUAL(ss.str(), MY_OCIO_CONFIG);
    }
}

OIIO_ADD_TEST(Config, display)
{
    static const std::string SIMPLE_PROFILE_HEADER =
        "ocio_profile_version: 1\n"
        "\n"
        "search_path: luts\n"
        "strictparsing: true\n"
        "luma: [0.2126, 0.7152, 0.0722]\n"
        "\n"
        "roles:\n"
        "  default: raw\n"
        "  scene_linear: lnh\n"
        "\n"
        "displays:\n"
        "  sRGB_1:\n"
        "    - !<View> {name: Raw, colorspace: raw}\n"
        "  sRGB_2:\n"
        "    - !<View> {name: Raw, colorspace: raw}\n"
        "  sRGB_3:\n"
        "    - !<View> {name: Raw, colorspace: raw}\n"
        "\n";

    static const std::string SIMPLE_PROFILE_FOOTER =
        "\n"
        "colorspaces:\n"
        "  - !<ColorSpace>\n"
        "    name: raw\n"
        "    family: \"\"\n"
        "    equalitygroup: \"\"\n"
        "    bitdepth: unknown\n"
        "    isdata: false\n"
        "    allocation: uniform\n"
        "\n"
        "  - !<ColorSpace>\n"
        "    name: lnh\n"
        "    family: \"\"\n"
        "    equalitygroup: \"\"\n"
        "    bitdepth: unknown\n"
        "    isdata: false\n"
        "    allocation: uniform\n";

    {
        std::string myProfile = 
            SIMPLE_PROFILE_HEADER
            +
            "active_displays: []\n"
            "active_views: []\n"
            + SIMPLE_PROFILE_FOOTER;

        std::istringstream is(myProfile);
        OCIO::ConstConfigRcPtr config;
        OIIO_CHECK_NO_THROW(config = OCIO::Config::CreateFromStream(is));
        OIIO_CHECK_EQUAL(config->getNumDisplays(), 3);
        OIIO_CHECK_EQUAL(std::string(config->getDisplay(0)), std::string("sRGB_1"));
        OIIO_CHECK_EQUAL(std::string(config->getDisplay(1)), std::string("sRGB_2"));
        OIIO_CHECK_EQUAL(std::string(config->getDisplay(2)), std::string("sRGB_3"));
        OIIO_CHECK_EQUAL(std::string(config->getDefaultDisplay()), "sRGB_1");
    }

    {
        std::string myProfile = 
            SIMPLE_PROFILE_HEADER
            +
            "active_displays: [sRGB_1]\n"
            "active_views: []\n"
            + SIMPLE_PROFILE_FOOTER;

        std::istringstream is(myProfile);
        OCIO::ConstConfigRcPtr config;
        OIIO_CHECK_NO_THROW(config = OCIO::Config::CreateFromStream(is));
        OIIO_CHECK_EQUAL(config->getNumDisplays(), 1);
        OIIO_CHECK_EQUAL(std::string(config->getDisplay(0)), std::string("sRGB_1"));
        OIIO_CHECK_EQUAL(std::string(config->getDefaultDisplay()), "sRGB_1");
    }

    {
        std::string myProfile = 
            SIMPLE_PROFILE_HEADER
            +
            "active_displays: [sRGB_2, sRGB_1]\n"
            "active_views: []\n"
            + SIMPLE_PROFILE_FOOTER;

        std::istringstream is(myProfile);
        OCIO::ConstConfigRcPtr config;
        OIIO_CHECK_NO_THROW(config = OCIO::Config::CreateFromStream(is));
        OIIO_CHECK_EQUAL(config->getNumDisplays(), 2);
        OIIO_CHECK_EQUAL(std::string(config->getDisplay(0)), std::string("sRGB_2"));
        OIIO_CHECK_EQUAL(std::string(config->getDisplay(1)), std::string("sRGB_1"));
        OIIO_CHECK_EQUAL(std::string(config->getDefaultDisplay()), "sRGB_2");
    }

    {
        std::string myProfile = 
            SIMPLE_PROFILE_HEADER
            +
            "active_displays: []\n"
            "active_views: []\n"
            + SIMPLE_PROFILE_FOOTER;

        const std::string active_displays(
            std::string(OCIO::OCIO_ACTIVE_DISPLAYS_ENVVAR) + "= sRGB_3, sRGB_2");
        putenv(const_cast<char *>(active_displays.c_str()));

        std::istringstream is(myProfile);
        OCIO::ConstConfigRcPtr config;
        OIIO_CHECK_NO_THROW(config = OCIO::Config::CreateFromStream(is));
        OIIO_CHECK_EQUAL(config->getNumDisplays(), 2);
        OIIO_CHECK_EQUAL(std::string(config->getDisplay(0)), std::string("sRGB_3"));
        OIIO_CHECK_EQUAL(std::string(config->getDisplay(1)), std::string("sRGB_2"));
        OIIO_CHECK_EQUAL(std::string(config->getDefaultDisplay()), "sRGB_3");
    }

    {
        std::string myProfile = 
            SIMPLE_PROFILE_HEADER
            +
            "active_displays: [sRGB_2, sRGB_1]\n"
            "active_views: []\n"
            + SIMPLE_PROFILE_FOOTER;

        const std::string active_displays(
            std::string(OCIO::OCIO_ACTIVE_DISPLAYS_ENVVAR) + "= sRGB_3, sRGB_2");
        putenv(const_cast<char *>(active_displays.c_str()));

        std::istringstream is(myProfile);
        OCIO::ConstConfigRcPtr config;
        OIIO_CHECK_NO_THROW(config = OCIO::Config::CreateFromStream(is));
        OIIO_CHECK_EQUAL(config->getNumDisplays(), 2);
        OIIO_CHECK_EQUAL(std::string(config->getDisplay(0)), std::string("sRGB_3"));
        OIIO_CHECK_EQUAL(std::string(config->getDisplay(1)), std::string("sRGB_2"));
        OIIO_CHECK_EQUAL(std::string(config->getDefaultDisplay()), "sRGB_3");
    }

    {
        const std::string active_displays(
            std::string(OCIO::OCIO_ACTIVE_DISPLAYS_ENVVAR) + "="); // No value
        putenv(const_cast<char *>(active_displays.c_str()));

        std::string myProfile = 
            SIMPLE_PROFILE_HEADER
            +
            "active_displays: [sRGB_2, sRGB_1]\n"
            "active_views: []\n"
            + SIMPLE_PROFILE_FOOTER;

        std::istringstream is(myProfile);
        OCIO::ConstConfigRcPtr config;
        OIIO_CHECK_NO_THROW(config = OCIO::Config::CreateFromStream(is));
        OIIO_CHECK_EQUAL(config->getNumDisplays(), 2);
        OIIO_CHECK_EQUAL(std::string(config->getDisplay(0)), std::string("sRGB_2"));
        OIIO_CHECK_EQUAL(std::string(config->getDisplay(1)), std::string("sRGB_1"));
        OIIO_CHECK_EQUAL(std::string(config->getDefaultDisplay()), "sRGB_2");
    }

    {
        const std::string active_displays(
            std::string(OCIO::OCIO_ACTIVE_DISPLAYS_ENVVAR) + "= "); // No value, but misleading space
        putenv(const_cast<char *>(active_displays.c_str()));

        std::string myProfile = 
            SIMPLE_PROFILE_HEADER
            +
            "active_displays: [sRGB_2, sRGB_1]\n"
            "active_views: []\n"
            + SIMPLE_PROFILE_FOOTER;

        std::istringstream is(myProfile);
        OCIO::ConstConfigRcPtr config;
        OIIO_CHECK_NO_THROW(config = OCIO::Config::CreateFromStream(is));
        OIIO_CHECK_EQUAL(config->getNumDisplays(), 2);
        OIIO_CHECK_EQUAL(std::string(config->getDisplay(0)), std::string("sRGB_2"));
        OIIO_CHECK_EQUAL(std::string(config->getDisplay(1)), std::string("sRGB_1"));
        OIIO_CHECK_EQUAL(std::string(config->getDefaultDisplay()), "sRGB_2");
    }
}

OIIO_ADD_TEST(Config, view)
{
    static const std::string SIMPLE_PROFILE_HEADER =
        "ocio_profile_version: 1\n"
        "\n"
        "search_path: luts\n"
        "strictparsing: true\n"
        "luma: [0.2126, 0.7152, 0.0722]\n"
        "\n"
        "roles:\n"
        "  default: raw\n"
        "  scene_linear: lnh\n"
        "\n"
        "displays:\n"
        "  sRGB_1:\n"
        "    - !<View> {name: View_1, colorspace: raw}\n"
        "    - !<View> {name: View_2, colorspace: raw}\n"
        "  sRGB_2:\n"
        "    - !<View> {name: View_2, colorspace: raw}\n"
        "    - !<View> {name: View_3, colorspace: raw}\n"
        "  sRGB_3:\n"
        "    - !<View> {name: View_3, colorspace: raw}\n"
        "    - !<View> {name: View_1, colorspace: raw}\n"
        "\n";

    static const std::string SIMPLE_PROFILE_FOOTER =
        "\n"
        "colorspaces:\n"
        "  - !<ColorSpace>\n"
        "    name: raw\n"
        "    family: \"\"\n"
        "    equalitygroup: \"\"\n"
        "    bitdepth: unknown\n"
        "    isdata: false\n"
        "    allocation: uniform\n"
        "\n"
        "  - !<ColorSpace>\n"
        "    name: lnh\n"
        "    family: \"\"\n"
        "    equalitygroup: \"\"\n"
        "    bitdepth: unknown\n"
        "    isdata: false\n"
        "    allocation: uniform\n";

    {
        std::string myProfile = 
            SIMPLE_PROFILE_HEADER
            +
            "active_displays: []\n"
            "active_views: []\n"
            + SIMPLE_PROFILE_FOOTER;

        std::istringstream is(myProfile);
        OCIO::ConstConfigRcPtr config;
        OIIO_CHECK_NO_THROW(config = OCIO::Config::CreateFromStream(is));
        OIIO_CHECK_EQUAL(std::string(config->getDefaultView("sRGB_1")), "View_1");
        OIIO_CHECK_EQUAL(std::string(config->getView("sRGB_1", 0)), "View_1");
        OIIO_CHECK_EQUAL(std::string(config->getView("sRGB_1", 1)), "View_2");
        OIIO_CHECK_EQUAL(std::string(config->getDefaultView("sRGB_2")), "View_2");
        OIIO_CHECK_EQUAL(std::string(config->getView("sRGB_2", 0)), "View_2");
        OIIO_CHECK_EQUAL(std::string(config->getView("sRGB_2", 1)), "View_3");
        OIIO_CHECK_EQUAL(std::string(config->getDefaultView("sRGB_3")), "View_3");
        OIIO_CHECK_EQUAL(std::string(config->getView("sRGB_3", 0)), "View_3");
        OIIO_CHECK_EQUAL(std::string(config->getView("sRGB_3", 1)), "View_1");
    }

    {
        std::string myProfile = 
            SIMPLE_PROFILE_HEADER
            +
            "active_displays: []\n"
            "active_views: [View_3]\n"
            + SIMPLE_PROFILE_FOOTER;

        std::istringstream is(myProfile);
        OCIO::ConstConfigRcPtr config;
        OIIO_CHECK_NO_THROW(config = OCIO::Config::CreateFromStream(is));
        OIIO_CHECK_EQUAL(std::string(config->getDefaultView("sRGB_1")), "View_1");
        OIIO_CHECK_EQUAL(std::string(config->getView("sRGB_1", 0)), "View_1");
        OIIO_CHECK_EQUAL(std::string(config->getView("sRGB_1", 1)), "View_2");
        OIIO_CHECK_EQUAL(std::string(config->getDefaultView("sRGB_2")), "View_3");
        OIIO_CHECK_EQUAL(std::string(config->getView("sRGB_2", 0)), "View_2");
        OIIO_CHECK_EQUAL(std::string(config->getView("sRGB_2", 1)), "View_3");
        OIIO_CHECK_EQUAL(std::string(config->getDefaultView("sRGB_3")), "View_3");
        OIIO_CHECK_EQUAL(std::string(config->getView("sRGB_3", 0)), "View_3");
        OIIO_CHECK_EQUAL(std::string(config->getView("sRGB_3", 1)), "View_1");
    }

    {
        std::string myProfile = 
            SIMPLE_PROFILE_HEADER
            +
            "active_displays: []\n"
            "active_views: [View_3, View_2, View_1]\n"
            + SIMPLE_PROFILE_FOOTER;

        std::istringstream is(myProfile);
        OCIO::ConstConfigRcPtr config;
        OIIO_CHECK_NO_THROW(config = OCIO::Config::CreateFromStream(is));
        OIIO_CHECK_EQUAL(std::string(config->getDefaultView("sRGB_1")), "View_2");
        OIIO_CHECK_EQUAL(std::string(config->getView("sRGB_1", 0)), "View_1");
        OIIO_CHECK_EQUAL(std::string(config->getView("sRGB_1", 1)), "View_2");
        OIIO_CHECK_EQUAL(std::string(config->getDefaultView("sRGB_2")), "View_3");
        OIIO_CHECK_EQUAL(std::string(config->getView("sRGB_2", 0)), "View_2");
        OIIO_CHECK_EQUAL(std::string(config->getView("sRGB_2", 1)), "View_3");
        OIIO_CHECK_EQUAL(std::string(config->getDefaultView("sRGB_3")), "View_3");
        OIIO_CHECK_EQUAL(std::string(config->getView("sRGB_3", 0)), "View_3");
        OIIO_CHECK_EQUAL(std::string(config->getView("sRGB_3", 1)), "View_1");
    }

    {
        std::string myProfile = 
            SIMPLE_PROFILE_HEADER
            +
            "active_displays: []\n"
            "active_views: []\n"
            + SIMPLE_PROFILE_FOOTER;

        const std::string active_displays(
            std::string(OCIO::OCIO_ACTIVE_VIEWS_ENVVAR) + "= View_3, View_2");
        putenv(const_cast<char *>(active_displays.c_str()));

        std::istringstream is(myProfile);
        OCIO::ConstConfigRcPtr config;
        OIIO_CHECK_NO_THROW(config = OCIO::Config::CreateFromStream(is));
        OIIO_CHECK_EQUAL(std::string(config->getDefaultView("sRGB_1")), "View_2");
        OIIO_CHECK_EQUAL(std::string(config->getView("sRGB_1", 0)), "View_1");
        OIIO_CHECK_EQUAL(std::string(config->getView("sRGB_1", 1)), "View_2");
        OIIO_CHECK_EQUAL(std::string(config->getDefaultView("sRGB_2")), "View_3");
        OIIO_CHECK_EQUAL(std::string(config->getView("sRGB_2", 0)), "View_2");
        OIIO_CHECK_EQUAL(std::string(config->getView("sRGB_2", 1)), "View_3");
        OIIO_CHECK_EQUAL(std::string(config->getDefaultView("sRGB_3")), "View_3");
        OIIO_CHECK_EQUAL(std::string(config->getView("sRGB_3", 0)), "View_3");
        OIIO_CHECK_EQUAL(std::string(config->getView("sRGB_3", 1)), "View_1");
    }

    {
        std::string myProfile = 
            SIMPLE_PROFILE_HEADER
            +
            "active_displays: []\n"
            "active_views: []\n"
            + SIMPLE_PROFILE_FOOTER;

        const std::string active_displays(
            std::string(OCIO::OCIO_ACTIVE_VIEWS_ENVVAR) + "="); // No value.
        putenv(const_cast<char *>(active_displays.c_str()));

        std::istringstream is(myProfile);
        OCIO::ConstConfigRcPtr config;
        OIIO_CHECK_NO_THROW(config = OCIO::Config::CreateFromStream(is));
        OIIO_CHECK_EQUAL(std::string(config->getDefaultView("sRGB_1")), "View_1");
        OIIO_CHECK_EQUAL(std::string(config->getView("sRGB_1", 0)), "View_1");
        OIIO_CHECK_EQUAL(std::string(config->getView("sRGB_1", 1)), "View_2");
        OIIO_CHECK_EQUAL(std::string(config->getDefaultView("sRGB_2")), "View_2");
        OIIO_CHECK_EQUAL(std::string(config->getView("sRGB_2", 0)), "View_2");
        OIIO_CHECK_EQUAL(std::string(config->getView("sRGB_2", 1)), "View_3");
        OIIO_CHECK_EQUAL(std::string(config->getDefaultView("sRGB_3")), "View_3");
        OIIO_CHECK_EQUAL(std::string(config->getView("sRGB_3", 0)), "View_3");
        OIIO_CHECK_EQUAL(std::string(config->getView("sRGB_3", 1)), "View_1");
    }

    {
        std::string myProfile = 
            SIMPLE_PROFILE_HEADER
            +
            "active_displays: []\n"
            "active_views: []\n"
            + SIMPLE_PROFILE_FOOTER;

        const std::string active_displays(
            std::string(OCIO::OCIO_ACTIVE_VIEWS_ENVVAR) + "= "); // No value, but misleading space
        putenv(const_cast<char *>(active_displays.c_str()));

        std::istringstream is(myProfile);
        OCIO::ConstConfigRcPtr config;
        OIIO_CHECK_NO_THROW(config = OCIO::Config::CreateFromStream(is));
        OIIO_CHECK_EQUAL(std::string(config->getDefaultView("sRGB_1")), "View_1");
        OIIO_CHECK_EQUAL(std::string(config->getView("sRGB_1", 0)), "View_1");
        OIIO_CHECK_EQUAL(std::string(config->getView("sRGB_1", 1)), "View_2");
        OIIO_CHECK_EQUAL(std::string(config->getDefaultView("sRGB_2")), "View_2");
        OIIO_CHECK_EQUAL(std::string(config->getView("sRGB_2", 0)), "View_2");
        OIIO_CHECK_EQUAL(std::string(config->getView("sRGB_2", 1)), "View_3");
        OIIO_CHECK_EQUAL(std::string(config->getDefaultView("sRGB_3")), "View_3");
        OIIO_CHECK_EQUAL(std::string(config->getView("sRGB_3", 0)), "View_3");
        OIIO_CHECK_EQUAL(std::string(config->getView("sRGB_3", 1)), "View_1");
    }
}

#endif // OCIO_UNIT_TEST
