/*!
 *  Make the given enum type implements the protocol RawRepresentable.
 *  TODO Only one member is added for test case, add the rest members
 */
static void makeRawRepresentable(const TypeBuilderPtr& type, GlobalScope* global)
{
    type->addProtocol(global->RawRepresentable());
    //var rawValue: RawValue { get }
    TypePtr RawValue = type->getParentType();
    SymbolPlaceHolderPtr rawValue(new SymbolPlaceHolder(L"rawValue", RawValue, SymbolPlaceHolder::R_PROPERTY, SymbolFlagMember | SymbolFlagReadable));
    type->addMember(rawValue);
}
void DeclarationAnalyzer::visitEnum(const EnumDefPtr& node)
{
    if(isLazyDeclared(node))
        return;
    TypeBuilderPtr type = static_pointer_cast<TypeBuilder>(getOrDefineType(node));
    ScopeGuard scope(symbolRegistry, type->getScope());
    SCOPED_SET(ctx->currentType, type);
    SCOPED_SET(ctx->currentExtension, nullptr);
    SCOPED_SET(ctx->currentFunction, nullptr);


    GlobalScope* global = symbolRegistry->getGlobalScope();
    SymbolScope* local = symbolRegistry->getCurrentScope();
    //check if it's raw value enum
    bool isRawValues = false;
    if(node->numParents() > 0)
    {
        TypePtr firstParent = lookupType(node->getParent(0));
        if(firstParent)
        {
            Type::Category  category = firstParent->getCategory();
            isRawValues = category == Type::Struct || category == Type::Enum || category == Type::Aggregate || category == Type::Class;
            if(isRawValues)
                type->setParentType(firstParent);
        }
    }

    //initialize enum's cases
    if(isRawValues)
    {
        if(!node->numCases())
        {
            error(node, Errors::E_ENUM_WITH_NO_CASES_CANNOT_DECLARE_A_RAW_TYPE);
            return;
        }
        //Add RawRepresentable protocol if it's not implemented
        if(!type->canAssignTo(global->RawRepresentable()))
        {
            makeRawRepresentable(type, global);
        }

        TypePtr rawType = type->getParentType();
        assert(rawType != nullptr);
        bool integerConvertible = rawType->canAssignTo(global->IntegerLiteralConvertible());
        for(auto c : node->getCases())
        {
            if(!c.value && !integerConvertible)
            {
                //Enum cases require explicit raw values when the raw type is not integer literal convertible
                error(node, Errors::E_ENUM_CASES_REQUIRE_EXPLICIT_RAW_VALUES_WHEN_THE_RAW_TYPE_IS_NOT_INTEGER_LITERAL_CONVERTIBLE);
                return;
            }
            if(c.value)
            {
                if(dynamic_pointer_cast<TupleType>(c.value))
                {
                    error(node, Errors::E_ENUM_WITH_RAW_TYPE_CANNOT_HAVE_CASES_WITH_ARGUMENTS);
                    return;
                }
            }
            type->addEnumCase(c.name, global->Void());
            //register it to scope
            local->addSymbol(SymbolPlaceHolderPtr(new SymbolPlaceHolder(c.name, type, SymbolPlaceHolder::R_PARAMETER, SymbolFlagReadable | SymbolFlagMember | SymbolFlagStatic | SymbolFlagInitialized)));
        }
    }
    else
    {
        //it's an associated-values enum
        for(auto c : node->getCases())
        {
            TypePtr associatedType = global->Void();
            if(c.value)
            {
                TypeNodePtr typeNode = dynamic_pointer_cast<TypeNode>(c.value);
                if(!typeNode)
                {
                    error(node, Errors::E_ENUM_CASE_CANNOT_HAVE_A_RAW_VALUE_IF_THE_ENUM_DOES_NOT_HAVE_A_RAW_TYPE);
                    return;
                }
                assert(typeNode != nullptr);
                associatedType = resolveType(typeNode, true);
            }
            type->addEnumCase(c.name, associatedType);
            if(associatedType == global->Void())
                local->addSymbol(SymbolPlaceHolderPtr(new SymbolPlaceHolder(c.name, type, SymbolPlaceHolder::R_PARAMETER, SymbolFlagReadable | SymbolFlagMember | SymbolFlagStatic | SymbolFlagInitialized)));
        }
    }
    //check member declaration of enum
    {
        SCOPED_SET(ctx->flags, (ctx->flags & (~SemanticContext::FLAG_PROCESS_IMPLEMENTATION)) | SemanticContext::FLAG_PROCESS_DECLARATION);
        for (const DeclarationPtr &decl : *node)
        {
            bool isStatic = decl->hasModifier(DeclarationModifiers::Class) || decl->hasModifier(DeclarationModifiers::Static);
            if (!isStatic && (decl->getNodeType() == NodeType::ValueBindings))
            {
                error(node, Errors::E_ENUMS_MAY_NOT_CONTAIN_STORED_PROPERTIES);
                continue;
            }
            decl->accept(this);
        }
        //add a default initializer for raw value enum
        if(isRawValues)
        {
            vector<Parameter> params = {Parameter(L"rawValue", false, type->getParentType())};
            TypePtr initType = Type::newFunction(params, global->Void(), false, nullptr);
            initType->setFlags(SymbolFlagAllocatingInit, true);
            static_pointer_cast<TypeBuilder>(initType)->setDeclaringType(type);
            initType->setFlags(SymbolFlagInit | SymbolFlagFailableInitializer | SymbolFlagMember | SymbolFlagAllocatingInit);
            FunctionSymbolPtr init(new FunctionSymbol(L"init", initType, FunctionRoleInit, nullptr));
            declarationFinished(init->getName(), init, nullptr);
        }
    }
    if(type->getParentType() != nullptr)
        verifyAccessLevel(node, type->getParentType(), DeclarationTypeEnum, ComponentTypeRawType);
    validateDeclarationModifiers(node);
    //visitImplementation(node);
}