/**************************************************************
 *
 * 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
 *
 * Unless required by applicable law or agreed to in writing,
 * software 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.
 *
 *************************************************************/



// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_idlc.hxx"
#include <idlc/astunion.hxx>
#include <idlc/astbasetype.hxx>
#include <idlc/errorhandler.hxx>

#include "registry/version.h"
#include "registry/writer.hxx"

using namespace ::rtl;

AstUnion::AstUnion(const ::rtl::OString& name, AstType* pDiscType, AstScope* pScope)
    : AstStruct(NT_union, name, NULL, pScope)
    , m_pDiscriminantType(pDiscType)
    , m_discExprType(ET_long)
{
    AstBaseType* pBaseType;

    if ( !pDiscType )
    {
        m_pDiscriminantType = NULL;
        m_discExprType = ET_none;
        return;
    }
    /*
     * If the discriminator type is a predefined type
     * then install the equivalent coercion target type in
     * the pd_udisc_type field.
     */
    if ( pDiscType->getNodeType() == NT_predefined )
    {
        pBaseType = (AstBaseType*)pDiscType;
        if ( !pBaseType )
        {
            m_pDiscriminantType = NULL;
            m_discExprType = ET_none;
            return;
        }
        m_pDiscriminantType = pDiscType;
        switch (pBaseType->getExprType())
        {
            case ET_long:
            case ET_ulong:
            case ET_short:
            case ET_ushort:
            case ET_char:
            case ET_boolean:
                m_discExprType = pBaseType->getExprType();
                break;
            default:
                m_discExprType = ET_none;
                m_pDiscriminantType = NULL;
                break;
        }
    } else
        if (pDiscType->getNodeType() == NT_enum)
        {
            m_discExprType = ET_any;
            m_pDiscriminantType = pDiscType;
        } else
        {
            m_discExprType = ET_none;
            m_pDiscriminantType = NULL;
        }

    if ( !m_pDiscriminantType )
        idlc()->error()->error2(EIDL_DISC_TYPE, this, pDiscType);
}

AstUnion::~AstUnion()
{
}

AstDeclaration* AstUnion::addDeclaration(AstDeclaration* pDecl)
{
    if ( pDecl->getNodeType() == NT_union_branch )
    {
        AstUnionBranch* pBranch = (AstUnionBranch*)pDecl;
        if ( lookupBranch(pBranch) )
        {
            idlc()->error()->error2(EIDL_MULTIPLE_BRANCH, this, pDecl);
            return NULL;
        }
    }

    return AstScope::addDeclaration(pDecl);
}

AstUnionBranch* AstUnion::lookupBranch(AstUnionBranch* pBranch)
{
    AstUnionLabel* pLabel = NULL;

    if ( pBranch )
        pLabel = pBranch->getLabel();

    if ( pLabel )
    {
        if (pLabel->getLabelKind() == UL_default)
            return lookupDefault();
        if (m_discExprType == ET_any)
            /* CONVENTION: indicates enum discr */
            return lookupEnum(pBranch);
        return lookupLabel(pBranch);
    }
    return NULL;
}

AstUnionBranch* AstUnion::lookupDefault(sal_Bool bReportError)
{
    DeclList::const_iterator iter = getIteratorBegin();
    DeclList::const_iterator end = getIteratorEnd();
    AstUnionBranch      *pBranch = NULL;
    AstDeclaration      *pDecl = NULL;

    while ( iter != end )
    {
        pDecl = *iter;
        if ( pDecl->getNodeType() == NT_union_branch )
        {
            pBranch = (AstUnionBranch*)pDecl;
            if (pBranch == NULL)
            {
                ++iter;
                continue;
            }
            if ( pBranch->getLabel() != NULL &&
                 pBranch->getLabel()->getLabelKind() == UL_default)
            {
                if ( bReportError )
                    idlc()->error()->error2(EIDL_MULTIPLE_BRANCH, this, pBranch);
                return pBranch;
            }
        }
        ++iter;
    }
    return NULL;
}

AstUnionBranch* AstUnion::lookupLabel(AstUnionBranch* pBranch)
{
    AstUnionLabel* pLabel = pBranch->getLabel();

    if ( !pLabel->getLabelValue() )
        return pBranch;
//  pLabel->getLabelValue()->setExprValue(pLabel->getLabelValue()->coerce(m_discExprType, sal_False));
    AstExprValue* pLabelValue = pLabel->getLabelValue()->coerce(
        m_discExprType, sal_False);
    if ( !pLabelValue )
    {
        idlc()->error()->evalError(pLabel->getLabelValue());
        return pBranch;
    } else
    {
        pLabel->getLabelValue()->setExprValue(pLabelValue);
    }

    DeclList::const_iterator iter = getIteratorBegin();
    DeclList::const_iterator end = getIteratorEnd();
    AstUnionBranch* pB = NULL;
    AstDeclaration* pDecl = NULL;

    while ( iter != end )
    {
        pDecl = *iter;
        if ( pDecl->getNodeType() == NT_union_branch )
        {
            pB = (AstUnionBranch*)pDecl;
            if ( !pB )
            {
                ++iter;
                continue;
            }
            if ( pB->getLabel() != NULL &&
                 pB->getLabel()->getLabelKind() == UL_label &&
                 pB->getLabel()->getLabelValue()->compare(pLabel->getLabelValue()) )
            {
                idlc()->error()->error2(EIDL_MULTIPLE_BRANCH, this, pBranch);
                return pBranch;
            }
        }
        ++iter;
    }
    return NULL;
}

AstUnionBranch* AstUnion::lookupEnum(AstUnionBranch* pBranch)
{
    AstDeclaration const * pType = resolveTypedefs(m_pDiscriminantType);
    if ( pType->getNodeType() != NT_enum )
        return NULL;

    AstUnionLabel* pLabel = pBranch->getLabel();
    AstExpression* pExpr = pLabel->getLabelValue();
    if ( !pExpr )
        return pBranch;

    /*
     * Expecting a symbol label
     */
    if ( pExpr->getCombOperator() != EC_symbol)
    {
        idlc()->error()->enumValExpected(this);
        return pBranch;
    }

    /*
     * See if the symbol defines a constant in the discriminator enum
     */
    AstEnum* pEnum = (AstEnum*)pType;
    AstDeclaration* pDecl = pEnum->lookupByName(*pExpr->getSymbolicName());
    if ( pDecl == NULL || pDecl->getScope() != pEnum)
    {
        idlc()->error()->enumValLookupFailure(this, pEnum, *pExpr->getSymbolicName());
        return pBranch;
    }


    DeclList::const_iterator iter = getIteratorBegin();
    DeclList::const_iterator end = getIteratorEnd();
    AstUnionBranch* pB = NULL;
    pDecl = NULL;

    while ( iter != end )
    {
        pDecl = *iter;
        if ( pDecl->getNodeType() == NT_union_branch )
        {
            pB = (AstUnionBranch*)pDecl;
            if ( !pB )
            {
                ++iter;
                continue;
            }
            if ( pB->getLabel() != NULL &&
                 pB->getLabel()->getLabelKind() == UL_label &&
                 pB->getLabel()->getLabelValue()->compare(pLabel->getLabelValue()) )
            {
                idlc()->error()->error2(EIDL_MULTIPLE_BRANCH, this, pBranch);
                return pBranch;
            }
        }
        ++iter;
    }
    return NULL;
}

sal_Bool AstUnion::dump(RegistryKey& rKey)
{
    RegistryKey localKey;
    if (rKey.createKey( OStringToOUString(getFullName(), RTL_TEXTENCODING_UTF8 ), localKey))
    {
        fprintf(stderr, "%s: warning, could not create key '%s' in '%s'\n",
                idlc()->getOptions()->getProgramName().getStr(),
                getFullName().getStr(), OUStringToOString(rKey.getRegistryName(), RTL_TEXTENCODING_UTF8).getStr());
        return sal_False;
    }

    sal_uInt16 nMember = getNodeCount(NT_union_branch);

    OUString emptyStr;
    typereg::Writer aBlob(
        TYPEREG_VERSION_0, getDocumentation(), emptyStr, RT_TYPE_UNION,
        false, OStringToOUString(getRelativName(), RTL_TEXTENCODING_UTF8), 1,
        nMember, 0, 0);
    aBlob.setSuperTypeName(
        0,
        OStringToOUString(
            getDiscrimantType()->getScopedName(), RTL_TEXTENCODING_UTF8));

    if ( nMember > 0 )
    {
        DeclList::const_iterator iter = getIteratorBegin();
        DeclList::const_iterator end = getIteratorEnd();
        AstDeclaration* pDecl = NULL;
        AstUnionBranch* pBranch = NULL;
        AstUnionBranch* pDefault = lookupDefault(sal_False);
        AstUnionLabel*  pLabel = NULL;
        AstExprValue*   pExprValue = NULL;
        RTConstValue    aConst;
        RTFieldAccess   access = RT_ACCESS_READWRITE;
        OUString    docu;
        sal_uInt16  index = 0;
        if ( pDefault )
            index = 1;

        sal_Int64   disc = 0;
        while ( iter != end )
        {
            pDecl = *iter;
            if ( pDecl->getNodeType() == NT_union_branch )
            {
                pBranch = (AstUnionBranch*)pDecl;
                if (pBranch == pDefault)
                {
                    ++iter;
                    continue;
                }

                pLabel = pBranch->getLabel();
                pExprValue = pLabel->getLabelValue()->coerce(ET_hyper, sal_False);
                aConst.m_type = RT_TYPE_INT64;
                aConst.m_value.aHyper = pExprValue->u.hval;
                if ( aConst.m_value.aHyper > disc )
                    disc = aConst.m_value.aHyper;

                aBlob.setFieldData(
                    index++, pBranch->getDocumentation(), emptyStr, RT_ACCESS_READWRITE,
                    OStringToOUString(
                        pBranch->getLocalName(), RTL_TEXTENCODING_UTF8),
                    OStringToOUString(
                        pBranch->getType()->getRelativName(),
                        RTL_TEXTENCODING_UTF8),
                    aConst);
            }
            ++iter;
        }

        if ( pDefault )
        {
            access = RT_ACCESS_DEFAULT;
            aConst.m_type = RT_TYPE_INT64;
            aConst.m_value.aHyper = disc + 1;
            aBlob.setFieldData(
                0, pDefault->getDocumentation(), emptyStr, RT_ACCESS_DEFAULT,
                OStringToOUString(
                    pDefault->getLocalName(), RTL_TEXTENCODING_UTF8),
                OStringToOUString(
                    pDefault->getType()->getRelativName(),
                    RTL_TEXTENCODING_UTF8),
                aConst);
        }
    }

    sal_uInt32 aBlobSize;
    void const * pBlob = aBlob.getBlob(&aBlobSize);

    if (localKey.setValue(OUString(), RG_VALUETYPE_BINARY,
                            (RegValue)pBlob, aBlobSize))
    {
        fprintf(stderr, "%s: warning, could not set value of key \"%s\" in %s\n",
                idlc()->getOptions()->getProgramName().getStr(),
                getFullName().getStr(), OUStringToOString(localKey.getRegistryName(), RTL_TEXTENCODING_UTF8).getStr());
        return sal_False;
    }

    return sal_True;
}

AstUnionBranch::AstUnionBranch(AstUnionLabel* pLabel, AstType const * pType, const ::rtl::OString& name, AstScope* pScope)
    : AstMember(NT_union_branch, pType, name, pScope)
    , m_pLabel(pLabel)
{
}

AstUnionBranch::~AstUnionBranch()
{
    if ( m_pLabel )
        delete m_pLabel;
}

AstUnionLabel::AstUnionLabel(UnionLabel labelKind, AstExpression* pExpr)
    : m_label(labelKind)
    , m_pLabelValue(pExpr)
{
    if ( m_pLabelValue )
        m_pLabelValue->evaluate(EK_const);
}

AstUnionLabel::~AstUnionLabel()
{
    if ( m_pLabelValue )
        delete m_pLabelValue;
}