/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*************************************************************************
*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright 2000, 2010 Oracle and/or its affiliates.
*
* OpenOffice.org - a multi-platform office productivity suite
*
* This file is part of OpenOffice.org.
*
* OpenOffice.org is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version 3
* only, as published by the Free Software Foundation.
*
* OpenOffice.org is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU Lesser General Public License version 3 for more details
* (a copy is included in the LICENSE file that accompanied this code).
*
* You should have received a copy of the GNU Lesser General Public License
* version 3 along with OpenOffice.org.  If not, see
* <http://www.openoffice.org/license.html>
* for a copy of the LGPLv3 License.
*
************************************************************************/

#include "precompiled_configmgr.hxx"
#include "sal/config.h"

#include "com/sun/star/uno/Any.hxx"
#include "com/sun/star/uno/Reference.hxx"
#include "com/sun/star/uno/RuntimeException.hpp"
#include "com/sun/star/uno/Sequence.hxx"
#include "com/sun/star/uno/XInterface.hpp"
#include "comphelper/sequenceasvector.hxx"
#include "osl/diagnose.h"
#include "rtl/string.h"
#include "rtl/string.hxx"
#include "rtl/ustring.h"
#include "rtl/ustring.hxx"
#include "sal/types.h"

#include "localizedvaluenode.hxx"
#include "node.hxx"
#include "nodemap.hxx"
#include "propertynode.hxx"
#include "span.hxx"
#include "type.hxx"
#include "valueparser.hxx"
#include "xmldata.hxx"
#include "xmlreader.hxx"

namespace configmgr {

namespace {

namespace css = com::sun::star;

bool parseHexDigit(char c, int * value) {
    OSL_ASSERT(value != 0);
    if (c >= '0' && c <= '9') {
        *value = c - '0';
        return true;
    }
    if (c >= 'A' && c <= 'F') {
        *value = c - 'A' + 10;
        return true;
    }
    if (c >= 'a' && c <= 'f') {
        *value = c - 'a' + 10;
        return true;
    }
    return false;
}

bool parseValue(Span const & text, sal_Bool * value) {
    OSL_ASSERT(text.is() && value != 0);
    if (text.equals(RTL_CONSTASCII_STRINGPARAM("true")) ||
        text.equals(RTL_CONSTASCII_STRINGPARAM("1")))
    {
        *value = true;
        return true;
    }
    if (text.equals(RTL_CONSTASCII_STRINGPARAM("false")) ||
        text.equals(RTL_CONSTASCII_STRINGPARAM("0")))
    {
        *value = false;
        return true;
    }
    return false;
}

bool parseValue(Span const & text, sal_Int16 * value) {
    OSL_ASSERT(text.is() && value != 0);
    // For backwards compatibility, support hexadecimal values:
    sal_Int32 n =
        rtl_str_shortenedCompareIgnoreAsciiCase_WithLength(
            text.begin, text.length, RTL_CONSTASCII_STRINGPARAM("0X"),
            RTL_CONSTASCII_LENGTH("0X")) == 0 ?
        rtl::OString(
            text.begin + RTL_CONSTASCII_LENGTH("0X"),
            text.length - RTL_CONSTASCII_LENGTH("0X")).toInt32(16) :
        rtl::OString(text.begin, text.length).toInt32();
        //TODO: check valid lexical representation
    if (n >= SAL_MIN_INT16 && n <= SAL_MAX_INT16) {
        *value = static_cast< sal_Int16 >(n);
        return true;
    }
    return false;
}

bool parseValue(Span const & text, sal_Int32 * value) {
    OSL_ASSERT(text.is() && value != 0);
    // For backwards compatibility, support hexadecimal values:
    *value =
        rtl_str_shortenedCompareIgnoreAsciiCase_WithLength(
            text.begin, text.length, RTL_CONSTASCII_STRINGPARAM("0X"),
            RTL_CONSTASCII_LENGTH("0X")) == 0 ?
        rtl::OString(
            text.begin + RTL_CONSTASCII_LENGTH("0X"),
            text.length - RTL_CONSTASCII_LENGTH("0X")).toInt32(16) :
        rtl::OString(text.begin, text.length).toInt32();
        //TODO: check valid lexical representation
    return true;
}

bool parseValue(Span const & text, sal_Int64 * value) {
    OSL_ASSERT(text.is() && value != 0);
    // For backwards compatibility, support hexadecimal values:
    *value =
        rtl_str_shortenedCompareIgnoreAsciiCase_WithLength(
            text.begin, text.length, RTL_CONSTASCII_STRINGPARAM("0X"),
            RTL_CONSTASCII_LENGTH("0X")) == 0 ?
        rtl::OString(
            text.begin + RTL_CONSTASCII_LENGTH("0X"),
            text.length - RTL_CONSTASCII_LENGTH("0X")).toInt64(16) :
        rtl::OString(text.begin, text.length).toInt64();
        //TODO: check valid lexical representation
    return true;
}

bool parseValue(Span const & text, double * value) {
    OSL_ASSERT(text.is() && value != 0);
    *value = rtl::OString(text.begin, text.length).toDouble();
        //TODO: check valid lexical representation
    return true;
}

bool parseValue(Span const & text, rtl::OUString * value) {
    OSL_ASSERT(text.is() && value != 0);
    *value = xmldata::convertFromUtf8(text);
    return true;
}

bool parseValue(Span const & text, css::uno::Sequence< sal_Int8 > * value) {
    OSL_ASSERT(text.is() && value != 0);
    if ((text.length & 1) != 0) {
        return false;
    }
    comphelper::SequenceAsVector< sal_Int8 > seq;
    for (sal_Int32 i = 0; i != text.length;) {
        int n1;
        int n2;
        if (!parseHexDigit(text.begin[i++], &n1) ||
            !parseHexDigit(text.begin[i++], &n2))
        {
            return false;
        }
        seq.push_back(static_cast< sal_Int8 >((n1 << 4) | n2));
    }
    *value = seq.getAsConstList();
    return true;
}

template< typename T > css::uno::Any parseSingleValue(Span const & text) {
    T val;
    if (!parseValue(text, &val)) {
        throw css::uno::RuntimeException(
            rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("invalid value")),
            css::uno::Reference< css::uno::XInterface >());
    }
    return css::uno::makeAny(val);
}

template< typename T > css::uno::Any parseListValue(
    rtl::OString const & separator, Span const & text)
{
    comphelper::SequenceAsVector< T > seq;
    Span sep;
    if (separator.getLength() == 0) {
        sep = Span(RTL_CONSTASCII_STRINGPARAM(" "));
    } else {
        sep = Span(separator.getStr(), separator.getLength());
    }
    if (text.length != 0) {
        for (Span t(text);;) {
            sal_Int32 i = rtl_str_indexOfStr_WithLength(
                t.begin, t.length, sep.begin, sep.length);
            T val;
            if (!parseValue(Span(t.begin, i == -1 ? t.length : i), &val)) {
                throw css::uno::RuntimeException(
                    rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("invalid value")),
                    css::uno::Reference< css::uno::XInterface >());
            }
            seq.push_back(val);
            if (i < 0) {
                break;
            }
            t.begin += i + sep.length;
            t.length -= i + sep.length;
        }
    }
    return css::uno::makeAny(seq.getAsConstList());
}

css::uno::Any parseValue(
    rtl::OString const & separator, Span const & text, Type type)
{
    switch (type) {
    case TYPE_ANY:
        throw css::uno::RuntimeException(
            rtl::OUString(
                RTL_CONSTASCII_USTRINGPARAM("invalid value of type any")),
            css::uno::Reference< css::uno::XInterface >());
    case TYPE_BOOLEAN:
        return parseSingleValue< sal_Bool >(text);
    case TYPE_SHORT:
        return parseSingleValue< sal_Int16 >(text);
    case TYPE_INT:
        return parseSingleValue< sal_Int32 >(text);
    case TYPE_LONG:
        return parseSingleValue< sal_Int64 >(text);
    case TYPE_DOUBLE:
        return parseSingleValue< double >(text);
    case TYPE_STRING:
        return parseSingleValue< rtl::OUString >(text);
    case TYPE_HEXBINARY:
        return parseSingleValue< css::uno::Sequence< sal_Int8 > >(text);
    case TYPE_BOOLEAN_LIST:
        return parseListValue< sal_Bool >(separator, text);
    case TYPE_SHORT_LIST:
        return parseListValue< sal_Int16 >(separator, text);
    case TYPE_INT_LIST:
        return parseListValue< sal_Int32 >(separator, text);
    case TYPE_LONG_LIST:
        return parseListValue< sal_Int64 >(separator, text);
    case TYPE_DOUBLE_LIST:
        return parseListValue< double >(separator, text);
    case TYPE_STRING_LIST:
        return parseListValue< rtl::OUString >(separator, text);
    case TYPE_HEXBINARY_LIST:
        return parseListValue< css::uno::Sequence< sal_Int8 > >(
            separator, text);
    default:
        OSL_ASSERT(false);
        throw css::uno::RuntimeException(
            rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("this cannot happen")),
            css::uno::Reference< css::uno::XInterface >());
    }
}

}

ValueParser::ValueParser(int layer): layer_(layer) {}

ValueParser::~ValueParser() {}

XmlReader::Text ValueParser::getTextMode() const {
    if (node_.is()) {
        switch (state_) {
        case STATE_TEXT:
            if (!items_.empty()) {
                break;
            }
            // fall through
        case STATE_IT:
            return
                (type_ == TYPE_STRING || type_ == TYPE_STRING_LIST ||
                 separator_.getLength() != 0)
                ? XmlReader::TEXT_RAW : XmlReader::TEXT_NORMALIZED;
        default:
            break;
        }
    }
    return XmlReader::TEXT_NONE;
}

bool ValueParser::startElement(
    XmlReader & reader, XmlReader::Namespace ns, Span const & name)
{
    if (!node_.is()) {
        return false;
    }
    switch (state_) {
    case STATE_TEXT:
        if (ns == XmlReader::NAMESPACE_NONE &&
            name.equals(RTL_CONSTASCII_STRINGPARAM("it")) &&
            isListType(type_) && separator_.getLength() == 0)
        {
            pad_.clear();
                // before first <it>, characters are not ignored; assume they
                // are only whitespace
            state_ = STATE_IT;
            return true;
        }
        // fall through
    case STATE_IT:
        if (ns == XmlReader::NAMESPACE_NONE &&
            name.equals(RTL_CONSTASCII_STRINGPARAM("unicode")) &&
            (type_ == TYPE_STRING || type_ == TYPE_STRING_LIST))
        {
            sal_Int32 scalar = -1;
            for (;;) {
                XmlReader::Namespace attrNs;
                Span attrLn;
                if (!reader.nextAttribute(&attrNs, &attrLn)) {
                    break;
                }
                if (attrNs == XmlReader::NAMESPACE_OOR &&
                    attrLn.equals(RTL_CONSTASCII_STRINGPARAM("scalar")))
                {
                    if (!parseValue(reader.getAttributeValue(true), &scalar)) {
                        scalar = -1;
                    }
                    break;
                }
            }
            if (scalar >= 0 && scalar < 0x20 && scalar != 0x09 &&
                scalar != 0x0A && scalar != 0x0D)
            {
                char c = static_cast< char >(scalar);
                pad_.add(&c, 1);
            } else if (scalar == 0xFFFE) {
                pad_.add(RTL_CONSTASCII_STRINGPARAM("\xEF\xBF\xBE"));
            } else if (scalar == 0xFFFF) {
                pad_.add(RTL_CONSTASCII_STRINGPARAM("\xEF\xBF\xBF"));
            } else {
                throw css::uno::RuntimeException(
                    (rtl::OUString(
                        RTL_CONSTASCII_USTRINGPARAM(
                            "bad unicode scalar attribute in ")) +
                     reader.getUrl()),
                    css::uno::Reference< css::uno::XInterface >());
            }
            state_ = State(state_ + 1);
            return true;
        }
        break;
    default:
        break;
    }
    throw css::uno::RuntimeException(
        (rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("bad member <")) +
         xmldata::convertFromUtf8(name) +
         rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("> in ")) + reader.getUrl()),
        css::uno::Reference< css::uno::XInterface >());
}

bool ValueParser::endElement() {
    if (!node_.is()) {
        return false;
    }
    switch (state_) {
    case STATE_TEXT:
        {
            css::uno::Any value;
            if (items_.empty()) {
                value = parseValue(separator_, pad_.get(), type_);
                pad_.clear();
            } else {
                switch (type_) {
                case TYPE_BOOLEAN_LIST:
                    value = convertItems< sal_Bool >();
                    break;
                case TYPE_SHORT_LIST:
                    value = convertItems< sal_Int16 >();
                    break;
                case TYPE_INT_LIST:
                    value = convertItems< sal_Int32 >();
                    break;
                case TYPE_LONG_LIST:
                    value = convertItems< sal_Int64 >();
                    break;
                case TYPE_DOUBLE_LIST:
                    value = convertItems< double >();
                    break;
                case TYPE_STRING_LIST:
                    value = convertItems< rtl::OUString >();
                    break;
                case TYPE_HEXBINARY_LIST:
                    value = convertItems< css::uno::Sequence< sal_Int8 > >();
                    break;
                default:
                    OSL_ASSERT(false); // this cannot happen
                    break;
                }
                items_.clear();
            }
            switch (node_->kind()) {
            case Node::KIND_PROPERTY:
                dynamic_cast< PropertyNode * >(node_.get())->setValue(
                    layer_, value);
                break;
            case Node::KIND_LOCALIZED_PROPERTY:
                {
                    NodeMap::iterator i(
                        node_->getMembers().find(localizedName_));
                    if (i == node_->getMembers().end()) {
                        node_->getMembers().insert(
                            NodeMap::value_type(
                                localizedName_,
                                new LocalizedValueNode(layer_, value)));
                    } else {
                        dynamic_cast< LocalizedValueNode * >(i->second.get())->
                            setValue(layer_, value);
                    }
                }
                break;
            default:
                OSL_ASSERT(false); // this cannot happen
                break;
            }
            separator_ = rtl::OString();
            node_.clear();
        }
        break;
    case STATE_TEXT_UNICODE:
    case STATE_IT_UNICODE:
        state_ = State(state_ - 1);
        break;
    case STATE_IT:
        items_.push_back(
            parseValue(rtl::OString(), pad_.get(), elementType(type_)));
        pad_.clear();
        state_ = STATE_TEXT;
        break;
    }
    return true;
}

void ValueParser::characters(Span const & text) {
    if (node_.is()) {
        OSL_ASSERT(state_ == STATE_TEXT || state_ == STATE_IT);
        pad_.add(text.begin, text.length);
    }
}

void ValueParser::start(
    rtl::Reference< Node > const & node, rtl::OUString const & localizedName)
{
    OSL_ASSERT(node.is() && !node_.is());
    node_ = node;
    localizedName_ = localizedName;
    state_ = STATE_TEXT;
}

int ValueParser::getLayer() const {
    return layer_;
}

template< typename T > css::uno::Any ValueParser::convertItems() {
    css::uno::Sequence< T > seq(items_.size());
    for (sal_Int32 i = 0; i < seq.getLength(); ++i) {
        OSL_VERIFY(items_[i] >>= seq[i]);
    }
    return css::uno::makeAny(seq);
}

}

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