using System;
using System.IO;
using System.Reflection;
using System.Collections.Generic;

public class Query
{
    private static StreamWriter outfile = null;

    public static int Main(string[] argv)
    { 
        if (argv.Length != 2) {
            Console.Error.WriteLine("Usage: query assembly-name output-file");
            return 1;
        }
        
        string name = argv[0];
        string outname = argv[1];
        Assembly ass;
        try {
            ass = Assembly.Load(name);
        } 
        catch(System.IO.IOException e) {
            Console.Error.WriteLine("Cannot load assembly {0}: {1}", name, e.Message);
            return 2;
        }

        try {
            outfile = new StreamWriter(outname);
        }
        catch(System.IO.IOException e) {
            Console.Error.WriteLine("Cannot open file {0}: {1}", outname, e.Message);
            return 3;
        }
        
        PrintAssembly(ass);
        outfile.Close();
        return 0;
    }

    private static void PrintAssembly(Assembly ass)
    {
        Type[] types = ass.GetTypes();
        outfile.WriteLine("# This file has been autogenerated by query.exe -- DO NOT EDIT");
        outfile.WriteLine("from rpython.translator.cli.query import ClassDesc");
        outfile.WriteLine("types = {}");
        foreach(Type t in types) {
            if (IgnoreType(t))
                continue;
            PrintType(t);
            outfile.WriteLine("types['{0}'] = desc", t.FullName);
            outfile.WriteLine("del desc");
        }
    }

    private static void PrintType(Type t)
    {
        outfile.WriteLine("desc = ClassDesc()");
        outfile.WriteLine("desc.Assembly = '{0}'", t.Assembly.FullName);
        outfile.WriteLine("desc.FullName = '{0}'", t.FullName);
        outfile.WriteLine("desc.AssemblyQualifiedName = '{0}'", t.AssemblyQualifiedName);
        outfile.WriteLine("desc.BaseType = '{0}'", GetBaseType(t));
        outfile.WriteLine("desc.IsArray = {0}", t.IsArray);
        outfile.WriteLine("desc.IsValueType = {0}", t.IsValueType);
        PrintMethods("desc.StaticMethods", t.GetMethods(BindingFlags.Static|BindingFlags.Public|BindingFlags.DeclaredOnly));
        PrintMethods("desc.Methods", t.GetMethods(BindingFlags.Instance|BindingFlags.Public|BindingFlags.DeclaredOnly));
        PrintFields("desc.StaticFields", t.GetFields(BindingFlags.Static|BindingFlags.Public|BindingFlags.DeclaredOnly));
    }

    private static string GetBaseType(Type t)
    {
        if (t == typeof(object))
            return "System.Object"; // special case for System.Object to avoid circular dependencies
        else if (t.BaseType == null)
             return "System.Object"; // the only known case is the BaseType of an interface
        else
            return t.BaseType.FullName;
    }

    private static string GetOOType(Type t)
    {
        if (t == null)
            return "";
        else if (t == typeof(void))
            return "ootype.Void";
        else if (t == typeof(int))
            return "ootype.Signed";
        else if (t == typeof(uint))
            return "ootype.Unsigned";
        else if (t == typeof(long))
            return "ootype.SignedLongLong";
        else if (t == typeof(ulong))
            return "ootype.UnsignedLongLong";
        else if (t == typeof(bool))
            return "ootype.Bool";
        else if (t == typeof(double))
            return "ootype.Float";
        else if (t == typeof(char))
            return "ootype.Char"; // maybe it should be unichar?
        else if (t == typeof(string))
            return "ootype.String";
        else {
            return t.FullName;
        }
    }

    private static void PrintMethods(string varname, MethodInfo[] methods)
    {
        outfile.WriteLine("{0} = [", varname);
        // MethodName, [ARGS], RESULT
        foreach(MethodInfo meth in methods) {
            if (IgnoreMethod(meth))
                continue;
            outfile.Write("    ('{0}', [", meth.Name);
            foreach(ParameterInfo par in meth.GetParameters()) {
                outfile.Write("'{0}'", GetOOType(par.ParameterType));
                outfile.Write(", ");
            }
            outfile.WriteLine("], '{0}'),", GetOOType(meth.ReturnType));
        }
        outfile.WriteLine("  ]");
    }

    private static void PrintFields(string varname, FieldInfo[] fields)
    {
        outfile.WriteLine("{0} = [", varname);
        // FieldName, RESULT
        foreach(FieldInfo fld in fields)
            outfile.WriteLine("    ('{0}', '{1}'),", fld.Name, GetOOType(fld.FieldType));
        outfile.WriteLine("  ]");
    }

    private static bool IgnoreMethod(MethodInfo meth)
    {
        if (!meth.IsPublic)
            return true;

        // ignore all SpecialName but properties getter/setter
        if (meth.IsSpecialName && !meth.Name.StartsWith("get_") && !meth.Name.StartsWith("set_"))
            return true;

        if (IgnoreType(meth.ReturnType))
            return true;
        foreach(ParameterInfo par in meth.GetParameters())
            if (IgnoreType(par.ParameterType))
                return true;

        return false;
    }

    private static bool IgnoreType(Type t)
    {
        return !t.IsPrimitive 
            && t != typeof(void)
            &&(t == typeof(System.Array) ||
               t.FullName == null ||
               t.FullName.StartsWith("System.Array+InternalArray") ||
               t.IsNested ||
               t.IsNotPublic ||
               t.IsByRef ||
               t.IsPointer ||
               t.IsGenericType ||
               t.IsGenericTypeDefinition);
    }
}
