////////////////////////////////////////////////////////////////////////////////
//
//  Microsoft Research Singularity
//
//  Copyright (c) Microsoft Corporation.  All rights reserved.
//
//  File:   TokenDefinition.cs
//
//  Note:   Declares a class describing MetaData tokens, so that the
//          metadata parser's consts can all be easily identified.
using System;
using System.Diagnostics;
using System.Xml;
using Bartok.MSIL;

public class TokenDefinition
{
    // name of the metadata attribute to match
    public readonly string attribute;

    // name of the xml tag to output
    public readonly string xmlTagName;

    // labels for constructor fields during output
    public readonly string [] constructorFields;

    // labels for properties to be renamed during output
    // each property uses two addresses [default, new name]
    public readonly string [] propertyReplacements;

    // fields to be added with default values, if not named
    public readonly string [] defaultFields;

    // basic constructor; everything is public, but now it's all very easy to
    // keep track of.
    public TokenDefinition(string xmlTagName,
                           string attribute,
                           string [] constructorFields,
                           string [] propertyReplacements,
                           string [] defaultFields)
    {
        this.xmlTagName = xmlTagName;
        this.attribute = attribute;
        this.constructorFields = constructorFields;
        if (propertyReplacements != null && (propertyReplacements.Length % 2) != 0) {
            Console.WriteLine("{0} has bad propertyReplacements", xmlTagName);
        }
        this.propertyReplacements = propertyReplacements;
        if (defaultFields != null && (defaultFields.Length % 2) != 0) {
            Console.WriteLine("{0} has bad defaultFields", xmlTagName);
        }
        this.defaultFields = defaultFields;
    }

    public TokenDefinition(string xmlTagName,
                           string attribute,
                           string [] constructorFields,
                           string [] propertyReplacements)
        : this(xmlTagName, attribute, constructorFields, propertyReplacements, new string[0])
    {
    }

    public TokenDefinition(string xmlTagName,
                           string attribute,
                           string [] constructorFields)
        : this(xmlTagName, attribute, constructorFields, new string[0], new string[0])
    {
    }

    public TokenDefinition(string xmlTagName,
                           string attribute)
        : this(xmlTagName, attribute, new string[0], new string[0], new string[0])
    {
    }

    public string FindPropertyReplacement(string name)
    {
        if (propertyReplacements != null) {
            for (int pair = 0; pair < propertyReplacements.Length; pair += 2) {
                if (propertyReplacements[pair] == name) {
                    return propertyReplacements[pair + 1];
                }
            }
        }
        return name;
    }
}

public class TriggerDefinition : TokenDefinition
{
    // name of the category
    public readonly string categoryName;

    // name of the class that the decorated class must directly inherit from
    public readonly string derivesFromClass;

    public TriggerDefinition(string xmlTagName,
                             string categoryName,
                             string attribute,
                             string derivesFromClass,
                             string [] constructorFields,
                             string [] propertyReplacements,
                             string [] defaultFields)
        : base(xmlTagName,
               attribute,
               constructorFields,
               propertyReplacements,
               defaultFields)
    {
        this.derivesFromClass = derivesFromClass;
        this.categoryName = categoryName;
    }

    public TriggerDefinition(string xmlTagName,
                             string categoryName,
                             string attribute,
                             string derivesFromClass,
                             string [] constructorFields,
                             string [] propertyReplacements)
        : base(xmlTagName, attribute, constructorFields, propertyReplacements, new string[0])
    {
        this.derivesFromClass = derivesFromClass;
        this.categoryName = categoryName;
    }

    public TriggerDefinition(string xmlTagName,
                             string categoryName,
                             string attribute,
                             string derivesFromClass,
                             string [] constructorFields)
        : base(xmlTagName, attribute, constructorFields, new string[0], new string[0])
    {
        this.derivesFromClass = derivesFromClass;
        this.categoryName = categoryName;
    }

    public TriggerDefinition(string xmlTagName,
                             string categoryName,
                             string attribute,
                             string derivesFromClass)
        : base(xmlTagName, attribute, new string[0], new string[0], new string[0])
    {
        this.derivesFromClass = derivesFromClass;
        this.categoryName = categoryName;
    }
}

public class ClassDefinition : TokenDefinition
{
    public ClassDefinition(string xmlTagName,
                           string attribute,
                           string [] constructorFields,
                           string [] propertyReplacements,
                           string [] defaultFields)
        : base(xmlTagName,
               attribute,
               constructorFields,
               propertyReplacements,
               defaultFields)
    {
    }

    public ClassDefinition(string xmlTagName,
                           string attribute,
                           string [] constructorFields,
                           string [] propertyReplacements)
        : base(xmlTagName, attribute, constructorFields, propertyReplacements, new string[0])
    {
    }

    public ClassDefinition(string xmlTagName,
                           string attribute,
                           string [] constructorFields)
        : base(xmlTagName, attribute, constructorFields, new string[0], new string[0])
    {
    }

    public ClassDefinition(string xmlTagName,
                           string attribute)
        : base(xmlTagName, attribute, new string[0], new string[0], new string[0])
    {
    }
}

public class ParameterDefinition : FieldDefinition
{
    public ParameterDefinition(string xmlGroup,
                           string xmlTagName,
                           string attribute,
                           string matchClassType,
                           string [] constructorFields)
        : base(xmlGroup,
               xmlTagName,
               attribute,
               matchClassType,
               constructorFields)
        {
        }
    
}
public class FieldDefinition : TokenDefinition
{
    // name of the group to put the field into.
    public readonly string xmlGroup;

    // name of the class that the decorated class must be (fields only)
    public readonly string matchClassType;

    public FieldDefinition(string xmlGroup,
                           string xmlTagName,
                           string attribute,
                           string matchClassType,
                           string [] constructorFields,
                           string [] propertyReplacements,
                           string [] defaultFields)
        : base(xmlTagName,
               attribute,
               constructorFields,
               propertyReplacements,
               defaultFields)
    {
        this.xmlGroup = xmlGroup;
        this.matchClassType = matchClassType;
    }

    public FieldDefinition(string xmlGroup,
                           string xmlTagName,
                           string attribute,
                           string matchClassType,
                           string [] constructorFields,
                           string [] propertyReplacements)
        : base(xmlTagName,
               attribute,
               constructorFields,
               propertyReplacements,
               new string[0])
    {
        this.xmlGroup = xmlGroup;
        this.matchClassType = matchClassType;
    }

    public FieldDefinition(string xmlGroup,
                           string xmlTagName,
                           string attribute,
                           string matchClassType,
                           string [] constructorFields)
        : base(xmlTagName,
               attribute,
               constructorFields,
               new string[0],
               new string[0])
    {
        this.xmlGroup = xmlGroup;
        this.matchClassType = matchClassType;
    }

    public FieldDefinition(string xmlGroup,
                           string xmlTagName,
                           string attribute,
                           string matchClassType)
        : base(xmlTagName,
               attribute,
               new string[0],
               new string[0],
               new string[0])
    {
        this.xmlGroup = xmlGroup;
        this.matchClassType = matchClassType;
    }
}

public class EndpointDefinition : FieldDefinition
{
    public EndpointDefinition(string xmlGroup,
                              string xmlTagName,
                              string attribute,
                              string matchClassType)
        : base(xmlGroup,
               xmlTagName,
               attribute,
               matchClassType,
               new string[0],
               new string[0],
               new string[0])
    {
    }
    public EndpointDefinition(string xmlGroup,
                              string xmlTagName,
                              string attribute,
                              string matchClassType,
                              string[] constructorFields)
        : base(xmlGroup,
                xmlTagName,
                attribute,
                matchClassType,
                constructorFields,
                new string[0],
                new string[0]) 
    {
    }
    
    public virtual void AddContractNameAttribute(ManifestBuilder mfb, 
                                                 XmlNode node,
                                                 MetaDataCustomAttribute data, 
                                                 string contractName)
    {
        mfb.AddAttribute(node, "contractName", contractName);
    }
}

public class ServiceEndpointDefinition : EndpointDefinition
{
    public ServiceEndpointDefinition(string xmlGroup,
                                     string xmlTagName,
                                     string attribute,
                                     string matchClassType)
        : base(xmlGroup, xmlTagName, attribute, matchClassType)
    {
    }

    public override void AddContractNameAttribute(ManifestBuilder mfb,
                                                  XmlNode node,
                                                  MetaDataCustomAttribute data,
                                                  string contractName)
    {
        // takes endpoint type as argument
        if (data.FixedArgs == null || data.FixedArgs.Length != 1) {
            mfb.Error("ServiceEndpoint attribute must have 1 type argument");
            return;
        }

        // now make it easy to split into name=value pairs by doing some
        // string replacing
        string ctorArgument = "contractName=" + data.FixedArgs[0];
        ctorArgument = ctorArgument.Replace("+", " endpointEnd=");
        ctorArgument = ctorArgument.Replace(", V", " v");
        ctorArgument = ctorArgument.Replace(", C", " c");
        ctorArgument = ctorArgument.Replace(", P", " p");
        ctorArgument = ctorArgument.Replace(",", " assembly=");

        // now we can split on ' ' and '=' to get name/value pairs
        string [] endpointConfig = ctorArgument.Split(' ');

        // grab only the contractName, ignore the rest
#if false
        foreach (string attrib in endpointConfig) {
            string [] pair = attrib.Split('=');
            if (pair.Length == 2) {
                string name = pair[0];
                string value = pair[1];
                AddAttribute(node, name, value);
            }
        }
#else
        string [] pair = endpointConfig[0].Split('=');
        Debug.Assert(pair.Length == 2);
        string name = pair[0];
        Debug.Assert(name == "contractName");
        string value = pair[1];
        base.AddContractNameAttribute(mfb, node, data, value);
#endif
    }
}
