/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include <sal/config.h>

#include <cassert>
#include <utility>
#include <vector>

#include <com/sun/star/container/XChild.hpp>
#include <com/sun/star/lang/NoSupportException.hpp>
#include <com/sun/star/lang/XUnoTunnel.hpp>
#include <com/sun/star/uno/Any.hxx>
#include <com/sun/star/uno/Reference.hxx>
#include <com/sun/star/uno/Sequence.hxx>
#include <com/sun/star/uno/Type.hxx>
#include <com/sun/star/uno/XInterface.hpp>
#include <cppu/unotype.hxx>
#include <cppuhelper/queryinterface.hxx>
#include <cppuhelper/weak.hxx>
#include <comphelper/servicehelper.hxx>
#include <osl/mutex.hxx>
#include <rtl/ref.hxx>
#include <rtl/ustrbuf.hxx>
#include <rtl/ustring.hxx>
#include <sal/types.h>

#include "access.hxx"
#include "childaccess.hxx"
#include "components.hxx"
#include "data.hxx"
#include "localizedpropertynode.hxx"
#include "localizedvaluenode.hxx"
#include "lock.hxx"
#include "modifications.hxx"
#include "node.hxx"
#include "propertynode.hxx"
#include "rootaccess.hxx"
#include "type.hxx"

namespace configmgr {

ChildAccess::ChildAccess(
    Components & components, rtl::Reference< RootAccess > const & root,
    rtl::Reference< Access > const & parent, OUString name,
    rtl::Reference< Node > const & node):
    Access(components), root_(root), parent_(parent), name_(std::move(name)), node_(node),
    inTransaction_(false),
    lock_( lock() )
{
    assert(root.is() && parent.is() && node.is());
}

ChildAccess::ChildAccess(
    Components & components, rtl::Reference< RootAccess > const & root,
    rtl::Reference< Node > const & node):
    Access(components), root_(root), node_(node), inTransaction_(false),
    lock_( lock() )
{
    assert(root.is() && node.is());
}

std::vector<OUString> ChildAccess::getAbsolutePath() {
    rtl::Reference< Access > parent(getParentAccess());
    assert(parent.is());
    std::vector<OUString> path(parent->getAbsolutePath());
    path.push_back(name_);
    return path;
}

std::vector<OUString> ChildAccess::getRelativePath() {
    std::vector<OUString> path;
    rtl::Reference< Access > parent(getParentAccess());
    if (parent.is()) {
        path = parent->getRelativePath();
    }
    path.push_back(name_);
    return path;
}

OUString ChildAccess::getRelativePathRepresentation() {
    OUStringBuffer path(128);
    rtl::Reference< Access > parent(getParentAccess());
    if (parent.is()) {
        path.append(parent->getRelativePathRepresentation());
        if (!path.isEmpty()) {
            path.append('/');
        }
    }
    path.append(Data::createSegment(node_->getTemplateName(), name_));
    return path.makeStringAndClear();
}

const rtl::Reference< Node > & ChildAccess::getNode() {
    return node_;
}

bool ChildAccess::isFinalized() {
    return node_->getFinalized() != Data::NO_LAYER ||
        (parent_.is() && parent_->isFinalized());
}

const OUString & ChildAccess::getNameInternal() {
    return name_;
}

rtl::Reference< RootAccess > ChildAccess::getRootAccess() {
    return root_;
}

rtl::Reference< Access > ChildAccess::getParentAccess() {
    return parent_;
}

void ChildAccess::acquire() noexcept {
    Access::acquire();
}

void ChildAccess::release() noexcept {
    Access::release();
}

css::uno::Reference< css::uno::XInterface > ChildAccess::getParent()
{
    assert(thisIs(IS_ANY));
    osl::MutexGuard g(*lock_);
    checkLocalizedPropertyAccess();
    return cppu::getXWeak(parent_.get());
}

void ChildAccess::setParent(css::uno::Reference< css::uno::XInterface > const &)
{
    assert(thisIs(IS_ANY));
    osl::MutexGuard g(*lock_);
    checkLocalizedPropertyAccess();
    throw css::lang::NoSupportException(
        u"setParent"_ustr, getXWeak());
}

void ChildAccess::bind(
    rtl::Reference< RootAccess > const & root,
    rtl::Reference< Access > const & parent, OUString const & name)
    noexcept
{
    assert(!parent_.is() && root.is() && parent.is() && !name.isEmpty());
    root_ = root;
    parent_ = parent;
    name_ = name;
}

void ChildAccess::unbind() noexcept {
    assert(parent_.is());
    parent_->releaseChild(name_);
    parent_.clear();
    inTransaction_ = true;
}

void ChildAccess::committed() {
    inTransaction_ = false;
}

void ChildAccess::setNode(rtl::Reference< Node > const & node) {
    node_ = node;
}

void ChildAccess::setProperty(
    css::uno::Any const & value, Modifications * localModifications)
{
    assert(localModifications != nullptr);
    Type type = TYPE_ERROR;
    bool isNillable = false;
    switch (node_->kind()) {
    case Node::KIND_PROPERTY:
        {
            PropertyNode * prop = static_cast< PropertyNode * >(node_.get());
            type = prop->getStaticType();
            isNillable = prop->isNillable();
        }
        break;
    case Node::KIND_LOCALIZED_PROPERTY:
        {
            OUString locale(getRootAccess()->getLocale());
            if (!Components::allLocales(locale)) {
                rtl::Reference< ChildAccess > child(getChild(locale));
                if (child.is()) {
                    child->setProperty(value, localModifications);
                } else {
                    insertLocalizedValueChild(
                        locale, value, localModifications);
                }
                return;
            }
        }
        break;
    case Node::KIND_LOCALIZED_VALUE:
        {
            LocalizedPropertyNode * locprop =
                static_cast< LocalizedPropertyNode * >(getParentNode().get());
            type = locprop->getStaticType();
            isNillable = locprop->isNillable();
        }
        break;
    default:
        break;
    }
    checkValue(value, type, isNillable);
    getParentAccess()->markChildAsModified(this);
    changedValue_.emplace(value);
    localModifications->add(getRelativePath());
}


css::uno::Any ChildAccess::asValue()
{
    if (changedValue_)
    {
        return *changedValue_;
    }
    css::uno::Any value;
    if (!asSimpleValue(node_, value, getComponents()))
    {
        if (node_->kind() == Node::KIND_LOCALIZED_PROPERTY)
        {
            OUString locale(getRootAccess()->getLocale());
            if (!Components::allLocales(locale)) {
                rtl::Reference< ChildAccess > child(getChild("*" + locale));
                // As a last resort, return a nil value even though it may be
                // illegal for the given property:
                return child.is() ? child->asValue() : css::uno::Any();
            }
        }
        value <<= css::uno::Reference< css::uno::XInterface >(getXWeak());
    }
    return value;
}

/// Can we quickly extract a simple value into value ? if so returns true
bool ChildAccess::asSimpleValue(const rtl::Reference< Node > &rNode,
                                css::uno::Any &value,
                                Components &components)
{
    switch (rNode->kind()) {
    case Node::KIND_PROPERTY:
        value = static_cast< PropertyNode * >(rNode.get())->getValue(components);
        return true;
    case Node::KIND_LOCALIZED_VALUE:
        value = static_cast< LocalizedValueNode * >(rNode.get())->getValue();
        return true;
    default:
        return false;
    }
}

void ChildAccess::commitChanges(bool valid, Modifications * globalModifications)
{
    assert(globalModifications != nullptr);
    commitChildChanges(valid, globalModifications);
    if (valid && changedValue_)
    {
        std::vector<OUString> path(getAbsolutePath());
        getComponents().addModification(path);
        globalModifications->add(path);
        switch (node_->kind()) {
        case Node::KIND_PROPERTY:
            static_cast< PropertyNode * >(node_.get())->setValue(
                Data::NO_LAYER, *changedValue_, true);
            break;
        case Node::KIND_LOCALIZED_VALUE:
            static_cast< LocalizedValueNode * >(node_.get())->setValue(
                Data::NO_LAYER, *changedValue_, true);
            break;
        default:
            assert(false); // this cannot happen
            break;
        }
    }
    changedValue_.reset();
}

ChildAccess::~ChildAccess() {
    osl::MutexGuard g(*lock_);
    if (parent_.is()) {
        parent_->releaseChild(name_);
    }
}

void ChildAccess::addTypes(std::vector< css::uno::Type > * types) const {
    assert(types != nullptr);
    types->push_back(cppu::UnoType< css::container::XChild >::get());
    types->push_back(cppu::UnoType< css::lang::XUnoTunnel >::get());
}

void ChildAccess::addSupportedServiceNames(
    std::vector<OUString> * services)
{
    assert(services != nullptr);
    services->push_back(
        getParentNode()->kind() == Node::KIND_GROUP
        ? u"com.sun.star.configuration.GroupElement"_ustr
        : u"com.sun.star.configuration.SetElement"_ustr);
}

css::uno::Any ChildAccess::queryInterface(css::uno::Type const & aType)
{
    assert(thisIs(IS_ANY));
    osl::MutexGuard g(*lock_);
    checkLocalizedPropertyAccess();
    css::uno::Any res(Access::queryInterface(aType));
    return res.hasValue()
        ? res
        : cppu::queryInterface(
            aType, static_cast< css::container::XChild * >(this));
}

}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
