#include <windows.h>
#include <iostream>
#include <cstdlib>
#include <atlbase.h> 
#include "matt_com.h"

using namespace std;

namespace matt_com {
char buffer[1024];
}

void matt_com::__com_assert(int cond, const char* file, int line)
{
   if(!cond) {
      memset(buffer,0,1024);
      sprintf(buffer,"ASSERT FAILED FILE %s ON LINE %d...",file,line);
      throw buffer;
   }
}

void matt_com::com_init()
{
   HRESULT hr = CoInitialize(NULL);
   COM_ASSERT(!FAILED(hr));
}

void matt_com::com_fin()
{
   CoUninitialize();
}

void* matt_com::create_object(const wchar_t* name)
{
   CLSID clsid;
   CComPtr<IDispatch>* ptr = new CComPtr<IDispatch>();
   HRESULT hr = CLSIDFromProgID(name, &clsid);
   if(!FAILED(hr)) {
      hr = CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER, IID_IDispatch, (void**)ptr);
      if(FAILED(hr)) {
         delete ptr;
         ptr = 0;
      }
   }
   else {
      delete ptr;
      ptr = 0;
   }
   return (void*)ptr;
}

void* matt_com::get_com_method(void* obj, const wchar_t* name)
{
   void* ret = 0;
   DISPID* dispID = new DISPID();
   OLECHAR* szMethod = (OLECHAR*)name;
   CComPtr<IDispatch>* cls = (CComPtr<IDispatch>*)obj;
   COM_ASSERT(cls != 0);
   HRESULT hr = (*cls)->GetIDsOfNames(IID_NULL, &szMethod, 1, LOCALE_USER_DEFAULT, dispID);
   if(!FAILED(hr))
      ret = (void*)dispID;
   else
      delete dispID;
   return ret;
}

void* matt_com::get_com_args(const char* fmt, ...)
{
   void* ret = 0;
   const char* ptr = fmt;
   char* vl = ((char*)(&fmt+1));
   int ival;
   wchar_t* str;
   int count = 0;
   CComPtr<IDispatch>* p;
   size_t offset = 0;
   for(const char* pch = fmt; *pch; pch++) {
      if(*pch == '%') {
         count++;
      }
   }
   VARIANTARG* args = new VARIANTARG[count];
   memset(args,0,sizeof(VARIANTARG)*count);
   while(*ptr) {
      if(*ptr == '%') {
         ptr++;
         if(*ptr == 'd') {
            ival = *((int*)vl);
            VariantInit(&args[offset]);
            args[offset].vt = VT_I4;
            args[offset].lVal = ival;
            offset++;
            vl += sizeof(size_t);
         }
         else if(*ptr == 's') {
            str = *((wchar_t**)vl);
            VariantInit(&args[offset]);
            BSTR bstrFilePath = SysAllocString(str);
            args[offset].vt = VT_BSTR;
            args[offset].bstrVal = bstrFilePath;
            offset++;
            vl += sizeof(size_t);
         }
         else if(*ptr == 'p') {
            p = *((CComPtr<IDispatch>**)vl);
            VariantInit(&args[offset]);
            args[offset].vt = VT_DISPATCH;
            args[offset].pdispVal = (*p);
            offset++;
            vl += sizeof(size_t);
         }
      }
      ptr++;
   }
   DISPPARAMS* dp = new DISPPARAMS();
   memset(dp,0,sizeof(DISPPARAMS));
   dp->cArgs = count;
   dp->rgvarg = args;
   dp->cNamedArgs = 0;
   ret = (void*)dp;
   return ret;
}

void* matt_com::object_call(void* obj, void* method, void* args)
{
   void* ret = 0;
   CComPtr<IDispatch>* cls = (CComPtr<IDispatch>*)obj;
   DISPID* dsp = (DISPID*)method;
   COM_ASSERT(cls != 0);
   COM_ASSERT(method != 0);
   VARIANT* Ret = new VARIANT();
   DISPPARAMS* dp = (DISPPARAMS*)args;
   COM_ASSERT(dp != 0);
   VariantInit(Ret);
   HRESULT hr = (*cls)->Invoke(*dsp, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, dp, Ret, NULL, NULL);
   if(!FAILED(hr)) {
      ret = (void*)Ret;
   }
   else {
      delete Ret;
   }
   release_args(args);
   return ret;
}

void matt_com::release_result(void* result) 
{
   VARIANT* Ret = (VARIANT*)result;
   COM_ASSERT(Ret != 0);
   VariantClear(Ret);
   delete Ret;
}

void matt_com::release_args(void* args)
{
   DISPPARAMS* dp = (DISPPARAMS*)args;
   COM_ASSERT(dp != 0);
   VARIANT* Var;
   for(int i = 0; i < dp->cArgs; i++) {
      Var = &dp->rgvarg[i];
      VariantClear(Var);
   }
   delete [] dp->rgvarg;
   delete dp;
}

void matt_com::release_method(void* method)
{
   DISPID* id = (DISPID*)method;
   COM_ASSERT(id != 0);
   delete id;
}

void matt_com::release_object(void* obj)
{
   CComPtr<IDispatch>* cls = (CComPtr<IDispatch>*)obj;
   COM_ASSERT(cls != 0);
   *cls = NULL;
   delete cls;
}

int matt_com::get_result_type(void* ptr)
{
   VARIANT* v = (VARIANT*)ptr;
   COM_ASSERT(v != 0);
   int ret = v->vt;
   return ret;
}

wchar_t* matt_com::get_result_string(void* ptr)
{
   VARIANT* v = (VARIANT*)ptr;
   COM_ASSERT(v != 0);
   COM_ASSERT(v->vt == STRING_RESULT);
   wchar_t* ret = v->bstrVal;
   return ret;
}

int matt_com::get_result_int(void* ptr)
{
   VARIANT* v = (VARIANT*)ptr;
   COM_ASSERT(v != 0);
   COM_ASSERT(v->vt == INT_RESULT);
   int ret = v->lVal;
   return ret;
}

void* matt_com::get_result_handle(void* ptr)
{
   VARIANT* v = (VARIANT*)ptr;
   COM_ASSERT(v != 0);
   COM_ASSERT(v->vt == HANDLE_RESULT);
   CComPtr<IDispatch>* p = new CComPtr<IDispatch>(v->pdispVal);
   void* ret = (void*)p;
   return ret;
}