/**************************************************************
 * 
 * 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;
}
	
