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

PublicHeader:   None
Filename    :   OVR_Profile.cpp
Content     :   Structs and functions for loading and storing device profile settings
Created     :   February 14, 2013
Notes       :
   
   Profiles are used to store per-user settings that can be transferred and used
   across multiple applications.  For example, player IPD can be configured once 
   and reused for a unified experience across games.  Configuration and saving of profiles
   can be accomplished in game via the Profile API or by the official Oculus Configuration
   Utility.

Copyright   :   Copyright 2014 Oculus VR, LLC All Rights reserved.

Licensed under the Oculus VR Rift SDK License Version 3.2 (the "License"); 
you may not use the Oculus VR Rift SDK except in compliance with the License, 
which is provided at the time of installation or download, or which 
otherwise accompanies this software in either electronic or hard copy form.

You may obtain a copy of the License at

http://www.oculusvr.com/licenses/LICENSE-3.2 

Unless required by applicable law or agreed to in writing, the Oculus VR SDK 
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

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

#include "OVR_Profile.h"
#include "OVR_JSON.h"
#include "Kernel/OVR_SysFile.h"
#include "Kernel/OVR_Allocator.h"
#include "OVR_Stereo.h"

#ifdef OVR_OS_WIN32
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <Shlobj.h>
#elif defined(OVR_OS_MS) // Other Microsoft OSs
// Nothing, thanks.
#else
#include <dirent.h>
#include <sys/stat.h>

#ifdef OVR_OS_LINUX
#include <unistd.h>
#include <pwd.h>
#endif

#endif

#define PROFILE_VERSION 2.0
#define MAX_PROFILE_MAJOR_VERSION 2
#define MAX_DEVICE_PROFILE_MAJOR_VERSION 1


namespace OVR {


//-----------------------------------------------------------------------------
// ProfileDeviceKey

ProfileDeviceKey::ProfileDeviceKey(const HMDInfo* info) :
    Valid(false)
{
    if (info)
    {
        PrintedSerial = info->PrintedSerial;
        ProductName = SanitizeProductName(info->ProductName);
        ProductId = info->ProductId;
        HmdType = info->HmdType;

        if (ProductId != 0)
        {
            Valid = true;
        }
    }
    else
    {
        ProductId = 0;
        HmdType = HmdType_None;
    }
}

String ProfileDeviceKey::SanitizeProductName(String productName)
{
    String result;

    if (!productName.IsEmpty())
    {
        const char* product_name = productName.ToCStr();

        // First strip off "Oculus"
        const char* oculus = strstr(product_name, "Oculus ");
        if (oculus)
        {
            product_name = oculus + OVR_strlen("Oculus ");
        }

        // And remove spaces from the name
        for (const char* s = product_name; *s != 0; s++)
        {
            if (*s != ' ')
            {
                result.AppendChar(*s);
            }
        }
    }

    return result;
}



//-----------------------------------------------------------------------------
// Returns the pathname of the JSON file containing the stored profiles
String GetBaseOVRPath(bool create_dir)
{
    String path;

#if defined(OVR_OS_WIN32)

    TCHAR data_path[MAX_PATH];
    SHGetFolderPath(0, CSIDL_LOCAL_APPDATA, NULL, 0, data_path);
    path = String(data_path);
    
    path += "/Oculus";

    if (create_dir)
    {   // Create the Oculus directory if it doesn't exist
        WCHAR wpath[128];
        OVR::UTF8Util::DecodeString(wpath, path.ToCStr());

        DWORD attrib = GetFileAttributes(wpath);
        bool exists = attrib != INVALID_FILE_ATTRIBUTES && (attrib & FILE_ATTRIBUTE_DIRECTORY);
        if (!exists)
        {   
            CreateDirectory(wpath, NULL);
        }
    }

#elif defined(OVR_OS_OS) // Other Microsoft OSs

    // TODO: figure this out.
    OVR_UNUSED ( create_dir );
    path = "";
        
#elif defined(OVR_OS_MAC)

    const char* home = getenv("HOME");
    path = home;
    path += "/Library/Preferences/Oculus";

    if (create_dir)
    {   // Create the Oculus directory if it doesn't exist
        DIR* dir = opendir(path);
        if (dir == NULL)
        {
            mkdir(path, S_IRWXU | S_IRWXG | S_IRWXO);
        }
        else
        {
            closedir(dir);
        }
    }

#else

    const char* home = getenv("HOME");
    path = home;
    path += "/.config/Oculus";

    if (create_dir)
    {   // Create the Oculus directory if it doesn't exist
        DIR* dir = opendir(path);
        if (dir == NULL)
        {
            mkdir(path, S_IRWXU | S_IRWXG | S_IRWXO);
        }
        else
        {
            closedir(dir);
        }
    }

#endif

    return path;
}

String ProfileManager::GetProfilePath()
{
    return BasePath + "/ProfileDB.json";
}

static JSON* FindTaggedData(JSON* data, const char** tag_names, const char** qtags, int num_qtags)
{
    if (data == NULL || !(data->Name == "TaggedData") || data->Type != JSON_Array)
        return NULL;

    JSON* tagged_item = data->GetFirstItem();
    while (tagged_item)
    {
        JSON* tags = tagged_item->GetItemByName("tags");
        if (tags->Type == JSON_Array && num_qtags == tags->GetArraySize())
        {   // Check for a full tag match on each item
            int num_matches = 0;
            
            for (int k=0; k<num_qtags; k++)
            {
                JSON* tag = tags->GetFirstItem();
                while (tag)
                {
                    JSON* tagval = tag->GetFirstItem();
                    if (tagval && tagval->Name == tag_names[k])
                    {
                        if (tagval->Value == qtags[k])
                            num_matches++;
                        break;
                    }
                    tag = tags->GetNextItem(tag);
                }
            }

            // if all tags were matched then copy the values into this Profile
            if (num_matches == num_qtags)
            {
                JSON* vals = tagged_item->GetItemByName("vals");
                return vals;
            }
        }

        tagged_item = data->GetNextItem(tagged_item);
    }

    return NULL;
}

static void FilterTaggedData(JSON* data, const char* tag_name, const char* qtag, Array<JSON*>& items)
{
    if (data == NULL || !(data->Name == "TaggedData") || data->Type != JSON_Array)
        return;

    JSON* tagged_item = data->GetFirstItem();
    while (tagged_item)
    {
        JSON* tags = tagged_item->GetItemByName("tags");
        if (tags->Type == JSON_Array)
        {   // Check for a tag match on the requested tag
            
            JSON* tag = tags->GetFirstItem();
            while (tag)
            {
                JSON* tagval = tag->GetFirstItem();
                if (tagval && tagval->Name == tag_name)
                {
                    if (tagval->Value == qtag)
                    {   // Add this item to the output list
                        items.PushBack(tagged_item);
                    }
                    break;
                }
                tag = tags->GetNextItem(tag);
            }
        }

        tagged_item = data->GetNextItem(tagged_item);
    }
}


//-----------------------------------------------------------------------------
// ***** ProfileManager

template<> ProfileManager* OVR::SystemSingletonBase<ProfileManager>::SlowGetInstance()
{
    static OVR::Lock lock;
    OVR::Lock::Locker locker(&lock);
    if (!SingletonInstance) SingletonInstance = new ProfileManager(true);
    return SingletonInstance;
}

ProfileManager::ProfileManager(bool sys_register) :
    Changed(false)
{
    // Attempt to get the base path automatically, but this may fail
    BasePath = GetBaseOVRPath(false);

    if (sys_register)
        PushDestroyCallbacks();
}

ProfileManager::~ProfileManager()
{
    ClearProfileData();
}

void ProfileManager::OnSystemDestroy()
{
    delete this;
}

// In the service process it is important to set the base path because this cannot be detected automatically
void ProfileManager::SetBasePath(String basePath)
{
    if (basePath != BasePath)
    {
        BasePath = basePath;
        LoadCache(false);
    }
}

// Clear the local profile cache
void ProfileManager::ClearProfileData()
{
    Lock::Locker lockScope(&ProfileLock);

    ProfileCache.Clear();
    Changed = false;
}

// Serializes the profiles to disk.
void ProfileManager::Save()
{
    Lock::Locker lockScope(&ProfileLock);

    if (ProfileCache == NULL)
        return;

    // Save the profile to disk
    BasePath = GetBaseOVRPath(true);  // create the base directory if it doesn't exist
    String path = GetProfilePath();
    ProfileCache->Save(path);
    Changed = false;
}

// Returns a profile with all system default values
Profile* ProfileManager::GetDefaultProfile(HmdTypeEnum device)
{
    // In the absence of any data, set some reasonable profile defaults.
    // However, this is not future proof and developers should still
    // provide reasonable default values for queried fields.
    
    // Biometric data
    Profile* profile = CreateProfile();
    profile->SetValue(OVR_KEY_USER,               "default");
    profile->SetValue(OVR_KEY_NAME,               "Default");
    profile->SetValue(OVR_KEY_GENDER,             OVR_DEFAULT_GENDER);
    profile->SetFloatValue(OVR_KEY_PLAYER_HEIGHT, OVR_DEFAULT_PLAYER_HEIGHT);
    profile->SetFloatValue(OVR_KEY_EYE_HEIGHT,    OVR_DEFAULT_EYE_HEIGHT);
    profile->SetFloatValue(OVR_KEY_IPD,           OVR_DEFAULT_IPD);
    float half_ipd[2] = { OVR_DEFAULT_IPD / 2, OVR_DEFAULT_IPD / 2 };
    profile->SetFloatValues(OVR_KEY_EYE_TO_NOSE_DISTANCE, half_ipd, 2);
    float dist[2] = {OVR_DEFAULT_NECK_TO_EYE_HORIZONTAL, OVR_DEFAULT_NECK_TO_EYE_VERTICAL};
    profile->SetFloatValues(OVR_KEY_NECK_TO_EYE_DISTANCE, dist, 2);
    
    // Device specific data
    if (device != HmdType_None)
    {
        if (device == HmdType_CrystalCoveProto || device == HmdType_DK2)
        {
            profile->SetValue("EyeCup", "A");
            profile->SetIntValue(OVR_KEY_EYE_RELIEF_DIAL, OVR_DEFAULT_EYE_RELIEF_DIAL);

            // TODO: These defaults are a little bogus and designed for continuity with 0.3
            // eye-relief values.  We need better measurement-based numbers in future releases
            float max_eye_plate[2] = { 0.01965f + 0.018f, 0.01965f + 0.018f };
            profile->SetFloatValues(OVR_KEY_MAX_EYE_TO_PLATE_DISTANCE, max_eye_plate, 2);
        }
        else
        {   // DK1 and DKHD variants
            profile->SetValue("EyeCup", "A");
            profile->SetIntValue(OVR_KEY_EYE_RELIEF_DIAL, OVR_DEFAULT_EYE_RELIEF_DIAL);

            // TODO: These defaults are a little bogus and designed for continuity with 0.3
            // DK1 distortion.  We need better measurement-based numbers in future releases
            float max_eye_plate[2] = { 0.02357f + 0.017f, 0.02357f + 0.017f };
            profile->SetFloatValues(OVR_KEY_MAX_EYE_TO_PLATE_DISTANCE, max_eye_plate, 2);
        }
    }

    return profile;
}

//------------------------------------------------------------------------------
void ProfileManager::Read()
{
    LoadCache(false);
}

// Populates the local profile cache.  This occurs on the first access of the profile
// data.  All profile operations are performed against the local cache until the
// ProfileManager is released or goes out of scope at which time the cache is serialized
// to disk.
void ProfileManager::LoadCache(bool create)
{
    Lock::Locker lockScope(&ProfileLock);

    ClearProfileData();

    String path = GetProfilePath();

    Ptr<JSON> root = *JSON::Load(path);
    if (root == NULL)
    {   
        path = BasePath + "/Profiles.json";  // look for legacy profile
        root = *JSON::Load(path);
        
        if (root == NULL)
        {
            if (create)
            {   // Generate a skeleton profile database
                root = *JSON::CreateObject();
                root->AddNumberItem("Oculus Profile Version", 2.0);
                root->AddItem("Users", JSON::CreateArray());
                root->AddItem("TaggedData", JSON::CreateArray());
                ProfileCache = root;
            }
            
            return;
        }

        // Verify the legacy version
        JSON* version_item = root->GetFirstItem();
        if (version_item->Name == "Oculus Profile Version")
        {
            int major = atoi(version_item->Value.ToCStr());
            if (major != 1)
                return;   // don't use the file on unsupported major version number
        }
        else
        {
            return;      // invalid file
        }

        // Convert the legacy format to the new database format
        LoadV1Profiles(root);
    }
    else
    {
        // Verify the file format and version
        JSON* version_item = root->GetFirstItem();
        if (version_item && version_item->Name == "Oculus Profile Version")
        {
            int major = atoi(version_item->Value.ToCStr());
            if (major != 2)
                return;   // don't use the file on unsupported major version number
        }
        else
        {
            return;       // invalid file 
        }

        ProfileCache = root;   // store the database contents for traversal
    }
}

void ProfileManager::LoadV1Profiles(JSON* v1)
{
    JSON* item0 = v1->GetFirstItem();
    JSON* item1 = v1->GetNextItem(item0);
    JSON* item2 = v1->GetNextItem(item1);

    OVR_ASSERT(item1 && item2);
    if(!item1 || !item2)
        return;

    // Create the new profile database
    Ptr<JSON> root = *JSON::CreateObject();
    root->AddNumberItem("Oculus Profile Version", 2.0);
    root->AddItem("Users", JSON::CreateArray());
    root->AddItem("TaggedData", JSON::CreateArray());
    ProfileCache = root;

    const char* default_dk1_user = item1->Value;
    
    // Read the number of profiles
    int   profileCount = (int)item2->dValue;
    JSON* profileItem  = item2;

    for (int p=0; p<profileCount; p++)
    {
        profileItem = root->GetNextItem(profileItem);
        if (profileItem == NULL)
            break;

        if (profileItem->Name == "Profile")
        {
            // Read the required Name field
            const char* profileName;
            JSON* item = profileItem->GetFirstItem();
        
            if (item && (item->Name == "Name"))
            {   
                profileName = item->Value;
            }
            else
            {
                return;   // invalid field
            }
            
            // Read the user profile fields
            if (CreateUser(profileName, profileName))
            {
                const char* tag_names[2] = {"User", "Product"};
                const char* tags[2];
                tags[0] = profileName;

                Ptr<Profile> user_profile = *CreateProfile();
                user_profile->SetValue(OVR_KEY_NAME, profileName);

                float neckeye[2] = { 0, 0 };

                item = profileItem->GetNextItem(item);
                while (item)
                {
                    if (item->Type != JSON_Object)
                    {
                        if (item->Name == OVR_KEY_PLAYER_HEIGHT)
                        {   // Add an explicit eye height

                        }
                        if (item->Name == "NeckEyeHori")
                            neckeye[0] = (float)item->dValue;
                        else if (item->Name == "NeckEyeVert")
                            neckeye[1] = (float)item->dValue;
                        else 
                            user_profile->SetValue(item);
                    }
                    else
                    {   
                        // Add the user/device tag values
                        const char* device_name = item->Name.ToCStr();
                        Ptr<Profile> device_profile = *CreateProfile();

                        JSON* device_item = item->GetFirstItem();
                        while (device_item)
                        {
                            device_profile->SetValue(device_item);
                            device_item = item->GetNextItem(device_item);
                        }

                        tags[1] = device_name;
                        SetTaggedProfile(tag_names, tags, 2, device_profile);
                    }

                    item = profileItem->GetNextItem(item);
                }

                // Add an explicit eye-height field
                float player_height = user_profile->GetFloatValue(OVR_KEY_PLAYER_HEIGHT,
                                                                  OVR_DEFAULT_PLAYER_HEIGHT);
                if (player_height > 0)
                {
                    char gender[16];
                    user_profile->GetValue(OVR_KEY_GENDER, gender, 16);
        
                    const float EYE_TO_HEADTOP_RATIO =   0.44538f;
                    const float MALE_AVG_HEAD_HEIGHT =   0.232f;
                    const float FEMALE_AVG_HEAD_HEIGHT = 0.218f;
     
                    // compute distance from top of skull to the eye
                    float head_height;
                    if (OVR_strcmp(gender, "Female") == 0)
                        head_height = FEMALE_AVG_HEAD_HEIGHT;
                    else
                        head_height = MALE_AVG_HEAD_HEIGHT;

                    float skull = EYE_TO_HEADTOP_RATIO * head_height;
                    float eye_height = player_height - skull;

                    user_profile->SetFloatValue(OVR_KEY_EYE_HEIGHT, eye_height);
                }

                // Convert NeckEye values to an array
                if (neckeye[0] > 0 && neckeye[1] > 0)
                    user_profile->SetFloatValues(OVR_KEY_NECK_TO_EYE_DISTANCE, neckeye, 2);

                // Add the user tag values
                SetTaggedProfile(tag_names, tags, 1, user_profile);
            }
        }
    }

    // since V1 profiles were only for DK1, the assign the user to all DK1's
    const char* tag_names[1] = { "Product" };
    const char* tags[1] = { "RiftDK1" };
    Ptr<Profile> product_profile = *CreateProfile();
    product_profile->SetValue("DefaultUser", default_dk1_user);
    SetTaggedProfile(tag_names, tags, 1, product_profile);
}

// Returns the number of stored profiles for this device type
int ProfileManager::GetUserCount()
{
    Lock::Locker lockScope(&ProfileLock);

    if (ProfileCache == NULL)
    {   // Load the cache
        LoadCache(false);
        if (ProfileCache == NULL)
            return 0;
    }

    JSON* users = ProfileCache->GetItemByName("Users");
    if (users == NULL)
        return 0;

    return users->GetItemCount();
}

bool ProfileManager::CreateUser(const char* user, const char* name)
{
    Lock::Locker lockScope(&ProfileLock);

    if (ProfileCache == NULL)
    {   // Load the cache
        LoadCache(true);
        if (ProfileCache == NULL)
            return false;
    }

    JSON* users = ProfileCache->GetItemByName("Users");
    if (users == NULL)
    {   // Generate the User section
        users = JSON::CreateArray();
        ProfileCache->AddItem("Users", users);
//TODO: Insert this before the TaggedData
    }

    // Search for the pre-existence of this user
    JSON* user_item = users->GetFirstItem();
    int index = 0;
    while (user_item)
    {
        JSON* userid = user_item->GetItemByName("User");
        int compare = OVR_strcmp(user, userid->Value);
        if (compare == 0)
        {   // The user already exists so simply update the fields
            JSON* name_item = user_item->GetItemByName("Name");
            if (name_item && OVR_strcmp(name, name_item->Value) != 0)
            {
                name_item->Value = name;
                Changed = true;
            }
            return true;
        }
        else if (compare < 0)
        {   // A new user should be placed before this item
            break;
        }
        
        user_item = users->GetNextItem(user_item);
        index++;
    }

    // Create and fill the user struct
    JSON* new_user = JSON::CreateObject();
    new_user->AddStringItem(OVR_KEY_USER, user);
    new_user->AddStringItem(OVR_KEY_NAME, name);
    // user_item->AddStringItem("Password", password);

    if (user_item == NULL)
        users->AddArrayElement(new_user);
    else
        users->InsertArrayElement(index, new_user);

    Changed = true;
    return true;
}

bool ProfileManager::HasUser(const char* user)
{
	Lock::Locker lockScope(&ProfileLock);

	if (ProfileCache == NULL)
	{   // Load the cache
		LoadCache(false);
		if (ProfileCache == NULL)
			return false;
	}

	JSON* users = ProfileCache->GetItemByName("Users");
	if (users == NULL)
		return false;

	// Remove this user from the User table
	JSON* user_item = users->GetFirstItem();
	while (user_item)
	{
		JSON* userid = user_item->GetItemByName("User");
		if (OVR_strcmp(user, userid->Value) == 0)
		{   
			return true;
		}

		user_item = users->GetNextItem(user_item);
	}

	return false;
}

// Returns the user id of a specific user in the list.  The returned 
// memory is locally allocated and should not be stored or deleted.  Returns NULL
// if the index is invalid
const char* ProfileManager::GetUser(unsigned int index)
{
    Lock::Locker lockScope(&ProfileLock);

    if (ProfileCache == NULL)
    {   // Load the cache
        LoadCache(false);
        if (ProfileCache == NULL)
            return NULL;
    }

    JSON* users = ProfileCache->GetItemByName("Users");
    
    if (users && index < users->GetItemCount())
    {
        JSON* user_item = users->GetItemByIndex(index);
        if (user_item)
        {
            JSON* user = user_item->GetFirstItem();
            if (user)
            {
                JSON* userid = user_item->GetItemByName(OVR_KEY_USER);
                if (userid)
                    return userid->Value.ToCStr();
            }
        }
    }
    

    return NULL;
}

bool ProfileManager::RemoveUser(const char* user)
{
    Lock::Locker lockScope(&ProfileLock);

    if (ProfileCache == NULL)
    {   // Load the cache
        LoadCache(false);
        if (ProfileCache == NULL)
            return true;
    }

    JSON* users = ProfileCache->GetItemByName("Users");
    if (users == NULL)
        return true;

    // Remove this user from the User table
    JSON* user_item = users->GetFirstItem();
    while (user_item)
    {
        JSON* userid = user_item->GetItemByName("User");
        if (OVR_strcmp(user, userid->Value) == 0)
        {   // Delete the user entry
            user_item->RemoveNode();
            user_item->Release();
            Changed = true;
            break;
        }
        
        user_item = users->GetNextItem(user_item);
    }

    // Now remove all data entries with this user tag
    JSON* tagged_data = ProfileCache->GetItemByName("TaggedData");
    Array<JSON*> user_items;
    FilterTaggedData(tagged_data, "User", user, user_items);
    for (unsigned int i=0; i<user_items.GetSize(); i++)
    {
        user_items[i]->RemoveNode();
        user_items[i]->Release();
        Changed = true;
    }
 
    return Changed;
}

Profile* ProfileManager::CreateProfile()
{
    Profile* profile = new Profile(BasePath);
    return profile;
}

const char* ProfileManager::GetDefaultUser(const ProfileDeviceKey& deviceKey)
{
    const char* product_str = deviceKey.ProductName.IsEmpty() ? NULL : deviceKey.ProductName.ToCStr();
    const char* serial_str = deviceKey.PrintedSerial.IsEmpty() ? NULL : deviceKey.PrintedSerial.ToCStr();

    return GetDefaultUser(product_str, serial_str);
}

// Returns the name of the profile that is marked as the current default user.
const char* ProfileManager::GetDefaultUser(const char* product, const char* serial)
{
    const char* tag_names[2] = {"Product", "Serial"};
    const char* tags[2];

    if (product && serial)
    {
        tags[0] = product;
        tags[1] = serial;
        // Look for a default user on this specific device
        Ptr<Profile> p = *GetTaggedProfile(tag_names, tags, 2);
        if (p == NULL)
        {   // Look for a default user on this product
            p = *GetTaggedProfile(tag_names, tags, 1);
        }

        if (p)
        {   
            const char* user = p->GetValue("DefaultUser");
            if (user != NULL && user[0] != 0)
            {
                TempBuff = user;
                return TempBuff.ToCStr();
            }
        }
    }

    return NULL;
}

//-----------------------------------------------------------------------------
bool ProfileManager::SetDefaultUser(const ProfileDeviceKey& deviceKey, const char* user)
{
    const char* tag_names[2] = {"Product", "Serial"};
    const char* tags[2];

    const char* product_str = deviceKey.ProductName.IsEmpty() ? NULL : deviceKey.ProductName.ToCStr();
    const char* serial_str = deviceKey.PrintedSerial.IsEmpty() ? NULL : deviceKey.PrintedSerial.ToCStr();

    if (product_str && serial_str)
    {
        tags[0] = product_str;
        tags[1] = serial_str;

        Ptr<Profile> p = *CreateProfile();
        p->SetValue("DefaultUser", user);
        return SetTaggedProfile(tag_names, tags, 2, p);
    }

    return false;
}

//-----------------------------------------------------------------------------
Profile* ProfileManager::GetTaggedProfile(const char** tag_names, const char** tags, int num_tags)
{
    Lock::Locker lockScope(&ProfileLock);

    if (ProfileCache == NULL)
    {   // Load the cache
        LoadCache(false);
        if (ProfileCache == NULL)
            return NULL;
    }

    JSON* tagged_data = ProfileCache->GetItemByName("TaggedData");
    OVR_ASSERT(tagged_data);
    if (tagged_data == NULL)
        return NULL;
    
    Profile* profile = new Profile(BasePath);
    
    JSON* vals = FindTaggedData(tagged_data, tag_names, tags, num_tags);
    if (vals)
    {   
        JSON* item = vals->GetFirstItem();
        while (item)
        {
            //printf("Add %s, %s\n", item->Name.ToCStr(), item->Value.ToCStr());
            //profile->Settings.Set(item->Name, item->Value);
            profile->SetValue(item);
            item = vals->GetNextItem(item);
        }

        return profile;
    }
    else
    {
        profile->Release();
        return NULL;
    }
}

//-----------------------------------------------------------------------------
bool ProfileManager::SetTaggedProfile(const char** tag_names, const char** tags, int num_tags, Profile* profile)
{
    Lock::Locker lockScope(&ProfileLock);

    if (ProfileCache == NULL)
    {   // Load the cache
        LoadCache(true);
        if (ProfileCache == NULL)
            return false;  // TODO: Generate a new profile DB
    }

    JSON* tagged_data = ProfileCache->GetItemByName("TaggedData");
    OVR_ASSERT(tagged_data);
    if (tagged_data == NULL)
        return false;

    // Get the cached tagged data section
    JSON* vals = FindTaggedData(tagged_data, tag_names, tags, num_tags);
    if (vals == NULL)
    {  
        JSON* tagged_item = JSON::CreateObject();
        JSON* taglist = JSON::CreateArray();
        for (int i=0; i<num_tags; i++)
        {
            JSON* k = JSON::CreateObject();
            k->AddStringItem(tag_names[i], tags[i]);
            taglist->AddArrayElement(k);
        }

        vals = JSON::CreateObject();
        
        tagged_item->AddItem("tags", taglist);
        tagged_item->AddItem("vals", vals);
        tagged_data->AddArrayElement(tagged_item);
    }

    // Now add or update each profile setting in cache
    for (unsigned int i=0; i<profile->Values.GetSize(); i++)
    {
        JSON* value = profile->Values[i];
        
        bool found = false;
        JSON* item = vals->GetFirstItem();
        while (item)
        {
            if (value->Name == item->Name)
            {
                // Don't allow a pre-existing type to be overridden
                OVR_ASSERT(value->Type == item->Type);

                if (value->Type == item->Type)
                {   // Check for the same value
                    if (value->Type == JSON_Array)
                    {   // Update each array item
                        if (item->GetArraySize() == value->GetArraySize())
                        {   // Update each value (assumed to be basic types and not array of objects)
                            JSON* value_element = value->GetFirstItem();
                            JSON* item_element = item->GetFirstItem();
                            while (item_element && value_element)
                            {
                                if (value_element->Type == JSON_String)
                                {
                                    if (item_element->Value != value_element->Value)
                                    {   // Overwrite the changed value and mark for file update
                                        item_element->Value = value_element->Value;
                                        Changed = true;
                                    }
                                }
                                else {
                                    if (item_element->dValue != value_element->dValue)
                                    {   // Overwrite the changed value and mark for file update
                                        item_element->dValue = value_element->dValue;
                                        Changed = true;
                                    }
                                }
                                
                                value_element = value->GetNextItem(value_element);
                                item_element = item->GetNextItem(item_element);
                            }
                        }
                        else
                        {   // if the array size changed, simply create a new one                            
// TODO: Create the new array
                        }
                    }
                    else if (value->Type == JSON_String)
                    {
                        if (item->Value != value->Value)
                        {   // Overwrite the changed value and mark for file update
                            item->Value = value->Value;
                            Changed = true;
                        }
                    }
                    else {
                        if (item->dValue != value->dValue)
                        {   // Overwrite the changed value and mark for file update
                            item->dValue = value->dValue;
                            Changed = true;
                        }
                    }
                }
                else
                {
                    return false;
                }

                found = true;
                break;
            }
            
            item = vals->GetNextItem(item);
        }

        if (!found)
        {   // Add the new value
            Changed = true;

            if (value->Type == JSON_String)
                vals->AddStringItem(value->Name, value->Value);
            else if (value->Type == JSON_Bool)
                vals->AddBoolItem(value->Name, ((int)value->dValue != 0));
            else if (value->Type == JSON_Number)
                vals->AddNumberItem(value->Name, value->dValue);
            else if (value->Type == JSON_Array)
                vals->AddItem(value->Name, value->Copy());
            else
            {
                OVR_ASSERT(false);
                Changed = false;
            }
        }
    }

    return true;
}

//-----------------------------------------------------------------------------
Profile* ProfileManager::GetDefaultUserProfile(const ProfileDeviceKey& deviceKey)
{
    const char* userName = GetDefaultUser(deviceKey);

    Profile* profile = GetProfile(deviceKey, userName);

    if (!profile)
    {
        profile = GetDefaultProfile(deviceKey.HmdType);
    }

    return profile;
}

//-----------------------------------------------------------------------------
Profile* ProfileManager::GetProfile(const ProfileDeviceKey& deviceKey, const char* user)
{
    Lock::Locker lockScope(&ProfileLock);

    if (ProfileCache == NULL)
    {   // Load the cache
        LoadCache(false);
        if (ProfileCache == NULL)
            return NULL;
    }
    
    Profile* profile = new Profile(BasePath);

    if (deviceKey.Valid)
    {
        if (!profile->LoadDeviceProfile(deviceKey) && (user == NULL))
        {
            profile->Release();
            return NULL;
        }
    }
    
    if (user)
    {
        const char* product_str = deviceKey.ProductName.IsEmpty() ? NULL : deviceKey.ProductName.ToCStr();
        const char* serial_str = deviceKey.PrintedSerial.IsEmpty() ? NULL : deviceKey.PrintedSerial.ToCStr();

        if (!profile->LoadProfile(ProfileCache.GetPtr(), user, product_str, serial_str))
        {
            profile->Release();
            return NULL;
        }
    }

    return profile;
}


//-----------------------------------------------------------------------------
// ***** Profile

Profile::~Profile()
{
    ValMap.Clear();
    for (unsigned int i=0; i<Values.GetSize(); i++)
        Values[i]->Release();

    Values.Clear();
}

bool Profile::Close()
{
    // TODO:
    return true;
}

//-----------------------------------------------------------------------------
void Profile::CopyItems(JSON* root, String prefix)
{
    JSON* item = root->GetFirstItem();
    while (item)
    {
        String item_name;
        if (prefix.IsEmpty())
            item_name = item->Name;
        else
            item_name = prefix + "." + item->Name;

        if (item->Type == JSON_Object)
        {   // recursively copy the children
            
            CopyItems(item, item_name);
        }
        else
        {
            //Settings.Set(item_name, item->Value);
            SetValue(item);
        }

        item = root->GetNextItem(item);
    }
}

//-----------------------------------------------------------------------------
bool Profile::LoadDeviceFile(unsigned int productId, const char* printedSerialNumber)
{
    if (printedSerialNumber[0] == 0)
        return false;

    String path = BasePath + "/Devices.json";

    // Load the device profiles
    Ptr<JSON> root = *JSON::Load(path);
    if (root == NULL)
        return false;

    // Quick sanity check of the file type and format before we parse it
    JSON* version = root->GetFirstItem();
    if (version && version->Name == "Oculus Device Profile Version")
    {   
        int major = atoi(version->Value.ToCStr());
        if (major > MAX_DEVICE_PROFILE_MAJOR_VERSION)
            return false;   // don't parse the file on unsupported major version number
    }
    else
    {
        return false;
    }   

    JSON* device = root->GetNextItem(version);
    while (device)
    {   
        if (device->Name == "Device")
        {   
            JSON* product_item = device->GetItemByName("ProductID");
            JSON* serial_item = device->GetItemByName("Serial");
            if (product_item && serial_item &&
                (product_item->dValue == productId) && (serial_item->Value == printedSerialNumber))
            {   
                // found the entry for this device so recursively copy all the settings to the profile
                CopyItems(device, "");
                return true;   
            }
        }

        device = root->GetNextItem(device);
    }
    
    return false;
}

#if 0
//-----------------------------------------------------------------------------
static int BCDByte(unsigned int byte)
{
    int digit1 = (byte >> 4) & 0x000f;
    int digit2 = byte & 0x000f;
    int decimal = digit1 * 10 + digit2;
    return decimal;
}
#endif

//-----------------------------------------------------------------------------
bool Profile::LoadDeviceProfile(const ProfileDeviceKey& deviceKey)
{
    bool success = false;
    if (!deviceKey.Valid)
            return false;

#if 0
        int dev_major = BCDByte((sinfo.Version >> 8) & 0x00ff);
        OVR_UNUSED(dev_major);
        //int dev_minor = BCDByte(sinfo.Version & 0xff);
      
        //if (dev_minor > 18)
        //{   // If the firmware supports hardware stored profiles then grab the device profile
            // from the sensor
            // TBD:  Implement this
        //}
        //else
        {
#endif
            // Grab the model and serial number from the device and use it to access the device
            // profile file stored on the local machine
        success = LoadDeviceFile(deviceKey.ProductId, deviceKey.PrintedSerial);
    //}

    return success;
}

//-----------------------------------------------------------------------------
bool Profile::LoadUser(JSON* root, 
                         const char* user,
                          const char* model_name,
                          const char* device_serial)
{
    if (user == NULL)
        return false;

    // For legacy files, convert to old style names
    //if (model_name && OVR_strcmp(model_name, "Oculus Rift DK1") == 0)
    //    model_name = "RiftDK1";
    
    bool user_found = false;
    JSON* data = root->GetItemByName("TaggedData");
    if (data)
    {   
        const char* tag_names[3];
        const char* tags[3];
        tag_names[0] = "User";
        tags[0] = user;
        int num_tags = 1;

        if (model_name)
        {
            tag_names[num_tags] = "Product";
            tags[num_tags] = model_name;
            num_tags++;
        }

        if (device_serial)
        {
            tag_names[num_tags] = "Serial";
            tags[num_tags] = device_serial;
            num_tags++;
        }

        // Retrieve all tag permutations
        for (int combos=1; combos<=num_tags; combos++)
        {
            for (int i=0; i<(num_tags - combos + 1); i++)
            {
                JSON* vals = FindTaggedData(data, tag_names+i, tags+i, combos);
                if (vals)
                {   
                    if (i==0)   // This tag-combination contains a user match
                        user_found = true;

                    // Add the values to the Profile.  More specialized multi-tag values
                    // will take precedence over and overwrite generalized ones 
                    // For example: ("Me","RiftDK1").IPD would overwrite ("Me").IPD
                    JSON* item = vals->GetFirstItem();
                    while (item)
                    {
                        //printf("Add %s, %s\n", item->Name.ToCStr(), item->Value.ToCStr());
                        //Settings.Set(item->Name, item->Value);
                        SetValue(item);
                        item = vals->GetNextItem(item);
                    }
                }
            }
        }
    }

    if (user_found)
        SetValue(OVR_KEY_USER, user);

    return user_found;
}


//-----------------------------------------------------------------------------
bool Profile::LoadProfile(JSON* root,
                          const char* user,
                          const char* device_model,
                          const char* device_serial)
{
    if (!LoadUser(root, user, device_model, device_serial))
        return false;

    return true;
}


//-----------------------------------------------------------------------------
char* Profile::GetValue(const char* key, char* val, int val_length) const
{
    JSON* value = NULL;
    if (ValMap.Get(key, &value))
    {
        OVR_strcpy(val, val_length, value->Value.ToCStr());
        return val;
    }
    else
    {
        val[0] = 0;
        return NULL;
    }
}

//-----------------------------------------------------------------------------
const char* Profile::GetValue(const char* key)
{
    // Non-reentrant query.  The returned buffer can only be used until the next call
    // to GetValue()
    JSON* value = NULL;
    if (ValMap.Get(key, &value))
    {
        TempVal = value->Value;
        return TempVal.ToCStr();
    }
    else
    {
        return NULL;
    }
}

//-----------------------------------------------------------------------------
int Profile::GetNumValues(const char* key) const
{
    JSON* value = NULL;
    if (ValMap.Get(key, &value))
    {  
        if (value->Type == JSON_Array)
            return value->GetArraySize();
        else
            return 1;
    }
    else
        return 0;        
}

//-----------------------------------------------------------------------------
bool Profile::GetBoolValue(const char* key, bool default_val) const
{
    JSON* value = NULL;
    if (ValMap.Get(key, &value) && value->Type == JSON_Bool)
        return (value->dValue != 0);
    else
        return default_val;
}

//-----------------------------------------------------------------------------
int Profile::GetIntValue(const char* key, int default_val) const
{
    JSON* value = NULL;
    if (ValMap.Get(key, &value) && value->Type == JSON_Number)
        return (int)(value->dValue);
    else
        return default_val;
}

//-----------------------------------------------------------------------------
float Profile::GetFloatValue(const char* key, float default_val) const
{
    JSON* value = NULL;
    if (ValMap.Get(key, &value) && value->Type == JSON_Number)
        return (float)(value->dValue);
    else
        return default_val;
}

//-----------------------------------------------------------------------------
int Profile::GetFloatValues(const char* key, float* values, int num_vals) const
{
    JSON* value = NULL;
    if (ValMap.Get(key, &value) && value->Type == JSON_Array)
    {
        int val_count = Alg::Min(value->GetArraySize(), num_vals);
        JSON* item = value->GetFirstItem();
        int count=0;
        while (item && count < val_count)
        {
            if (item->Type == JSON_Number)
                values[count] = (float)item->dValue;
            else
                break;

            count++;
            item = value->GetNextItem(item);
        }

        return count;
    }
    else
    {
        return 0;
    }
}

//-----------------------------------------------------------------------------
double Profile::GetDoubleValue(const char* key, double default_val) const
{
    JSON* value = NULL;
    if (ValMap.Get(key, &value) && value->Type == JSON_Number)
        return value->dValue;
    else
        return default_val;
}

//-----------------------------------------------------------------------------
int Profile::GetDoubleValues(const char* key, double* values, int num_vals) const
{
    JSON* value = NULL;
    if (ValMap.Get(key, &value) && value->Type == JSON_Array)
    {
        int val_count = Alg::Min(value->GetArraySize(), num_vals);
        JSON* item = value->GetFirstItem();
        int count=0;
        while (item && count < val_count)
        {
            if (item->Type == JSON_Number)
                values[count] = item->dValue;
            else
                break;

            count++;
            item = value->GetNextItem(item);
        }

        return count;
    }
    return 0;
}

//-----------------------------------------------------------------------------
void Profile::SetValue(JSON* val)
{
    if (val == NULL)
        return;

    if (val->Type == JSON_Number)
        SetDoubleValue(val->Name, val->dValue);
    else if (val->Type == JSON_Bool)
        SetBoolValue(val->Name, (val->dValue != 0));
    else if (val->Type == JSON_String)
        SetValue(val->Name, val->Value);
    else if (val->Type == JSON_Array)
    {
        // Create a copy of the array
        JSON* value = val->Copy();
        Values.PushBack(value);
        ValMap.Set(value->Name, value);
    }
}

//-----------------------------------------------------------------------------
void Profile::SetValue(const char* key, const char* val)
{
    if (key == NULL || val == NULL)
        return;

    JSON* value = NULL;
    if (ValMap.Get(key, &value))
    {
        value->Value = val;
    }
    else
    {
        value = JSON::CreateString(val);
        value->Name = key;

        Values.PushBack(value);
        ValMap.Set(key, value);
    }
}

//-----------------------------------------------------------------------------
void Profile::SetBoolValue(const char* key, bool val)
{
    if (key == NULL)
        return;

    JSON* value = NULL;
    if (ValMap.Get(key, &value))
    {
        value->dValue = val;
    }
    else
    {
        value = JSON::CreateBool(val);
        value->Name = key;

        Values.PushBack(value);
        ValMap.Set(key, value);
    }
}

//-----------------------------------------------------------------------------
void Profile::SetIntValue(const char* key, int val)
{
    SetDoubleValue(key, val);
}

//-----------------------------------------------------------------------------
void Profile::SetFloatValue(const char* key, float val)
{
    SetDoubleValue(key, val);
}

//-----------------------------------------------------------------------------
void Profile::SetFloatValues(const char* key, const float* vals, int num_vals)
{
    JSON* value = NULL;
    int val_count = 0;
    if (ValMap.Get(key, &value))
    {
        if (value->Type == JSON_Array)
        {
            // truncate the existing array if fewer entries provided
            int num_existing_vals = value->GetArraySize();
            for (int i=num_vals; i<num_existing_vals; i++)
                value->RemoveLast();
            
            JSON* item = value->GetFirstItem();
            while (item && val_count < num_vals)
            {
                if (item->Type == JSON_Number)
                    item->dValue = vals[val_count];

                item = value->GetNextItem(item);
                val_count++;
            }
        }
        else
        {
            return;  // Maybe we should change the data type?
        }
    }
    else
    {
        value = JSON::CreateArray();
        value->Name = key;

        Values.PushBack(value);
        ValMap.Set(key, value);
    }

    for (; val_count < num_vals; val_count++)
        value->AddArrayNumber(vals[val_count]);
}

//-----------------------------------------------------------------------------
void Profile::SetDoubleValue(const char* key, double val)
{
    JSON* value = NULL;
    if (ValMap.Get(key, &value))
    {
        value->dValue = val;
    }
    else
    {
        value = JSON::CreateNumber(val);
        value->Name = key;

        Values.PushBack(value);
        ValMap.Set(key, value);
    }
}

//-----------------------------------------------------------------------------
void Profile::SetDoubleValues(const char* key, const double* vals, int num_vals)
{
    JSON* value = NULL;
    int val_count = 0;
    if (ValMap.Get(key, &value))
    {
        if (value->Type == JSON_Array)
        {
            // truncate the existing array if fewer entries provided
            int num_existing_vals = value->GetArraySize();
            for (int i=num_vals; i<num_existing_vals; i++)
                value->RemoveLast();
            
            JSON* item = value->GetFirstItem();
            while (item && val_count < num_vals)
            {
                if (item->Type == JSON_Number)
                    item->dValue = vals[val_count];

                item = value->GetNextItem(item);
                val_count++;
            }
        }
        else
        {
            return;  // Maybe we should change the data type?
        }
    }
    else
    {
        value = JSON::CreateArray();
        value->Name = key;

        Values.PushBack(value);
        ValMap.Set(key, value);
    }

    for (; val_count < num_vals; val_count++)
        value->AddArrayNumber(vals[val_count]);
}

//------------------------------------------------------------------------------
bool Profile::IsDefaultProfile()
{
    return 0 == OVR::String::CompareNoCase("Default", GetValue(OVR_KEY_NAME));
}


}  // namespace OVR
