/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include <stdio.h>

typedef unsigned nsresult;
typedef unsigned uint32_t;
typedef unsigned nsXPCVariant;

#if defined(WIN32)
#  define NS_IMETHOD virtual nsresult __stdcall
#  define NS_IMETHODIMP nsresult __stdcall
#else
#  define NS_IMETHOD virtual nsresult
#  define NS_IMETHODIMP nsresult
#endif

class base {
 public:
  NS_IMETHOD ignored() = 0;
};

class foo : public base {
 public:
  NS_IMETHOD callme1(int i, int j) = 0;
  NS_IMETHOD callme2(int i, int j) = 0;
  NS_IMETHOD callme3(int i, int j) = 0;
};

class bar : public foo {
 public:
  NS_IMETHOD ignored() override;
  NS_IMETHOD callme1(int i, int j) override;
  NS_IMETHOD callme2(int i, int j) override;
  NS_IMETHOD callme3(int i, int j) override;
};

class baz : public base {
 public:
  NS_IMETHOD ignored() override;
  NS_IMETHOD callme1() override;
  NS_IMETHOD callme2() override;
  NS_IMETHOD callme3() override;
  void setfoo(foo* f) { other = f; }

  foo* other;
};
NS_IMETHODIMP baz::ignored() { return 0; }

NS_IMETHODIMP bar::ignored() { return 0; }

NS_IMETHODIMP bar::callme1(int i, int j) {
  printf("called bar::callme1 with: %d %d\n", i, j);
  return 15;
}

NS_IMETHODIMP bar::callme2(int i, int j) {
  printf("called bar::callme2 with: %d %d\n", i, j);
  return 25;
}

NS_IMETHODIMP bar::callme3(int i, int j) {
  printf("called bar::callme3 with: %d %d\n", i, j);
  return 35;
}

void docall(foo* f, int i, int j) { f->callme1(i, j); }

/***************************************************************************/
#if defined(WIN32)

static int __stdcall PrepareAndDispatch(baz* self, uint32_t methodIndex,
                                        uint32_t* args,
                                        uint32_t* stackBytesToPop) {
  fprintf(stdout, "PrepareAndDispatch (%p, %d, %p)\n", (void*)self, methodIndex,
          (void*)args);
  foo* a = self->other;
  int p1 = (int)*args;
  int p2 = (int)*(args + 1);
  int out = 0;
  switch (methodIndex) {
    case 1:
      out = a->callme1(p1, p2);
      break;
    case 2:
      out = a->callme2(p1, p2);
      break;
    case 3:
      out = a->callme3(p1, p2);
      break;
  }
  *stackBytesToPop = 2 * 4;
  return out;
}

#  ifndef __GNUC__
static __declspec(naked) void SharedStub(void) {
  __asm {
        push ebp  // set up simple stack frame
        mov  ebp, esp  // stack has: ebp/vtbl_index/retaddr/this/args
        push ecx  // make room for a ptr
        lea  eax, [ebp-4]  // pointer to stackBytesToPop
        push eax
        lea  ecx, [ebp+16]  // pointer to args
        push ecx
        mov  edx, [ebp+4]  // vtbl_index
        push edx
        mov  eax, [ebp+12]  // this
        push eax
        call PrepareAndDispatch
        mov  edx, [ebp+8]  // return address
        mov  ecx, [ebp-4]  // stackBytesToPop
        add  ecx, 12  // for this, the index, and ret address
        mov  esp, ebp
        pop  ebp
        add  esp, ecx  // fix up stack pointer
        jmp  edx  // simulate __stdcall return
  }
}

// these macros get expanded (many times) in the file #included below
#    define STUB_ENTRY(n)                                     \
      __declspec(naked) nsresult __stdcall baz::callme##n() { \
        __asm push n __asm jmp SharedStub                     \
      }

#  else /* __GNUC__ */

#    define STUB_ENTRY(n)                                            \
      nsresult __stdcall baz::callme##n() {                          \
        uint32_t *args, stackBytesToPop;                             \
        int result = 0;                                              \
        baz* obj;                                                    \
        __asm__ __volatile__(                                        \
            "leal   0x0c(%%ebp), %0\n\t" /* args */                  \
            "movl   0x08(%%ebp), %1\n\t" /* this */                  \
            : "=r"(args), "=r"(obj));                                \
        result = PrepareAndDispatch(obj, n, args, &stackBytesToPop); \
        fprintf(stdout, "stub returning: %d\n", result);             \
        fprintf(stdout, "bytes to pop:  %d\n", stackBytesToPop);     \
        return result;                                               \
      }

#  endif /* ! __GNUC__ */

#else
/***************************************************************************/
// just Linux_x86 now. Add other later...

static int PrepareAndDispatch(baz* self, uint32_t methodIndex, uint32_t* args) {
  foo* a = self->other;
  int p1 = (int)*args;
  int p2 = (int)*(args + 1);
  switch (methodIndex) {
    case 1:
      a->callme1(p1, p2);
      break;
    case 2:
      a->callme2(p1, p2);
      break;
    case 3:
      a->callme3(p1, p2);
      break;
  }
  return 1;
}

#  define STUB_ENTRY(n)                                      \
    nsresult baz::callme##n() {                              \
      void* method = PrepareAndDispatch;                     \
      nsresult result;                                       \
      __asm__ __volatile__(                                  \
          "leal   0x0c(%%ebp), %%ecx\n\t" /* args */         \
          "pushl  %%ecx\n\t"                                 \
          "pushl  $" #n                                      \
          "\n\t"                          /* method index */ \
          "movl   0x08(%%ebp), %%ecx\n\t" /* this */         \
          "pushl  %%ecx\n\t"                                 \
          "call   *%%edx" /* PrepareAndDispatch */           \
          : "=a"(result)  /* %0 */                           \
          : "d"(method)   /* %1 */                           \
          : "memory");                                       \
      return result;                                         \
    }

#endif
/***************************************************************************/

STUB_ENTRY(1)
STUB_ENTRY(2)
STUB_ENTRY(3)

int main() {
  foo* a = new bar();
  baz* b = new baz();

  /* here we make the global 'check for alloc failure' checker happy */
  if (!a || !b) return 1;

  foo* c = (foo*)b;

  b->setfoo(a);
  c->callme1(1, 2);
  c->callme2(2, 4);
  c->callme3(3, 6);

  return 0;
}
