/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * The contents of this file are subject to the Netscape Public License
 * Version 1.0 (the "NPL"); you may not use this file except in
 * compliance with the NPL.  You may obtain a copy of the NPL at
 * http://www.mozilla.org/NPL/
 *
 * Software distributed under the NPL is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
 * for the specific language governing rights and limitations under the
 * NPL.
 *
 * The Initial Developer of this code under the NPL is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
 * Reserved.
 */

#ifndef XP_MAC
#include <sys/types.h>
#include <sys/stat.h>
#else
#include <stat.h>
#endif

#if !defined XP_UNIX && !defined XP_MAC && !defined XP_BEOS
#include <direct.h>
#endif

#include <fstream.h>
#include <ctype.h>

#include "XPCOMGen.h"
#include "Exceptions.h"
#include "plhash.h"
#include "IdlSpecification.h"
#include "IdlInterface.h"
#include "IdlVariable.h"
#include "IdlAttribute.h"
#include "IdlFunction.h"
#include "IdlParameter.h"

static const char *kFilePrefix = "nsIDOM";
static const char *kFileSuffix = "h";
static const char *kIfdefStr = "\n"
"#ifndef nsIDOM%s_h__\n"
"#define nsIDOM%s_h__\n\n";
static const char *kIncludeDefaultsStr = 
"#include \"nsISupports.h\"\n"
"#include \"nsString.h\"\n"
"#include \"nsIScriptContext.h\"\n";
static const char *kIncludeStr = "#include \"nsIDOM%s.h\"\n";
static const char *kIncludeJSStr = "#include \"jsapi.h\"\n";
static const char *kForwardClassStr = "class nsIDOM%s;\n";
static const char *kForwardXPIDLClassStr = "class %s;\n";
static const char *kUuidStr = 
"#define %s \\\n"
"%s\n\n";
static const char *kNoUuidStr = 
"--- IID GOES HERE ---\n\n";
static const char *kClassDeclStr = "class nsIDOM%s : ";
static const char *kBaseClassStr = "public nsIDOM%s";
static const char *kNoBaseClassStr = "public nsISupports";
static const char *kClassPrologStr = " {\npublic:\n";
static const char *kStaticIIDStr = "  static const nsIID& GetIID() { static nsIID iid = %s; return iid; }\n";
static const char *kEnumDeclBeginStr = "  enum {\n";
static const char *kEnumEntryStr = "    %s = %d%s\n";
static const char *kEnumDeclEndStr = "  };\n";
static const char *kGetterMethodDeclStr = "\n  NS_IMETHOD    Get%s(%s%s a%s)=0;\n";
static const char *kGetterMethodDeclNonVirtualStr = "  NS_IMETHOD    Get%s(%s%s a%s);  \\\n";
static const char *kGetterMethodForwardStr = "  NS_IMETHOD    Get%s(%s%s a%s) { return _to Get%s(a%s); } \\\n";
static const char *kSetterMethodDeclStr = "  NS_IMETHOD    Set%s(%s%s a%s)=0;\n";
static const char *kSetterMethodDeclNonVirtualStr = "  NS_IMETHOD    Set%s(%s%s a%s);  \\\n";
static const char *kSetterMethodForwardStr = "  NS_IMETHOD    Set%s(%s%s a%s) { return _to Set%s(a%s); } \\\n";
static const char *kMethodDeclStr = "\n  NS_IMETHOD    %s(%s)=0;\n";
static const char *kMethodDeclNonVirtualStr = "  NS_IMETHOD    %s(%s);  \\\n";
static const char *kMethodForwardStr = "  NS_IMETHOD    %s(%s) { return _to %s(%s); }  \\\n";
static const char *kParamStr = "%s a%s";
static const char *kCallParamStr = "a%s";
static const char *kDelimiterStr = ", ";
static const char *kEllipsisParamStr = "JSContext* cx, jsval* argv, PRUint32 argc";
static const char *kEllipsisCallStr = "cx, argv, argc";
static const char *kReturnStr = "%s%s aReturn";
static const char *kReturnCallStr = "aReturn";
static const char *kClassEpilogStr = "};\n\n";

static const char *kFactoryClassDeclBeginStr =
"class nsIDOM%sFactory : public nsISupports {\n"
"public:\n";
static const char *kConstructorDeclStr = "  NS_IMETHOD   CreateInstance(%snsIDOM%s **aReturn)=0;\n";
static const char *kFactoryClassDeclEndStr = "\n};\n\n";
static const char *kGlobalInitClassStr = "extern nsresult NS_Init%sClass(nsIScriptContext *aContext, nsIScriptGlobalObject *aGlobal);\n\n";
static const char *kInitClassStr = "extern \"C\" NS_DOM nsresult NS_Init%sClass(nsIScriptContext *aContext, void **aPrototype);\n\n";
static const char *kNewObjStr = "extern \"C\" NS_DOM nsresult NS_NewScript%s(nsIScriptContext *aContext, nsISupports *aSupports, nsISupports *aParent, void **aReturn);\n\n";
static const char *kMethodDeclMacroStr = "\n#define NS_DECL_IDOM%s   \\\n";
static const char *kMethodForwardMacroStr = "\n#define NS_FORWARD_IDOM%s(_to)  \\\n";
static const char *kEndifStr = "#endif // nsIDOM%s_h__\n";


XPCOMGen::XPCOMGen()
{
}

XPCOMGen::~XPCOMGen()
{
}

void     
XPCOMGen::Generate(char *aFileName,
                   char *aOutputDirName,
                   IdlSpecification &aSpec,
                   int aIsGlobal)
{
  mIsGlobal = aIsGlobal;

  int i, icount = aSpec.InterfaceCount();
  for (i = 0; i < icount; i++) {
    IdlInterface *iface = aSpec.GetInterfaceAt(i);
    
    if (iface) {
      if (!OpenFile(iface->GetName(), aOutputDirName, 
                    kFilePrefix, kFileSuffix)) {
        throw new CantOpenFileException(aFileName);
      }
  

      GenerateNPL();
      GenerateIfdef(*iface);
      GenerateIncludes(*iface);
      GenerateForwardDecls(*iface);
      
      GenerateGuid(*iface);
      GenerateClassDecl(*iface);
      GenerateStatic(*iface);
      GenerateEnums(*iface);
      GenerateMethods(*iface);
      GenerateEndClassDecl();
      GenerateDeclMacro(*iface);
      GenerateForwardMacro(*iface);
      if (i == 0) {
        GenerateEpilog(*iface, PR_TRUE);
      }
      else {
        GenerateEpilog(*iface, PR_FALSE);
      }
      CloseFile();
    }
  }
}


void     
XPCOMGen::GenerateIfdef(IdlInterface& aInterface)
{
  char buf[512];
  ofstream *file = GetFile();

  sprintf(buf, kIfdefStr, aInterface.GetName(), aInterface.GetName());
  *file << buf;
}

void     
XPCOMGen::GenerateIncludes(IdlInterface &aInterface)
{
  char buf[512];
  ofstream *file = GetFile();

  *file << kIncludeDefaultsStr;

  int b, bcount = aInterface.BaseClassCount();
  for (b = 0; b < bcount; b++) {
    sprintf(buf, kIncludeStr, aInterface.GetBaseClassAt(b));
    *file << buf;
  }

  int m, mcount = aInterface.FunctionCount();
  for (m = 0; m < mcount; m++) {
    IdlFunction *func = aInterface.GetFunctionAt(m);
      
    if (func->GetHasEllipsis()) {
      *file << kIncludeJSStr;
      break;
    }
  }
  
  *file << "\n";
}

static PRIntn 
ForwardDeclEnumerator(PLHashEntry *he, PRIntn i, void *arg)
{
  char buf[512];

  ofstream *file = (ofstream *)arg;
  switch ((Type)(int)(he->value)) {
  case TYPE_OBJECT:
  case TYPE_FUNC:
    sprintf(buf, kForwardClassStr, (char *)he->key);
    break;

  case TYPE_XPIDL_OBJECT:
    sprintf(buf, kForwardXPIDLClassStr, (char *)he->key);
    break;

  default:
    // uh oh...
    break;
  }

  *file << buf;
  
  return HT_ENUMERATE_NEXT;
}
 
void     
XPCOMGen::GenerateForwardDecls(IdlInterface &aInterface)
{
  ofstream *file = GetFile();
  EnumerateAllObjects(aInterface, (PLHashEnumerator)ForwardDeclEnumerator, 
                      file);
  *file << "\n";
}
 
void     
XPCOMGen::GenerateGuid(IdlInterface &aInterface)
{
  char buf[512];
  char uuid_buf[256];
  ofstream *file = GetFile();
  char *uuid;
  
  uuid = aInterface.GetIIDAt(0);
  GetInterfaceIID(uuid_buf, aInterface);
  if (NULL != uuid) {
    sprintf(buf, kUuidStr, uuid_buf, uuid);
  }
  else {
    sprintf(buf, kUuidStr, uuid_buf, kNoUuidStr);
  }
  *file << buf;
}
 
void     
XPCOMGen::GenerateClassDecl(IdlInterface &aInterface)
{
  char buf[512];
  ofstream *file = GetFile();
  
  sprintf(buf, kClassDeclStr, aInterface.GetName());
  *file << buf;

  if (aInterface.BaseClassCount() > 0) {
    int b, bcount = aInterface.BaseClassCount();
    for (b = 0; b < bcount; b++) {
      if (b > 0) {
        *file << kDelimiterStr;
      }
      sprintf(buf, kBaseClassStr, aInterface.GetBaseClassAt(b));
      *file << buf;
    }
  }
  else {
    *file << kNoBaseClassStr;
  }
  
  *file << kClassPrologStr;
}

void     
XPCOMGen::GenerateStatic(IdlInterface &aInterface)
{
  char buf[512];
  char uuid_buf[256];
  ofstream *file = GetFile();

  GetInterfaceIID(uuid_buf, aInterface);
  sprintf(buf, kStaticIIDStr, uuid_buf);
  *file << buf;
}

void     
XPCOMGen::GenerateEnums(IdlInterface &aInterface)
{
  char buf[512];
  ofstream *file = GetFile();

  if (aInterface.ConstCount() > 0) {
    int c, ccount = aInterface.ConstCount();

    *file << kEnumDeclBeginStr;
    
    for (c = 0; c < ccount; c++) {
      IdlVariable *var = aInterface.GetConstAt(c);
      
      if (NULL != var) {
        sprintf(buf, kEnumEntryStr, var->GetName(), var->GetLongValue(), 
                ((c < ccount-1) ? "," : ""));
        *file << buf;
      }
    }
    
    *file << kEnumDeclEndStr;
  }
}

void     
XPCOMGen::GenerateMethods(IdlInterface &aInterface)
{
  char buf[512];
  char name_buf[128];
  char type_buf[128];
  ofstream *file = GetFile();

  int a, acount = aInterface.AttributeCount();
  for (a = 0; a < acount; a++) {
    IdlAttribute *attr = aInterface.GetAttributeAt(a);

    GetVariableTypeForParameter(type_buf, *attr);
    GetCapitalizedName(name_buf, *attr);
    sprintf(buf, kGetterMethodDeclStr, name_buf, type_buf,
            attr->GetType() == TYPE_STRING ? "" : "*", name_buf);
    *file << buf;

    if (!attr->GetReadOnly()) {
      sprintf(buf, kSetterMethodDeclStr, name_buf, 
              attr->GetType() == TYPE_STRING ? "const " : "", type_buf, 
              name_buf);
      *file << buf;
    }
  }
  
  int m, mcount = aInterface.FunctionCount();
  for (m = 0; m < mcount; m++) {
    char param_buf[256];
    char *cur_param = param_buf;
    IdlFunction *func = aInterface.GetFunctionAt(m);

    // Don't generate anything for a constructor
    if (strcmp(func->GetName(), aInterface.GetName()) != 0) {
      int p, pcount = func->ParameterCount();
      for (p = 0; p < pcount; p++) {
        IdlParameter *param = func->GetParameterAt(p);
        
        if (p > 0) {
          strcpy(cur_param, kDelimiterStr);
          cur_param += strlen(kDelimiterStr);
        }
        
        GetParameterType(type_buf, *param);
        GetCapitalizedName(name_buf, *param);
        sprintf(cur_param, kParamStr, type_buf, name_buf);
        cur_param += strlen(cur_param);
      }
      
      if (func->GetHasEllipsis()) {
        if (pcount > 0) {
          strcpy(cur_param, kDelimiterStr);
          cur_param += strlen(kDelimiterStr);
        }
        sprintf(cur_param, kEllipsisParamStr);
        cur_param += strlen(cur_param);
      }
      
      IdlVariable *rval = func->GetReturnValue();
      if (rval->GetType() != TYPE_VOID) {
        if ((pcount > 0) || func->GetHasEllipsis()) {
          strcpy(cur_param, kDelimiterStr);
          cur_param += strlen(kDelimiterStr);
        }
        GetVariableTypeForParameter(type_buf, *rval);
        sprintf(cur_param, kReturnStr, type_buf,
                rval->GetType() == TYPE_STRING ? "" : "*");
      }
      else {
        *cur_param++ = '\0';
      }
      
      GetCapitalizedName(name_buf, *func);
      sprintf(buf, kMethodDeclStr, name_buf, param_buf);
      *file << buf;
    }
  }
}

void     
XPCOMGen::GenerateDeclMacro(IdlInterface &aInterface)
{
  char buf[512];
  char name_buf[128];
  char type_buf[128];
  ofstream *file = GetFile();
  
  strcpy(name_buf, aInterface.GetName());
  StrUpr(name_buf);
  sprintf(buf, kMethodDeclMacroStr, name_buf);
  *file << buf;

  int a, acount = aInterface.AttributeCount();
  for (a = 0; a < acount; a++) {
    IdlAttribute *attr = aInterface.GetAttributeAt(a);

    GetVariableTypeForParameter(type_buf, *attr);
    GetCapitalizedName(name_buf, *attr);
    sprintf(buf, kGetterMethodDeclNonVirtualStr, name_buf, type_buf,
            attr->GetType() == TYPE_STRING ? "" : "*", name_buf);
    *file << buf;

    if (!attr->GetReadOnly()) {
      sprintf(buf, kSetterMethodDeclNonVirtualStr, name_buf, 
              attr->GetType() == TYPE_STRING ? "const " : "", type_buf, 
              name_buf);
      *file << buf;
    }
  }
  
  int m, mcount = aInterface.FunctionCount();
  for (m = 0; m < mcount; m++) {
    char param_buf[256];
    char *cur_param = param_buf;
    IdlFunction *func = aInterface.GetFunctionAt(m);

    // Don't generate a constructor method
    if (strcmp(func->GetName(), aInterface.GetName()) == 0) {
      continue;
    }

    int p, pcount = func->ParameterCount();
    for (p = 0; p < pcount; p++) {
      IdlParameter *param = func->GetParameterAt(p);

      if (p > 0) {
        strcpy(cur_param, kDelimiterStr);
        cur_param += strlen(kDelimiterStr);
      }

      GetParameterType(type_buf, *param);
      GetCapitalizedName(name_buf, *param);
      sprintf(cur_param, kParamStr, type_buf, name_buf);
      cur_param += strlen(cur_param);
    }

    if (func->GetHasEllipsis()) {
      if (pcount > 0) {
        strcpy(cur_param, kDelimiterStr);
        cur_param += strlen(kDelimiterStr);
      }
      sprintf(cur_param, kEllipsisParamStr);
      cur_param += strlen(cur_param);
    }

    IdlVariable *rval = func->GetReturnValue();
    if (rval->GetType() != TYPE_VOID) {
      if ((pcount > 0) || func->GetHasEllipsis()) {
        strcpy(cur_param, kDelimiterStr);
        cur_param += strlen(kDelimiterStr);
      }
      GetVariableTypeForParameter(type_buf, *rval);
      sprintf(cur_param, kReturnStr, type_buf,
              rval->GetType() == TYPE_STRING ? "" : "*");
    }
    else {
      *cur_param++ = '\0';
    }
 
    GetCapitalizedName(name_buf, *func);
    sprintf(buf, kMethodDeclNonVirtualStr, name_buf, param_buf);
    *file << buf;
  }

  *file << "\n\n";
}

void     
XPCOMGen::GenerateForwardMacro(IdlInterface &aInterface)
{
  char buf[512];
  char name_buf[128];
  char type_buf[128];
  ofstream *file = GetFile();
  
  strcpy(name_buf, aInterface.GetName());
  StrUpr(name_buf);
  sprintf(buf, kMethodForwardMacroStr, name_buf);
  *file << buf;

  int a, acount = aInterface.AttributeCount();
  for (a = 0; a < acount; a++) {
    IdlAttribute *attr = aInterface.GetAttributeAt(a);

    GetVariableTypeForParameter(type_buf, *attr);
    GetCapitalizedName(name_buf, *attr);
    sprintf(buf, kGetterMethodForwardStr, name_buf, type_buf,
            attr->GetType() == TYPE_STRING ? "" : "*", name_buf,
            name_buf, name_buf);
    *file << buf;

    if (!attr->GetReadOnly()) {
      sprintf(buf, kSetterMethodForwardStr, name_buf, 
              attr->GetType() == TYPE_STRING ? "const " : "", type_buf, 
              name_buf, name_buf, name_buf);
      *file << buf;
    }
  }
  
  int m, mcount = aInterface.FunctionCount();
  for (m = 0; m < mcount; m++) {
    char param_buf[256];
    char *cur_param = param_buf;
    char param_buf2[256];
    char *cur_param2 = param_buf2;
    IdlFunction *func = aInterface.GetFunctionAt(m);

    // Don't generate a constructor method
    if (strcmp(func->GetName(), aInterface.GetName()) == 0) {
      continue;
    }

    int p, pcount = func->ParameterCount();
    for (p = 0; p < pcount; p++) {
      IdlParameter *param = func->GetParameterAt(p);

      if (p > 0) {
        strcpy(cur_param, kDelimiterStr);
        cur_param += strlen(kDelimiterStr);
        strcpy(cur_param2, kDelimiterStr);
        cur_param2 += strlen(kDelimiterStr);
      }

      GetParameterType(type_buf, *param);
      GetCapitalizedName(name_buf, *param);
      sprintf(cur_param, kParamStr, type_buf, name_buf);
      cur_param += strlen(cur_param);
      sprintf(cur_param2, kCallParamStr, name_buf);
      cur_param2 += strlen(cur_param2);
    }

    if (func->GetHasEllipsis()) {
      if (pcount > 0) {
        strcpy(cur_param, kDelimiterStr);
        cur_param += strlen(kDelimiterStr);
        strcpy(cur_param2, kDelimiterStr);
        cur_param2 += strlen(kDelimiterStr);
      }
      sprintf(cur_param, kEllipsisParamStr);
      cur_param += strlen(cur_param);
      sprintf(cur_param2, kEllipsisCallStr);
      cur_param2 += strlen(cur_param2);
    }

    IdlVariable *rval = func->GetReturnValue();
    if (rval->GetType() != TYPE_VOID) {
      if ((pcount > 0) || func->GetHasEllipsis()) {
        strcpy(cur_param, kDelimiterStr);
        cur_param += strlen(kDelimiterStr);
        strcpy(cur_param2, kDelimiterStr);
        cur_param2 += strlen(kDelimiterStr);
      }
      GetVariableTypeForParameter(type_buf, *rval);
      sprintf(cur_param, kReturnStr, type_buf,
              rval->GetType() == TYPE_STRING ? "" : "*");
      strcpy(cur_param2, kReturnCallStr);
    }
    else {
      *cur_param++ = '\0';
      *cur_param2++ = '\0';
    }
 
    GetCapitalizedName(name_buf, *func);
    sprintf(buf, kMethodForwardStr, name_buf, param_buf, name_buf, param_buf2);
    *file << buf;
  }

  *file << "\n\n";
}

void
XPCOMGen::GenerateEndClassDecl()
{
  ofstream *file = GetFile();
  
  *file << kClassEpilogStr;
}

void 
XPCOMGen::GenerateFactory(IdlInterface &aInterface)
{
  char buf[512];
  ofstream *file = GetFile();
  char *iface_name = aInterface.GetName();
  char name_buf[128];
  char type_buf[128];
  char uuid_buf[128];
  char param_buf[256];
  char *cur_param = param_buf;
  IdlFunction *constructor;

  *cur_param = '\0';
  
  if (HasConstructor(aInterface, &constructor)) {
    strcpy(name_buf, iface_name);
    strcat(name_buf, "Factory");
    char *iid = aInterface.GetIIDAt(1);
    GetInterfaceIID(uuid_buf, name_buf);
    if (NULL != iid) {
      sprintf(buf, kUuidStr, uuid_buf, iid);
    }
    else {
      sprintf(buf, kUuidStr, uuid_buf, kNoUuidStr);
    }
    *file << buf;
    
    sprintf(buf, kFactoryClassDeclBeginStr, iface_name);
    *file << buf;
    
    int p, pcount = constructor->ParameterCount();
    for (p = 0; p < pcount; p++) {
      IdlParameter *param = constructor->GetParameterAt(p);
      
      if (p > 0) {
        strcpy(cur_param, kDelimiterStr);
        cur_param += strlen(kDelimiterStr);
      }

      GetParameterType(type_buf, *param);
      GetCapitalizedName(name_buf, *param);
      sprintf(cur_param, kParamStr, type_buf, name_buf);
      cur_param += strlen(cur_param);
    }
    
    if (constructor->GetHasEllipsis()) {
      if (pcount > 0) {
        strcpy(cur_param, kDelimiterStr);
        cur_param += strlen(kDelimiterStr);
      }
      sprintf(cur_param, kEllipsisParamStr);
      cur_param += strlen(cur_param);
    }
    
    GetCapitalizedName(name_buf, *constructor);
    sprintf(buf, kConstructorDeclStr, param_buf, name_buf);
    *file << buf;
    
    *file << kFactoryClassDeclEndStr;
  }
}

void     
XPCOMGen::GenerateEpilog(IdlInterface &aInterface, PRBool aIsPrimary)
{
  char buf[512];
  ofstream *file = GetFile();
  char *iface_name = aInterface.GetName();

  if (aIsPrimary) {
    if (mIsGlobal) {
      sprintf(buf, kGlobalInitClassStr, iface_name);
    }
    else {
      sprintf(buf, kInitClassStr, iface_name);
    }
    *file << buf;
  
    sprintf(buf, kNewObjStr, iface_name);
    *file << buf;
  }

  sprintf(buf, kEndifStr, iface_name);
  *file << buf;
}
