#pragma once

namespace xlang
{
    static void write_license(writer& w)
    {
        auto format = R"(// WARNING: Please don't edit this file. It was generated by CS/WinRT v%
)";

        w.write(format, XLANG_VERSION_STRING);
    }

    static void write_using_modules(writer& w)
    {
        auto format = R"(
using System;
using System.Runtime.InteropServices;
)";
        w.write(format);
    }

    static void write_type_namespace(writer& w, std::string_view const& ns)
    {
        auto format = R"(
namespace @
{)";
        w.write(format, ns);
    }

    static void write_close_namespace(writer& w)
    {
        auto format = R"(
})";
        w.write(format);
    }

    static void write_enum_field(writer& w, Field const& field)
    {
        auto format = R"(
        % = %,)";

        if (auto constant = field.Constant())
        {
            w.write(format, field.Name(), *constant);
        }
    }

    static void write_flags_attribute(writer& w, TypeDef const& type)
    {
        auto format = R"(
    [Flags])";
        if (get_attribute(type, "System", "FlagsAttribute"))
        {
            w.write(format);
        }
    }

    static void write_enum(writer& w, TypeDef const& type)
    {
        auto format = R"(%
    public enum @ : % 
    {%
    })";
        auto fields = type.FieldList();
        w.write(format,
            bind<write_flags_attribute>(type),
            type.TypeName(),
            fields.first.Signature().Type(),
            bind_each<write_enum_field>(fields));
    }

    static void write_struct_field(writer& w, Field const& field)
    {
        auto format = R"(
        public % @;)";
        w.write(format, field.Signature().Type(), field.Name());
    }

    static void write_struct(writer& w, TypeDef const& type)
    {
        auto format = R"(
    [StructLayout(LayoutKind.Sequential)]
    public struct @
    {%
    })";
        w.write(format,
            type.TypeName(),
            bind_each<write_struct_field>(type.FieldList()));
    }

//     void write_method_param(writer& w, Param const& param)
//     {
//         w.write(", % %", param.Name(), param.Seq());
//     }

    template<typename T, typename U>
    static auto zip(const T& t, const U& u)
    {
        auto tc = begin(t);
        auto te = end(t);
        auto uc = begin(u);
        auto ue = end(u);
        std::vector< std::pair< decltype(*tc), decltype(*uc) > > r;
        for (; tc != te && uc != ue; ++tc, ++uc)
        {
            r.emplace_back(*tc, *uc);
        }
        XLANG_ASSERT(tc==te && uc==ue);
        return r;
    }

    static void write_method_params(writer& w, method_signature const& signature)
    {
        separator sep{ w };
        for (auto&&[param, param_signature] : signature.params())
        {
            sep();
            w.write("% %", "int"/*param_signature->Type()*/, param.Name());
        }
    }
    static void write_interface_method(writer& w, MethodDef const& method)
    {
        auto format = R"---(
        % @(%);)---";
        method_signature msig{ method };
        w.write(format,
            msig.return_signature(),
            method.Name(),
            bind<write_method_params>(msig)
        );
    }
    
    static void write_impl_method_locals(writer& w, RetTypeSig const& sig)
    {
        if (!sig)
        {
            return;
        }
        if ( is_value_type(sig.Type()) )
        {
            auto format = R"(
            var ret = new %();)";
            w.write(format, sig);
        }
        else
        {
            auto format = R"(
            void* ret;)";
            w.write(format);
        }
        
    }

    static void write_impl_method_return(writer& w, RetTypeSig const& sig)
    {
        if (!sig)
        {
            return;
        }
        if (is_value_type(sig.Type()))
        {
            auto format = R"(
            return ret;)";
            w.write(format);
        }
        else
        {
            auto format = R"(
            return new %(ret);)";
            w.write(format, sig);
        }
    }

    static void write_impl_method(writer& w, MethodDef const& method)
    {
        auto format = R"---(
        public % @(%)
        {
            var vslot = ((void***)instance)[0][%];
            var func = Marshal.GetDelegateForFunctionPointer<@>((IntPtr)vslot);%
            int hr = func(instance%);
            if (hr < 0)
                throw new Xlang.XlangException(hr);%
        })---";
        method_signature msig{ method };
        w.write(format,
            msig.return_signature(),
            method.Name(),
            bind<write_method_params>(msig),
            "0",
            "DEL/*FIXME*/",
            bind<write_impl_method_locals>(msig.return_signature()),
            ", 10/*FIXME*/",
            bind<write_impl_method_return>(msig.return_signature())
        );
    }

    static void write_impl_methods(writer& w, TypeDef const& type)
    {
        for (auto& iface : get_required_interfaces(type))
        {
            w.write( bind_each<write_impl_method>(iface.type.MethodList()) );
        }
    }


    static void write_impl_name(writer& w, std::string_view name)
    {
        auto start = (name.size() && name[0] == 'I') ? 1 : 0;
        w.write(name.substr(start));
    }

    static void write_interfaces_required(writer& w, std::vector<interface_info> const& ifaces)
    {
        auto sep = " : ";
        for (auto& iface : ifaces)
        {
            if (is_ptype(iface.type))
            {
                w.write("/*SKIP %*/", iface.type);
                continue;
            }
            w.write("%%", sep, iface.type);
            sep = ", ";
        }
    }

    static void write_guid_value(writer& w, std::vector<FixedArgSig> const& args)
    {
        using std::get;

        w.write_printf("%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X",
            get<uint32_t>(get<ElemSig>(args[0].value).value),
            get<uint16_t>(get<ElemSig>(args[1].value).value),
            get<uint16_t>(get<ElemSig>(args[2].value).value),
            get<uint8_t>(get<ElemSig>(args[3].value).value),
            get<uint8_t>(get<ElemSig>(args[4].value).value),
            get<uint8_t>(get<ElemSig>(args[5].value).value),
            get<uint8_t>(get<ElemSig>(args[6].value).value),
            get<uint8_t>(get<ElemSig>(args[7].value).value),
            get<uint8_t>(get<ElemSig>(args[8].value).value),
            get<uint8_t>(get<ElemSig>(args[9].value).value),
            get<uint8_t>(get<ElemSig>(args[10].value).value));
    }

    static void write_pure_interface(writer& w, TypeDef const& type)
    {
        auto format = R"---(
    public interface @
    {%
    })---";
        w.write(format,
            type.TypeName(),
            bind_each<write_interface_method>(type.MethodList()));
    }

    static void write_impl_interface(writer& w, TypeDef const& type)
    {
        auto format = R"---(
    [Guid("%")]
    public unsafe partial class %%
    {
        internal unsafe void* instance;
        internal %(void* native) { instance = native; }%
    })---";
        auto guid = get_attribute(type, "Windows.Foundation.Metadata", "GuidAttribute");
        w.write(format,
            bind<write_guid_value>(guid.Value().FixedArgs()),
            bind<write_impl_name>(type.TypeName()),
            bind<write_interfaces_required>(get_required_interfaces(type)),
            bind<write_impl_name>(type.TypeName()),
            bind<write_impl_methods>(type) );
    }

    static void write_interface(writer& w, TypeDef const& type)
    {
        if (is_ptype(type) || type.TypeName() == "IGetActivationFactory")
        {
            w.write("\n//SKIP %", type.TypeName());
            return;
        }

        write_pure_interface(w, type);
        write_impl_interface(w, type);
    }
}