/*
 * IDL Compiler
 *
 * Copyright 2002 Ove Kaaven
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#include <string.h>
#include <assert.h>
#include <ctype.h>
#include <signal.h>

#include "widl.h"
#include "utils.h"
#include "parser.h"
#include "header.h"

/* FIXME: support generation of stubless proxies */

static void write_stubdesc(void)
{
  fprintf(proxy, "const MIDL_STUB_DESC Object_StubDesc = {\n");
  fprintf(proxy, "    0,\n");
  fprintf(proxy, "    NdrOleAllocate,\n");
  fprintf(proxy, "    NdrOleFree,\n");
  fprintf(proxy, "    {0}, 0, 0, 0, 0,\n");
  fprintf(proxy, "    0 /* __MIDL_TypeFormatString.Format */\n");
  fprintf(proxy, "};\n");
  fprintf(proxy, "\n");
}

static void init_proxy(void)
{
  if (proxy) return;
  proxy = fopen(proxy_name, "w");
  fprintf(proxy, "/*** Autogenerated by WIDL %s - Do not edit ***/\n", WIDL_FULLVERSION);
  fprintf(proxy, "#include \"rpcproxy.h\"\n");
  fprintf(proxy, "#include \"%s\"\n", header_name);
  fprintf(proxy, "\n");
  write_stubdesc();
}

static void gen_proxy(type_t *iface, func_t *cur, int idx)
{
  var_t *def = cur->def;
  int has_ret = !is_void(def->type, def);

  write_type(proxy, def->type, def, def->tname);
  fprintf(proxy, " CALLBACK %s_", iface->name);
  write_name(proxy, def);
  fprintf(proxy, "_Proxy(\n");
  write_method_args(proxy, cur->args, iface->name);
  fprintf(proxy, ")\n");
  fprintf(proxy, "{\n");
  /* local variables */
  if (has_ret) {
    fprintf(proxy, "    ");
    write_type(proxy, def->type, def, def->tname);
    fprintf(proxy, " _Ret;\n");
  }
  fprintf(proxy, "    RPC_MESSAGE _Msg;\n");
  fprintf(proxy, "    MIDL_STUB_MESSAGE _StubMsg;\n");
  fprintf(proxy, "\n");

  /* FIXME: trace */
  /* FIXME: clear output vars? */

  fprintf(proxy, "    NdrProxyInitialize(This, &_Msg, &_StubMsg, &Object_StubDesc, %d);\n", idx);

  /* FIXME: size buffer */

  fprintf(proxy, "    NdrProxyGetBuffer(This, &_StubMsg);\n");

  /* FIXME: marshall */

  fprintf(proxy, "    NdrProxySendReceive(This, &_StubMsg);\n");

  /* FIXME: unmarshall */

  fprintf(proxy, "    NdrProxyFreeBuffer(This, &_StubMsg);\n");

  if (has_ret) {
    fprintf(proxy, "    return _Ret;\n");
  }
  fprintf(proxy, "}\n");
  fprintf(proxy, "\n");
}

static void gen_stub(type_t *iface, func_t *cur, char *cas)
{
  var_t *def = cur->def;
  var_t *arg;
  int has_ret = !is_void(def->type, def);

  fprintf(proxy, "void __RPC_STUB %s_", iface->name);
  write_name(proxy, def);
  fprintf(proxy, "_Stub(\n");
  fprintf(proxy, "    IRpcStubBuffer* This,\n");
  fprintf(proxy, "    IRpcChannelBuffer* pRpcChannelBuffer,\n");
  fprintf(proxy, "    PRPC_MESSAGE pRpcMessage,\n");
  fprintf(proxy, "    DWORD* pdwStubPhase)\n");
  fprintf(proxy, "{\n");
  /* local variables */
  if (has_ret) {
    fprintf(proxy, "    ");
    write_type(proxy, def->type, def, def->tname);
    fprintf(proxy, " _Ret;\n");
  }
  fprintf(proxy, "    %s* _This = (%s*)((CStdStubBuffer*)This)->pvServerObject;\n", iface->name, iface->name);
  fprintf(proxy, "    MIDL_STUB_MESSAGE _StubMsg;\n");
  arg = cur->args;
  while (arg) {
    fprintf(proxy, "    ");
    write_type(proxy, arg->type, arg, arg->tname);
    fprintf(proxy, " ");
    write_name(proxy, arg);
    fprintf(proxy, ";\n");
    arg = NEXT_LINK(arg);
  }
  fprintf(proxy, "\n");

  /* FIXME: trace */
  /* FIXME: clear output vars? */

  fprintf(proxy, "    NdrStubInitialize(pRpcMessage, &_StubMsg, &Object_StubDesc, pRpcChannelBuffer);\n");

  /* FIXME: unmarshall */

  fprintf(proxy, "    *pdwStubPhase = STUB_CALL_SERVER;\n");
  fprintf(proxy, "    ");
  if (has_ret) fprintf(proxy, "_Ret = ");
  fprintf(proxy, "%s_", iface->name);
  if (cas) fprintf(proxy, "%s_Stub", cas);
  else write_name(proxy, def);
  fprintf(proxy, "(_This");
  arg = cur->args;
  if (arg) {
    while (NEXT_LINK(arg)) arg = NEXT_LINK(arg);
    while (arg) {
      fprintf(proxy, ", ");
      write_name(proxy, arg);
      arg = PREV_LINK(arg);
    }
  }
  fprintf(proxy, ");\n");
  fprintf(proxy, "    *pdwStubPhase = STUB_MARSHAL;\n");

  /* FIXME: size buffer */

  fprintf(proxy, "    NdrStubGetBuffer(This, pRpcChannelBuffer, &_StubMsg);\n");

  /* FIXME: marshall */

  fprintf(proxy, "}\n");
  fprintf(proxy, "\n");
}

static int write_proxy_methods(type_t *iface)
{
  func_t *cur = iface->funcs;
  int i = 0;
  while (NEXT_LINK(cur)) cur = NEXT_LINK(cur);

  if (iface->ref) i = write_proxy_methods(iface->ref);
  while (cur) {
    var_t *def = cur->def;
    if (!is_callas(def->attrs)) {
      if (i) fprintf(proxy, ",\n     ");
      fprintf(proxy, "%s_", iface->name);
      write_name(proxy, def);
      fprintf(proxy, "_Proxy");
      i++;
    }
    cur = PREV_LINK(cur);
  }
  return i;
}

static int write_stub_methods(type_t *iface)
{
  func_t *cur = iface->funcs;
  int i = 0;
  while (NEXT_LINK(cur)) cur = NEXT_LINK(cur);

  if (iface->ref) i = write_stub_methods(iface->ref);
  else return i; /* skip IUnknown */
  while (cur) {
    var_t *def = cur->def;
    if (!is_local(def->attrs)) {
      if (i) fprintf(proxy, ",\n");
      fprintf(proxy, "    %s_", iface->name);
      write_name(proxy, def);
      fprintf(proxy, "_Stub");
      i++;
    }
    cur = PREV_LINK(cur);
  }
  return i;
}

typedef struct _if_list if_list;
struct _if_list {
  type_t *iface;
  DECL_LINK(if_list)
};

if_list *if_first;

void write_proxy(type_t *iface)
{
  int midx = -1, stubs;
  func_t *cur = iface->funcs;
  if_list *if_cur;

  if (!cur) {
    yywarning("%s has no methods", iface->name);
    return;
  }

  while (NEXT_LINK(cur)) cur = NEXT_LINK(cur);

  /* FIXME: check for [oleautomation], shouldn't generate proxies/stubs if specified */

  init_proxy();

  if_cur = xmalloc(sizeof(if_list));
  if_cur->iface = iface;
  INIT_LINK(if_cur);
  LINK(if_cur, if_first);
  if_first = if_cur;

  fprintf(proxy, "/*****************************************************************************\n");
  fprintf(proxy, " * %s interface\n", iface->name);
  fprintf(proxy, " */\n");
  while (cur) {
    var_t *def = cur->def;
    if (!is_local(def->attrs)) {
      var_t *cas = is_callas(def->attrs);
      char *cname = cas ? cas->name : NULL;
      int idx = cur->idx;
      if (cname) {
        func_t *m = iface->funcs;
        while (m && strcmp(get_name(m->def), cname))
          m = NEXT_LINK(m);
        idx = m->idx;
      }
      gen_proxy(iface, cur, idx);
      gen_stub(iface, cur, cname);
      if (midx == -1) midx = idx;
      else if (midx != idx) yyerror("method index mismatch in write_proxy");
      midx++;
    }
    cur = PREV_LINK(cur);
  }

  /* proxy vtable */
  fprintf(proxy, "const CINTERFACE_PROXY_VTABLE(%d) %sProxyVtbl = {\n", midx, iface->name);
  fprintf(proxy, "    {&IID_%s},\n", iface->name);
  fprintf(proxy, "    {");
  write_proxy_methods(iface);
  fprintf(proxy, "}\n");
  fprintf(proxy, "};\n");
  fprintf(proxy, "\n");

  /* stub vtable */
  fprintf(proxy, "static const PRPC_STUB_FUNCTION %s_table[] = {\n", iface->name);
  stubs = write_stub_methods(iface);
  fprintf(proxy, "\n");
  fprintf(proxy, "};\n");
  fprintf(proxy, "\n");
  fprintf(proxy, "const CInterfaceStubVtbl %sStubVtbl = {\n", iface->name);
  fprintf(proxy, "    {&IID_%s,\n", iface->name);
  fprintf(proxy, "     0,\n");
  fprintf(proxy, "     %d,\n", stubs+3);
  fprintf(proxy, "     &%s_table[-3]},\n", iface->name);
  fprintf(proxy, "    {CStdStubBuffer_METHODS}\n");
  fprintf(proxy, "};\n");
  fprintf(proxy, "\n");
}

void finish_proxy(void)
{
  if_list *lcur = if_first;
  if_list *cur;
  int c;

  if (!lcur) return;
  while (NEXT_LINK(lcur)) lcur = NEXT_LINK(lcur);

  fprintf(proxy, "const CInterfaceProxyVtbl* _XXX_ProxyVtblList[] = {\n");
  cur = lcur;
  while (cur) {
    fprintf(proxy, "    (CInterfaceProxyVtbl*)&%sProxyVtbl,\n", cur->iface->name);
    cur = PREV_LINK(cur);
  }
  fprintf(proxy, "    0\n");
  fprintf(proxy, "};\n");
  fprintf(proxy, "\n");

  fprintf(proxy, "const CInterfaceStubVtbl* _XXX_StubVtblList[] = {\n");
  cur = lcur;
  while (cur) {
    fprintf(proxy, "    (CInterfaceStubVtbl*)&%sStubVtbl,\n", cur->iface->name);
    cur = PREV_LINK(cur);
  }
  fprintf(proxy, "    0\n");
  fprintf(proxy, "};\n");
  fprintf(proxy, "\n");

  fprintf(proxy, "const PCInterfaceName _XXX_InterfaceNamesList[] = {\n");
  cur = lcur;
  while (cur) {
    fprintf(proxy, "    \"%s\",\n", cur->iface->name);
    cur = PREV_LINK(cur);
  }
  fprintf(proxy, "    0\n");
  fprintf(proxy, "};\n");
  fprintf(proxy, "\n");

  fprintf(proxy, "#define _XXX_CHECK_IID(n) IID_GENERIC_CHECK_IID(_XXX, pIID, n)\n");
  fprintf(proxy, "\n");
  fprintf(proxy, "int __stdcall _XXX_IID_Lookup(const IID* pIID, int* pIndex)\n");
  fprintf(proxy, "{\n");
  cur = lcur;
  c = 0;
  while (cur) {
    fprintf(proxy, "    if (!_XXX_CHECK_IID(%d)) {\n", c);
    fprintf(proxy, "        *pIndex = %d\n", c);
    fprintf(proxy, "        return 1;\n");
    fprintf(proxy, "    }\n");
    cur = PREV_LINK(cur);
    c++;
  }
  fprintf(proxy, "    return 0;\n");
  fprintf(proxy, "}\n");
  fprintf(proxy, "\n");

  fprintf(proxy, "const ExtendedProxyFileInfo XXX_ProxyFileInfo = {\n");
  fprintf(proxy, "    (PCInterfaceProxyVtblList*)&_XXX_ProxyVtblList,\n");
  fprintf(proxy, "    (PCInterfaceStubVtblList*)&_XXX_StubVtblList,\n");
  fprintf(proxy, "    (const PCInterfaceName*)&_XXX_InterfaceNamesList,\n");
  fprintf(proxy, "    0,\n");
  fprintf(proxy, "    &_XXX_IID_Lookup,\n");
  fprintf(proxy, "    %d,\n", c);
  fprintf(proxy, "    1\n");
  fprintf(proxy, "};\n");

  fclose(proxy);
}
