//===-- OptionValueProperties.cpp ---------------------------------*- C++ -*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "lldb/Interpreter/OptionValueProperties.h"

// C Includes
// C++ Includes
// Other libraries and framework includes
// Project includes
#include "lldb/Core/Flags.h"
#include "lldb/Core/Stream.h"
#include "lldb/Core/StringList.h"
#include "lldb/Core/UserSettingsController.h"
#include "lldb/Interpreter/Args.h"
#include "lldb/Interpreter/OptionValues.h"
#include "lldb/Interpreter/Property.h"

using namespace lldb;
using namespace lldb_private;


OptionValueProperties::OptionValueProperties (const ConstString &name) :
    OptionValue (),
    m_name (name),
    m_properties (),
    m_name_to_index ()
{
}

OptionValueProperties::OptionValueProperties (const OptionValueProperties &global_properties) :
    OptionValue (global_properties),
    std::enable_shared_from_this<OptionValueProperties> (),
    m_name (global_properties.m_name),
    m_properties (global_properties.m_properties),
    m_name_to_index (global_properties.m_name_to_index)
{
    // We now have an exact copy of "global_properties". We need to now
    // find all non-global settings and copy the property values so that
    // all non-global settings get new OptionValue instances created for
    // them.
    const size_t num_properties = m_properties.size();
    for (size_t i=0; i<num_properties; ++i)
    {
        // Duplicate any values that are not global when constructing properties from
        // a global copy
        if (m_properties[i].IsGlobal() == false)
        {
            lldb::OptionValueSP new_value_sp (m_properties[i].GetValue()->DeepCopy());
            m_properties[i].SetOptionValue(new_value_sp);
        }
    }
}



size_t
OptionValueProperties::GetNumProperties() const
{
    return m_properties.size();
}


void
OptionValueProperties::Initialize (const PropertyDefinition *defs)
{
    for (size_t i=0; defs[i].name; ++i)
    {
        Property property(defs[i]);
        assert(property.IsValid());
        m_name_to_index.Append(property.GetName().GetCString(),m_properties.size());
        property.GetValue()->SetParent(shared_from_this());
        m_properties.push_back(property);
    }
    m_name_to_index.Sort();
}

void
OptionValueProperties::SetValueChangedCallback (uint32_t property_idx,
                                                OptionValueChangedCallback callback,
                                                void *baton)
{
    Property *property = ProtectedGetPropertyAtIndex (property_idx);
    if (property)
        property->SetValueChangedCallback (callback, baton);
}

void
OptionValueProperties::AppendProperty(const ConstString &name,
                                      const ConstString &desc,
                                      bool is_global,
                                      const OptionValueSP &value_sp)
{
    Property property(name, desc, is_global, value_sp);
    m_name_to_index.Append(name.GetCString(),m_properties.size());
    m_properties.push_back(property);
    value_sp->SetParent (shared_from_this());
    m_name_to_index.Sort();
}



//bool
//OptionValueProperties::GetQualifiedName (Stream &strm)
//{
//    bool dumped_something = false;
////    lldb::OptionValuePropertiesSP parent_sp(GetParent ());
////    if (parent_sp)
////    {
////        parent_sp->GetQualifiedName (strm);
////        strm.PutChar('.');
////        dumped_something = true;
////    }
//    if (m_name)
//    {
//        strm << m_name;
//        dumped_something = true;
//    }
//    return dumped_something;
//}
//
lldb::OptionValueSP
OptionValueProperties::GetValueForKey  (const ExecutionContext *exe_ctx,
                                        const ConstString &key,
                                        bool will_modify) const
{
    lldb::OptionValueSP value_sp;
    size_t idx = m_name_to_index.Find (key.GetCString(), SIZE_MAX);
    if (idx < m_properties.size())
        value_sp = GetPropertyAtIndex(exe_ctx, will_modify, idx)->GetValue();
    return value_sp;
}

lldb::OptionValueSP
OptionValueProperties::GetSubValue (const ExecutionContext *exe_ctx,
                                    const char *name,
                                    bool will_modify,
                                    Error &error) const
{
    lldb::OptionValueSP value_sp;
    
    if (name && name[0])
    {
        const char *sub_name = nullptr;
        ConstString key;
        size_t key_len = ::strcspn (name, ".[{");
        
        if (name[key_len])
        {
            key.SetCStringWithLength (name, key_len);
            sub_name = name + key_len;
        }
        else
            key.SetCString (name);
        
        value_sp = GetValueForKey (exe_ctx, key, will_modify);
        if (sub_name && value_sp)
        {
            switch (sub_name[0])
            {
            case '.':
            {
                lldb::OptionValueSP return_val_sp;
                return_val_sp = value_sp->GetSubValue (exe_ctx, sub_name + 1, will_modify, error);
                if (!return_val_sp)
                {
                    if (Properties::IsSettingExperimental(sub_name + 1))
                    {
                        size_t experimental_len = strlen(Properties::GetExperimentalSettingsName()); 
                        if (*(sub_name + experimental_len + 1) == '.')
                        return_val_sp = value_sp->GetSubValue(exe_ctx, sub_name + experimental_len + 2, will_modify, error);
                        // It isn't an error if an experimental setting is not present.
                        if (!return_val_sp)
                            error.Clear();
                    }
                }
                return return_val_sp;
            }
            case '{':
                // Predicate matching for predicates like
                // "<setting-name>{<predicate>}"
                // strings are parsed by the current OptionValueProperties subclass
                // to mean whatever they want to. For instance a subclass of
                // OptionValueProperties for a lldb_private::Target might implement:
                // "target.run-args{arch==i386}"   -- only set run args if the arch is i386
                // "target.run-args{path=/tmp/a/b/c/a.out}" -- only set run args if the path matches
                // "target.run-args{basename==test&&arch==x86_64}" -- only set run args if executable basename is "test" and arch is "x86_64"
                if (sub_name[1])
                {
                    const char *predicate_start = sub_name + 1;
                    const char *predicate_end = strchr(predicate_start, '}');
                    if (predicate_end)
                    {
                        std::string predicate(predicate_start, predicate_end);
                        if (PredicateMatches(exe_ctx, predicate.c_str()))
                        {
                            if (predicate_end[1])
                            {
                                // Still more subvalue string to evaluate
                                return value_sp->GetSubValue (exe_ctx, predicate_end + 1, will_modify, error);
                            }
                            else
                            {
                                // We have a match!
                                break;
                            }
                        }
                    }
                }
                // Predicate didn't match or wasn't correctly formed
                value_sp.reset();
                break;
            
            case '[':
                // Array or dictionary access for subvalues like:
                // "[12]"       -- access 12th array element
                // "['hello']"  -- dictionary access of key named hello
                return value_sp->GetSubValue (exe_ctx, sub_name, will_modify, error);

            default:
                value_sp.reset();
                break;
            }
        }
    }
    return value_sp;
}

Error
OptionValueProperties::SetSubValue (const ExecutionContext *exe_ctx,
                                    VarSetOperationType op,
                                    const char *name,
                                    const char *value)
{
    Error error;
    const bool will_modify = true;
    lldb::OptionValueSP value_sp (GetSubValue (exe_ctx, name, will_modify, error));
    if (value_sp)
        error = value_sp->SetValueFromString(value ? llvm::StringRef(value) : llvm::StringRef(), op);
    else
    {
        if (error.AsCString() == nullptr)
            error.SetErrorStringWithFormat("invalid value path '%s'", name);
    }
    return error;
}


ConstString
OptionValueProperties::GetPropertyNameAtIndex (uint32_t idx) const
{
    const Property *property = GetPropertyAtIndex(nullptr, false, idx);
    if (property)
        return property->GetName();
    return ConstString();
    
}

const char *
OptionValueProperties::GetPropertyDescriptionAtIndex (uint32_t idx) const
{
    const Property *property = GetPropertyAtIndex(nullptr, false, idx);
    if (property)
        return property->GetDescription();
    return nullptr;
}

uint32_t
OptionValueProperties::GetPropertyIndex (const ConstString &name) const
{
    return m_name_to_index.Find (name.GetCString(), SIZE_MAX);
}

const Property *
OptionValueProperties::GetProperty (const ExecutionContext *exe_ctx, bool will_modify, const ConstString &name) const
{
    return GetPropertyAtIndex (exe_ctx, will_modify, m_name_to_index.Find (name.GetCString(), SIZE_MAX));
}

const Property *
OptionValueProperties::GetPropertyAtIndex (const ExecutionContext *exe_ctx, bool will_modify, uint32_t idx) const
{
    return ProtectedGetPropertyAtIndex (idx);
}

lldb::OptionValueSP
OptionValueProperties::GetPropertyValueAtIndex (const ExecutionContext *exe_ctx,
                                                bool will_modify,
                                                uint32_t idx) const
{
    const Property *setting = GetPropertyAtIndex (exe_ctx, will_modify, idx);
    if (setting)
        return setting->GetValue();
    return OptionValueSP();
}

OptionValuePathMappings *
OptionValueProperties::GetPropertyAtIndexAsOptionValuePathMappings (const ExecutionContext *exe_ctx, bool will_modify, uint32_t idx) const
{
    OptionValueSP value_sp(GetPropertyValueAtIndex (exe_ctx, will_modify, idx));
    if (value_sp)
        return value_sp->GetAsPathMappings();
    return nullptr;
}

OptionValueFileSpecList *
OptionValueProperties::GetPropertyAtIndexAsOptionValueFileSpecList (const ExecutionContext *exe_ctx, bool will_modify, uint32_t idx) const
{
    OptionValueSP value_sp(GetPropertyValueAtIndex (exe_ctx, will_modify, idx));
    if (value_sp)
        return value_sp->GetAsFileSpecList();
    return nullptr;
}

OptionValueArch *
OptionValueProperties::GetPropertyAtIndexAsOptionValueArch (const ExecutionContext *exe_ctx, uint32_t idx) const
{
    const Property *property = GetPropertyAtIndex (exe_ctx, false, idx);
    if (property)
        return property->GetValue()->GetAsArch();
    return nullptr;
}

OptionValueLanguage *
OptionValueProperties::GetPropertyAtIndexAsOptionValueLanguage (const ExecutionContext *exe_ctx, uint32_t idx) const
{
    const Property *property = GetPropertyAtIndex (exe_ctx, false, idx);
    if (property)
        return property->GetValue()->GetAsLanguage();
    return nullptr;
}

bool
OptionValueProperties::GetPropertyAtIndexAsArgs (const ExecutionContext *exe_ctx, uint32_t idx, Args &args) const
{
    const Property *property = GetPropertyAtIndex (exe_ctx, false, idx);
    if (property)
    {
        OptionValue *value = property->GetValue().get();
        if (value)
        {
            const OptionValueArray *array = value->GetAsArray();
            if (array)
                return array->GetArgs(args);
            else
            {
                const OptionValueDictionary *dict = value->GetAsDictionary();
                if (dict)
                    return dict->GetArgs(args);
            }
        }
    }
    return false;
}

bool
OptionValueProperties::SetPropertyAtIndexFromArgs (const ExecutionContext *exe_ctx, uint32_t idx, const Args &args)
{
    const Property *property = GetPropertyAtIndex (exe_ctx, true, idx);
    if (property)
    {
        OptionValue *value = property->GetValue().get();
        if (value)
        {
            OptionValueArray *array = value->GetAsArray();
            if (array)
                return array->SetArgs(args, eVarSetOperationAssign).Success();
            else
            {
                OptionValueDictionary *dict = value->GetAsDictionary();
                if (dict)
                    return dict->SetArgs(args, eVarSetOperationAssign).Success();
            }
        }
    }
    return false;
}

bool
OptionValueProperties::GetPropertyAtIndexAsBoolean (const ExecutionContext *exe_ctx, uint32_t idx, bool fail_value) const
{
    const Property *property = GetPropertyAtIndex (exe_ctx, false, idx);
    if (property)
    {
        OptionValue *value = property->GetValue().get();
        if (value)
            return value->GetBooleanValue(fail_value);
    }
    return fail_value;
}

bool
OptionValueProperties::SetPropertyAtIndexAsBoolean (const ExecutionContext *exe_ctx, uint32_t idx, bool new_value)
{
    const Property *property = GetPropertyAtIndex (exe_ctx, true, idx);
    if (property)
    {
        OptionValue *value = property->GetValue().get();
        if (value)
        {
            value->SetBooleanValue(new_value);
            return true;
        }
    }
    return false;
}

OptionValueDictionary *
OptionValueProperties::GetPropertyAtIndexAsOptionValueDictionary (const ExecutionContext *exe_ctx, uint32_t idx) const
{
    const Property *property = GetPropertyAtIndex (exe_ctx, false, idx);
    if (property)
        return property->GetValue()->GetAsDictionary();
    return nullptr;
}

int64_t
OptionValueProperties::GetPropertyAtIndexAsEnumeration (const ExecutionContext *exe_ctx, uint32_t idx, int64_t fail_value) const
{
    const Property *property = GetPropertyAtIndex (exe_ctx, false, idx);
    if (property)
    {
        OptionValue *value = property->GetValue().get();
        if (value)
            return value->GetEnumerationValue(fail_value);
    }
    return fail_value;
}

bool
OptionValueProperties::SetPropertyAtIndexAsEnumeration (const ExecutionContext *exe_ctx, uint32_t idx, int64_t new_value)
{
    const Property *property = GetPropertyAtIndex (exe_ctx, true, idx);
    if (property)
    {
        OptionValue *value = property->GetValue().get();
        if (value)
            return value->SetEnumerationValue(new_value);
    }
    return false;
}

const FormatEntity::Entry *
OptionValueProperties::GetPropertyAtIndexAsFormatEntity (const ExecutionContext *exe_ctx, uint32_t idx)
{
    const Property *property = GetPropertyAtIndex (exe_ctx, true, idx);
    if (property)
    {
        OptionValue *value = property->GetValue().get();
        if (value)
            return value->GetFormatEntity();
    }
    return nullptr;
}

OptionValueFileSpec *
OptionValueProperties::GetPropertyAtIndexAsOptionValueFileSpec (const ExecutionContext *exe_ctx, bool will_modify, uint32_t idx) const
{
    const Property *property = GetPropertyAtIndex (exe_ctx, false, idx);
    if (property)
    {
        OptionValue *value = property->GetValue().get();
        if (value)
            return value->GetAsFileSpec();
    }
    return nullptr;
}


FileSpec
OptionValueProperties::GetPropertyAtIndexAsFileSpec (const ExecutionContext *exe_ctx, uint32_t idx) const
{
    const Property *property = GetPropertyAtIndex (exe_ctx, false, idx);
    if (property)
    {
        OptionValue *value = property->GetValue().get();
        if (value)
            return value->GetFileSpecValue();
    }
    return FileSpec();
}


bool
OptionValueProperties::SetPropertyAtIndexAsFileSpec (const ExecutionContext *exe_ctx, uint32_t idx, const FileSpec &new_file_spec)
{
    const Property *property = GetPropertyAtIndex (exe_ctx, true, idx);
    if (property)
    {
        OptionValue *value = property->GetValue().get();
        if (value)
            return value->SetFileSpecValue(new_file_spec);
    }
    return false;
}

const RegularExpression *
OptionValueProperties::GetPropertyAtIndexAsOptionValueRegex (const ExecutionContext *exe_ctx, uint32_t idx) const
{
    const Property *property = GetPropertyAtIndex (exe_ctx, false, idx);
    if (property)
    {
        OptionValue *value = property->GetValue().get();
        if (value)
            return value->GetRegexValue();
    }
    return nullptr;
}

OptionValueSInt64 *
OptionValueProperties::GetPropertyAtIndexAsOptionValueSInt64 (const ExecutionContext *exe_ctx, uint32_t idx) const
{
    const Property *property = GetPropertyAtIndex (exe_ctx, false, idx);
    if (property)
    {
        OptionValue *value = property->GetValue().get();
        if (value)
            return value->GetAsSInt64();
    }
    return nullptr;
}

int64_t
OptionValueProperties::GetPropertyAtIndexAsSInt64 (const ExecutionContext *exe_ctx, uint32_t idx, int64_t fail_value) const
{
    const Property *property = GetPropertyAtIndex (exe_ctx, false, idx);
    if (property)
    {
        OptionValue *value = property->GetValue().get();
        if (value)
            return value->GetSInt64Value(fail_value);
    }
    return fail_value;
}

bool
OptionValueProperties::SetPropertyAtIndexAsSInt64 (const ExecutionContext *exe_ctx, uint32_t idx, int64_t new_value)
{
    const Property *property = GetPropertyAtIndex (exe_ctx, true, idx);
    if (property)
    {
        OptionValue *value = property->GetValue().get();
        if (value)
            return value->SetSInt64Value(new_value);
    }
    return false;
}

const char *
OptionValueProperties::GetPropertyAtIndexAsString (const ExecutionContext *exe_ctx, uint32_t idx, const char *fail_value) const
{
    const Property *property = GetPropertyAtIndex (exe_ctx, false, idx);
    if (property)
    {
        OptionValue *value = property->GetValue().get();
        if (value)
            return value->GetStringValue(fail_value);
    }
    return fail_value;
}

bool
OptionValueProperties::SetPropertyAtIndexAsString (const ExecutionContext *exe_ctx, uint32_t idx, const char *new_value)
{
    const Property *property = GetPropertyAtIndex (exe_ctx, true, idx);
    if (property)
    {
        OptionValue *value = property->GetValue().get();
        if (value)
            return value->SetStringValue(new_value);
    }
    return false;
}

OptionValueString *
OptionValueProperties::GetPropertyAtIndexAsOptionValueString (const ExecutionContext *exe_ctx, bool will_modify, uint32_t idx) const
{
    OptionValueSP value_sp(GetPropertyValueAtIndex (exe_ctx, will_modify, idx));
    if (value_sp)
        return value_sp->GetAsString();
    return nullptr;
}


uint64_t
OptionValueProperties::GetPropertyAtIndexAsUInt64 (const ExecutionContext *exe_ctx, uint32_t idx, uint64_t fail_value) const
{
    const Property *property = GetPropertyAtIndex (exe_ctx, false, idx);
    if (property)
    {
        OptionValue *value = property->GetValue().get();
        if (value)
            return value->GetUInt64Value(fail_value);
    }
    return fail_value;
}

bool
OptionValueProperties::SetPropertyAtIndexAsUInt64 (const ExecutionContext *exe_ctx, uint32_t idx, uint64_t new_value)
{
    const Property *property = GetPropertyAtIndex (exe_ctx, true, idx);
    if (property)
    {
        OptionValue *value = property->GetValue().get();
        if (value)
            return value->SetUInt64Value(new_value);
    }
    return false;
}

bool
OptionValueProperties::Clear ()
{
    const size_t num_properties = m_properties.size();
    for (size_t i=0; i<num_properties; ++i)
        m_properties[i].GetValue()->Clear();
    return true;
}


Error
OptionValueProperties::SetValueFromString (llvm::StringRef value, VarSetOperationType op)
{
    Error error;
    
//    Args args(value_cstr);
//    const size_t argc = args.GetArgumentCount();
    switch (op)
    {
        case eVarSetOperationClear:
            Clear ();
            break;
            
        case eVarSetOperationReplace:
        case eVarSetOperationAssign:
        case eVarSetOperationRemove:
        case eVarSetOperationInsertBefore:
        case eVarSetOperationInsertAfter:
        case eVarSetOperationAppend:
        case eVarSetOperationInvalid:
            error = OptionValue::SetValueFromString (value, op);
            break;
    }
    
    return error;
}

void
OptionValueProperties::DumpValue (const ExecutionContext *exe_ctx, Stream &strm, uint32_t dump_mask)
{
    const size_t num_properties = m_properties.size();
    for (size_t i=0; i<num_properties; ++i)
    {
        const Property *property = GetPropertyAtIndex(exe_ctx, false, i);
        if (property)
        {
            OptionValue *option_value = property->GetValue().get();
            assert (option_value);
            const bool transparent_value = option_value->ValueIsTransparent ();
            property->Dump (exe_ctx,
                            strm,
                            dump_mask);
            if (!transparent_value)
                strm.EOL();
        }
    }
}

Error
OptionValueProperties::DumpPropertyValue (const ExecutionContext *exe_ctx,
                                          Stream &strm,
                                          const char *property_path,
                                          uint32_t dump_mask)
{
    Error error;
    const bool will_modify = false;
    lldb::OptionValueSP value_sp (GetSubValue (exe_ctx, property_path, will_modify, error));
    if (value_sp)
    {
        if (!value_sp->ValueIsTransparent ())
        {
            if (dump_mask & eDumpOptionName)
                strm.PutCString (property_path);
            if (dump_mask & ~eDumpOptionName)
                strm.PutChar (' ');
        }
        value_sp->DumpValue (exe_ctx, strm, dump_mask);
    }
    return error;
}

lldb::OptionValueSP
OptionValueProperties::DeepCopy () const
{
    assert(!"this shouldn't happen");
    return lldb::OptionValueSP();
}

const Property *
OptionValueProperties::GetPropertyAtPath (const ExecutionContext *exe_ctx,
                                          bool will_modify,
                                          const char *name) const
{
    const Property *property = nullptr;
    if (name && name[0])
    {
        const char *sub_name = nullptr;
        ConstString key;
        size_t key_len = ::strcspn (name, ".[{");
        
        if (name[key_len])
        {
            key.SetCStringWithLength (name, key_len);
            sub_name = name + key_len;
        }
        else
            key.SetCString (name);
        
        property = GetProperty (exe_ctx, will_modify, key);
        if (sub_name && property)
        {
            if (sub_name[0] == '.')
            {
                OptionValueProperties *sub_properties = property->GetValue()->GetAsProperties();
                if (sub_properties)
                    return sub_properties->GetPropertyAtPath(exe_ctx, will_modify, sub_name + 1);
            }
            property = nullptr;
        }
    }
    return property;
}

void
OptionValueProperties::DumpAllDescriptions (CommandInterpreter &interpreter,
                                            Stream &strm) const
{
    size_t max_name_len = 0;
    const size_t num_properties = m_properties.size();
    for (size_t i=0; i<num_properties; ++i)
    {
        const Property *property = ProtectedGetPropertyAtIndex(i);
        if (property)
            max_name_len = std::max<size_t>(property->GetName().GetLength(), max_name_len);
    }
    for (size_t i=0; i<num_properties; ++i)
    {
        const Property *property = ProtectedGetPropertyAtIndex(i);
        if (property)
            property->DumpDescription (interpreter, strm, max_name_len, false);
    }
}

void
OptionValueProperties::Apropos (const char *keyword, std::vector<const Property *> &matching_properties) const
{
    const size_t num_properties = m_properties.size();
    StreamString strm;
    for (size_t i=0; i<num_properties; ++i)
    {
        const Property *property = ProtectedGetPropertyAtIndex(i);
        if (property)
        {
            const OptionValueProperties *properties = property->GetValue()->GetAsProperties();
            if (properties)
            {
                properties->Apropos (keyword, matching_properties);
            }
            else
            {
                bool match = false;
                const char *name = property->GetName().GetCString();
                if (name && ::strcasestr(name, keyword))
                    match = true;
                else
                {
                    const char *desc = property->GetDescription();
                    if (desc && ::strcasestr(desc, keyword))
                        match = true;
                }
                if (match)
                {
                    matching_properties.push_back (property);
                }
            }
        }
    }
}

lldb::OptionValuePropertiesSP
OptionValueProperties::GetSubProperty (const ExecutionContext *exe_ctx,
                                       const ConstString &name)
{
    lldb::OptionValueSP option_value_sp(GetValueForKey(exe_ctx, name, false));
    if (option_value_sp)
    {
        OptionValueProperties *ov_properties = option_value_sp->GetAsProperties ();
        if (ov_properties)
            return ov_properties->shared_from_this();
    }
    return lldb::OptionValuePropertiesSP();
}



