// CustomAttribute.cpp
#include "stdj2cpp.h"

#include "ArrayType.h"
#include "Boolean.h"
#include "Byte.h"
#include "ByteBuffer.h"
#include "CSharpKeywords.h"
#include "Character.h"
#include "Class.h"
#include "ClassWriter.h"
#include "Double.h"
#include "Float.h"
#include "ILAsmKeywords.h"
#include "ILAsmWriter.h"
#include "Integer.h"
#include "Long.h"
#include "Main.h"
#include "ManagedCppWriter.h"
#include "MemberInfo.h"
#include "MemberRef.h"
#include "MethodInfo.h"
#include "ModuleInfo.h"
#include "Name.h"
#include "NamedArg.h"
#include "ParameterInfo.h"
#include "PrintStream.h"
#include "Short.h"
#include "SignatureInfo.h"
#include "System.h"
#include "Type.h"
#include "TypeInfo.h"
#include "TypeValue.h"
#include "CustomAttribute.h"

/**
Decompile a custom attribute
*/

CustomAttribute::CustomAttribute(int mdMethodRef, Array<Object*>* args, Array<NamedArg*>* namedArgs, Object* target) : 
Object() , classfile(null)
{
    methodRefToken = mdMethodRef;
    this->args = args;
    this->namedArgs = namedArgs;
    this->target = target;
    classfile = getTypeInfo(target);
    constructor = classfile->getMethodRef(methodRefToken);
}

TypeInfo* CustomAttribute::getTypeInfo(Object* target)
{
    TypeInfo* typedef_ = null;
    if (instanceof(target, TypeInfo)) {
        typedef_ = (TypeInfo*)target;
    }
    else if (instanceof(target, MemberInfo)) {
        typedef_ = (((MemberInfo*)target))->getDeclaringType();
    }
    else if (instanceof(target, ParameterInfo)) {
        typedef_ = (((ParameterInfo*)target)->parent)->getDeclaringType();
    }
    else if (instanceof(target, ModuleInfo)) {
        typedef_ = (((ModuleInfo*)target))->getGlobalType();
    }
    else {
        (System::err)->println(StringBuffer("CustomAttribute target yet to handle: ") + (target->getClass())->getName());
    }
    return typedef_;
}

Array<Object*>* CustomAttribute::getArgs()
{
    return args;
}

Array<NamedArg*>* CustomAttribute::getNamedArgs()
{
    return namedArgs;
}

void CustomAttribute::emitStringAsILAsm(ByteBuffer* buf, String str)
{
    if (str == null) {
        buf->appendByte(-1);
        return;
    }
    int len = str.length();
    if (len <= 127) {
        buf->appendByte((jbyte)len);
    }
    else if (len <= 16383) {
        buf->appendByte((jbyte)(len >> (int)8 | 128));
        buf->appendByte((jbyte)(len & 255));
    }
    else {
        buf->appendByte((jbyte)(len >> (int)24 | 192));
        buf->appendByte((jbyte)(len >> (int)16 & 255));
        buf->appendByte((jbyte)(len >> (int)8 & 255));
        buf->appendByte((jbyte)(len & 255));
    }
    Name* nm = Name::fromString(str);
    Array<jbyte>* utfs = nm->toUtf();
    buf->appendBytes(utfs);
}

/**
write a constant value according to ILAsm custom attribute blob format
*/
void CustomAttribute::writeValueAsILAsm(ByteBuffer* tmp, Type* t, Object* val)
{
    if (t->isBoolean()) {
        boolean z = (((Boolean*)val))->booleanValue();
        tmp->appendByte((z) ? 1 : 0);
    }
    else if (t->isI1()) {
        jbyte b = (((Byte*)val))->byteValue();
        tmp->appendByte(b);
    }
    else if (t->isU1()) {
        short s = (((Short*)val))->shortValue();
        tmp->appendByte((jbyte)s);
    }
    else if (t->isChar()) {
        jchar c = (((Character*)val))->charValue();
        tmp->appendCharWithLittleEndian(c);
    }
    else if (t->isU2()) {
        int n = (((Integer*)val))->intValue();
        tmp->appendShortWithLittleEndian((short)n);
    }
    else if (t->isI2()) {
        short s = (((Short*)val))->shortValue();
        tmp->appendShortWithLittleEndian(s);
    }
    else if (t->isI4()) {
        int n = (((Integer*)val))->intValue();
        tmp->appendIntWithLittleEndian(n);
    }
    else if (t->isU4()) {
        jlong l = (((Long*)val))->longValue();
        tmp->appendIntWithLittleEndian((int)l);
    }
    else if (t->isI8() || t->isU8()) {
        jlong l = (((Long*)val))->longValue();
        tmp->appendLongWithLittleEndian(l);
    }
    else if (t->isR4()) {
        float f = (((Float*)val))->floatValue();
        tmp->appendFloatWithLittleEndian(f);
    }
    else if (t->isR8()) {
        double d = (((Double*)val))->doubleValue();
        tmp->appendDoubleWithLittleEndian(d);
    }
    else if (t->isString() || t->equals(Type::System_Type)) {
        emitStringAsILAsm(tmp, *(String*)val);
    }
    else if (t->isSZArray()) {
        Array<Object*>* values = (Array<Object*>*)val;
        if (values == null) {
            tmp->appendByte(-1);
        }
        else {
            tmp->appendIntWithLittleEndian(values->length);
            for (int i = 0; i < values->length ; i++) {
                writeValueAsILAsm(tmp, t->getElementType(), (*values)[i]);
            }
        }
    }
    else if (t->isValueType()) {
        TypeInfo* enumtype = t->loadDefinition();
        Type* underlyingType = enumtype->getEnumUnderlyingType();
        if (underlyingType == null) {
            underlyingType = Type::I4;
        }
        writeValueAsILAsm(tmp, underlyingType, val);
    }
    else if (t->isObject()) {
        TypeValue* tv = (TypeValue*)val;
        jbyte tc = tv->typecode;
        Object* value = tv->value;
        tmp->appendByte(tc);
        Type* serializedType = null;
        if (tc == remotesoft_cormd_TypeValue_SERIALIZATION_TYPE_TAGGED_OBJECT) {
            (System::err)->println("yet to handle: SERIALIZATION_TYPE_TAGGED_OBJECT.");
        }
        else if (tc == remotesoft_cormd_TypeValue_SERIALIZATION_TYPE_TYPE) {
            serializedType = Type::STRING;
        }
        else if (tc == remotesoft_cormd_TypeValue_SERIALIZATION_TYPE_ENUM) {
            String s = tv->enumTypename;
            emitStringAsILAsm(tmp, s);
            TypeInfo* enumtype = (ModuleInfo::currentAssembly)->loadType(s);
            Type* underlyingType = (enumtype != null) ? enumtype->getEnumUnderlyingType() : null;
            if (underlyingType == null) {
                underlyingType = Type::I4;
            }
            serializedType = underlyingType;
            value = tv->value;
        }
        else if (tc == remotesoft_cormd_Type_ELEMENT_TYPE_SZARRAY) {
            serializedType = new ArrayType(tc, Type::createSimpleType(tv->elemtypecode));
            tmp->appendByte(tv->elemtypecode);
        }
        else {
            serializedType = Type::createSimpleType(tc);
        }
        writeValueAsILAsm(tmp, serializedType, value);
    }
}

/**
Emit type for named arg
*/
void CustomAttribute::emitType(ByteBuffer* buf, Type* t)
{
    jbyte typecode = t->typecode;
    buf->appendByte(typecode);
    if (typecode == remotesoft_cormd_TypeValue_SERIALIZATION_TYPE_ENUM) {
        emitStringAsILAsm(buf, t->getAssemblyQualifiedName());
    }
    else if (typecode == remotesoft_cormd_Type_ELEMENT_TYPE_SZARRAY) {
        emitType(buf, t->getElementType());
    }
}

void CustomAttribute::outputAsILAsm(ByteBuffer* buf)
{
    buf->appendName(ILAsmKeywords::_custom);
    buf->appendByte(' ');
    constructor->outputAsILAsm(buf);
    buf->appendByte(' ');
    buf->appendByte('=');
    buf->appendByte(' ');
    buf->appendByte('(');
    ByteBuffer* tmp = new ByteBuffer();
    tmp->appendShortWithLittleEndian((short)1);
    Array<Type*>* argTypes = (constructor->getSignature())->getArgTypes();
    for (int i = 0; i < argTypes->length ; i++) {
        Type* t = (*argTypes)[i];
        writeValueAsILAsm(tmp, t, (*args)[i]);
    }
    tmp->appendShortWithLittleEndian((short)namedArgs->length);
    for (i = 0; i < namedArgs->length ; i++) {
        NamedArg* narg = (*namedArgs)[i];
        tmp->appendByte(narg->kind);
        emitType(tmp, narg->type);
        writeValueAsILAsm(tmp, Type::STRING, &narg->name);
        writeValueAsILAsm(tmp, new Type(narg->typecode), narg->value);
    }
    ILAsmWriter::writeHexBytes(buf, tmp->elems, tmp->length);
    buf->appendByte(' ');
    buf->appendByte(')');
}

void CustomAttribute::writeValue(ByteBuffer* buf, Type* t, Object* val)
{
    if (t->equals(Type::System_Type)) {
        buf->appendName((ClassWriter::currentWriter)->mapKeyword(CSharpKeywords::_typeof));
        buf->appendByte('(');
        String fullyQualifiedName = *(String*)val;
        int comma = fullyQualifiedName.indexOf(',');
        if (comma > 0) {
            fullyQualifiedName = fullyQualifiedName.substring(0, comma);
        }
        buf->appendString(classfile->toSimpleName(fullyQualifiedName));
        buf->appendByte(')');
    }
    else if (t->isSZArray()) {
        if (!Main::writer::isCPP()) {
            buf->appendName(CSharpKeywords::_new);
            buf->appendByte(' ');
            buf->appendString(t->toString());
        }
        buf->appendByte('{');
        Array<Object*>* values = (Array<Object*>*)val;
        for (int j = 0; j < values->length ; j++) {
            if (j != 0) {
                buf->appendByte(',');
                buf->appendByte(' ');
            }
            writeValue(buf, t->getElementType(), (*values)[j]);
        }
        buf->appendByte('}');
    }
    else if (!t->isPrimitive() && t->isValueType() || t->typecode == remotesoft_cormd_TypeValue_SERIALIZATION_TYPE_ENUM) {
        if (instanceof(t, TypeInfo) && Main::writer::isCPP()) {
            ManagedCppWriter* writer = (ManagedCppWriter*)Main::writer;
            if (writer->hfile) {
                writer->insertHFileInclude((TypeInfo*)t);
            }
        }
        TypeInfo* enumtype = t->loadDefinition();
        Type* underlyingType = (enumtype != null) ? enumtype->getEnumUnderlyingType() : null;
        if (underlyingType == null) {
            underlyingType = Type::I4;
        }
        if (enumtype != null) {
            buf->appendString(classfile->toSimpleName(enumtype));
            if (Main::writer::isCPP()) {
                buf->appendAsciiString("::");
            }
            else {
                buf->appendByte('.');
            }
            buf->appendAsciiString((ClassWriter::currentWriter)->toLiteral(enumtype, val));
        }
        else {
            writeValue(buf, underlyingType, val);
        }
    }
    else if (t->isObject()) {
        TypeValue* tv = (TypeValue*)val;
        jbyte tc = tv->typecode;
        Object* value = tv->value;
        Type* serializedType = null;
        if (tc == remotesoft_cormd_TypeValue_SERIALIZATION_TYPE_TAGGED_OBJECT) {
            (System::err)->println("yet to handle: SERIALIZATION_TYPE_TAGGED_OBJECT.");
        }
        else if (tc == remotesoft_cormd_TypeValue_SERIALIZATION_TYPE_TYPE) {
            serializedType = Type::System_Type;
        }
        else if (tc == remotesoft_cormd_TypeValue_SERIALIZATION_TYPE_ENUM) {
            String s = tv->enumTypename;
            TypeInfo* enumtype = (ModuleInfo::currentAssembly)->loadType(s);
            Type* underlyingType = (enumtype != null) ? enumtype->getEnumUnderlyingType() : null;
            if (underlyingType == null) {
                underlyingType = Type::I4;
            }
            if (enumtype != null) {
                classfile->registerType(enumtype);
                buf->appendString(classfile->toSimpleName(enumtype));
                if (Main::writer::isCPP()) {
                    buf->appendAsciiString("::");
                }
                else {
                    buf->appendByte('.');
                }
                buf->appendAsciiString((ClassWriter::currentWriter)->toLiteral(enumtype, tv->value));
                return;
            }
            serializedType = underlyingType;
            value = tv->value;
        }
        else if (tc == remotesoft_cormd_Type_ELEMENT_TYPE_SZARRAY) {
            serializedType = new ArrayType(tc, Type::createSimpleType(tv->elemtypecode));
        }
        else {
            serializedType = Type::createSimpleType(tc);
        }
        writeValue(buf, serializedType, value);
    }
    else {
        if (t->isString() && val == null) {
            buf->appendAsciiString((ClassWriter::currentWriter)->nullLiteral());
        }
        else {
            buf->appendAsciiString((ClassWriter::currentWriter)->toLiteral(t, val));
        }
    }
}

/**
Generate a C# rep, e.g., [MyAttribute("Christina", "Zhang")]
*/
void CustomAttribute::output(ByteBuffer* buf, boolean isReturnTarget)
{
    if (ClassWriter::targetLanguage != remotesoft_salamander_ClassWriter_VB) {
        buf->appendByte('[');
    }
    if (isReturnTarget) {
        if (Main::writer::isCPP()) {
            buf->appendAsciiString("returnvalue: ");
        }
        else {
            buf->appendAsciiString("return: ");
        }
    }
    else if (classfile->isFakedGlobalType()) {
        buf->appendAsciiString("assembly: ");
    }
    output(buf);
}

/**
Generate a C# rep, e.g., [MyAttribute("Christina", "Zhang")]
*/
void CustomAttribute::output(ByteBuffer* buf, String target)
{
    if (ClassWriter::targetLanguage != remotesoft_salamander_ClassWriter_VB) {
        buf->appendByte('[');
    }
    buf->appendAsciiString(target);
    buf->appendByte(':');
    buf->appendByte(' ');
    output(buf);
}

void CustomAttribute::output(ByteBuffer* buf)
{
    buf->appendString(classfile->getFlatName(constructor->owner));
    if (Main::writer::isCPP() && (constructor->getSignature())->getArgTypes()->length == 0 && namedArgs->length == 0) {
        buf->appendByte(']');
        return;
    }
    buf->appendByte('(');
    Array<Type*>* argTypes = (constructor->getSignature())->getArgTypes();
    for (int i = 0; i < argTypes->length ; i++) {
        if (i != 0) {
            buf->appendByte(',');
            buf->appendByte(' ');
        }
        Type* t = (*argTypes)[i];
        writeValue(buf, t, (*args)[i]);
    }
    boolean first = true;
    for (i = 0; i < namedArgs->length ; i++) {
        if (argTypes->length != 0 || !first) {
            buf->appendByte(',');
            buf->appendByte(' ');
        }
        first = false;
        NamedArg* narg = (*namedArgs)[i];
        buf->appendString(narg->name);
        if (ClassWriter::targetLanguage == remotesoft_salamander_ClassWriter_VB) {
            buf->appendByte(':');
        }
        buf->appendByte('=');
        writeValue(buf, narg->type, narg->value);
    }
    buf->appendByte(')');
    if (ClassWriter::targetLanguage != remotesoft_salamander_ClassWriter_VB) {
        buf->appendByte(']');
    }
}

/**
Check if this is a System.ParamArrayAttribute
*/
boolean CustomAttribute::isParamArrayAttribute()
{
    if ((constructor->owner)->equals(Type::ParamArrayAttribute)) {
        return (constructor->signature)->getArgTypes()->length == 0;
    }
    else {
        return false;
    }
}

/**
Check if this is a System.ParamArrayAttribute
*/
boolean CustomAttribute::isDefaultMemberAttribute()
{
    return (constructor->owner)->equals(Type::DefaultMemberAttribute);
}

/**
Check if this is a System.Diagnostics.DebuggableAttribute
*/
boolean CustomAttribute::isDebuggableAttribute()
{
    return (constructor->owner)->equals(Type::DebuggableAttribute);
}

/**
Get the type that defines this attribute
*/
Type* CustomAttribute::getDeclaringType()
{
    return constructor->owner;
}


Object* CustomAttribute::clone()
{
    return new CustomAttribute(*this);
}
