/********************************************************************
                          AUTODUCK

  Description: Creates a Autoduck or JavaDoc compilant comment block
               for the currently selected C/C++ or Java function
               prototype. If the function prototype is within one
               line, you don't need to select line.
  Syntax:      AUTODUCK
  Version:     2

  If you modify this macro, save it under different name because
  new version of the GWD Text Editor may overwrite it.

  THIS SOURCE CODE IS UNDER COPYRIGHT. YOU CAN MODIFY IT, BUT
  ONLY IF YOU INTEND TO USE IT WITH THE GWD TEXT EDITOR.

  Copyright (c) 1998 Vedran Gaco. All right reserved.



  About Autoduck, the source code documentation tool

  Autoduck is a command-line utility that extracts specially tagged
  comment blocks from programming source files and generates rich
  text and HTML files containing the contents of those comment
  blocks. Autoduck has traditionally been used to document
  programming APIs.

  Autoduck is free. There is no licensing fee or restriction. The
  application is considered sample code and is not supported by
  Microsoft Corporation. Here's the legalese:

  THIS TOOL IS NOT SUPPORTED BY MICROSOFT CORPORATION. IT IS PROVIDED
  "AS IS" BECAUSE WE BELIEVE IT MAY BE USEFUL TO YOU. WE REGRET THAT
  MICROSOFT IS UNABLE TO SUPPORT OR ASSIST YOU SHOULD YOU HAVE
  PROBLEMS USING THIS TOOL.

  Eric Artzt erica@microsoft.com

********************************************************************/

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <editor\doc.h>
#include <editor\macros.h>


#define LANG_C     1
#define LANG_JAVA  2

typedef struct _cursorpos {
   long x;
   long y;
} CURSORPOS;


// function prototypes
void AddFunctionDescription(HDOC, char *, int);
void ReplaceTabsWithSpaces(char *psz);
void AppendSpaces(char *psz, int nNumOfSpaces);


/*-------------------------------------------------------------------
                    main function
-------------------------------------------------------------------*/

int main(int argc, char *argv[])
{
   HDOC hdoc;
   int  nLangID;

   hdoc = Gte_GetActiveDocument();
   if(hdoc == NULL) {
      MsgBox("Cannot get handle of the active document!", MB_OK);
      return 1;
   }
   // check if document is read only
   if(Gte_IsReadOnly(hdoc)) {
      MsgBox("Document is read only!", MB_OK | MB_ICONEXCLAMATION);
      return 1;
   }

   // if the document is in selection mode turn it off. This is not
   // necessary for this macro but this is sample.
   GM_EndSelect(hdoc);
   // if the document is in line drawing mode turn it off.
   GM_EndLineDraw(hdoc);

   // check file extension
   {
      char szFileExt[16];

      Gte_GetFileExtension(hdoc, szFileExt, sizeof(szFileExt));
      // if document does not have file ext, empty string will be returned
      if(szFileExt[0] != 0)
      {
         if(stricmp(szFileExt, "c") != 0 && stricmp(szFileExt, "cpp") != 0 &&
            stricmp(szFileExt, "cxx") != 0 && stricmp(szFileExt, "cc") != 0 &&
            stricmp(szFileExt, "h") != 0 && stricmp(szFileExt, "hxx") != 0 &&
            stricmp(szFileExt, "hpp") != 0 && stricmp(szFileExt, "java") != 0)
         {
            if(MsgBox("This is not C/C++ or Java file. Continue?", MB_YESNO) != IDYES)
            {
               return 1;
            }
         }

         if(stricmp(szFileExt, "java") == 0)
            nLangID = LANG_JAVA;
         else
            nLangID = LANG_C;
      }
   }

   // Create comment block
   {
      char *pszSelection;

      // if there is no selection select current line
      if(Gte_GetSelType(hdoc) == 0)
      {
         GM_LineStart(hdoc);
         GM_LineEndSelect(hdoc);
      }

      // get selected text
      pszSelection = Gte_GetSelection(hdoc);
      if(pszSelection == NULL)
      {
         MsgBox("You must select C/C++ or Java function prototype", MB_OK | MB_ICONEXCLAMATION);
         return 1;
      }

      AddFunctionDescription(hdoc, pszSelection, nLangID);

      // free memory
      free(pszSelection);
   }
   return 0;
}

/*-------------------------------------------------------------------
                 AddFunctionDescription

  Creates a Microsoft Autoduck compilant comment block for the
  currently selected C/C++ function prototype.

  If you learning C/C++, skip this function. It is very large
  function, and it should be splited into small ones.
-------------------------------------------------------------------*/

void AddFunctionDescription(HDOC hdoc, char *pszSelection, int nLangID)
{
   char *pszFuncProt;
   char *pszComment;          // Function comment text
   char szRetType[64];        // return type
   char szFuncName[64];       // function name
   char szParams[10][64+2];   // function parameters (search for 64-1 if you want to increase buffer)
   int  nParams;              // number of parameters in the function
   long nCommentIndent;       // number of leading spaces before function prototype (for Java)

   pszFuncProt = pszSelection;

   ReplaceTabsWithSpaces(pszFuncProt);
   // count number of leading spaces (for Java)
   {
      char *psz = pszFuncProt;
      
      Gte_GetSelBeginPos(hdoc, &nCommentIndent, NULL);
      while(*psz == ' ')
      {
         nCommentIndent++;
         psz++;
      }
   }

   // remove leading and ending spaces
   strtrim(pszFuncProt);

   if(*pszFuncProt == 0) {
      // empty string
      MsgBox("You must select C/C++ function prototype", MB_OK | MB_ICONEXCLAMATION);
      return;
   }


   // get the function return type
   // ----------------------------
   {
      char *psz = strchr(pszFuncProt, ' ');
      if(psz) {
         while(*psz == ' ')
            psz++;
         if(*(psz+1) == '*' || *(psz+1) == '&')
            psz++;
      }

      if(psz && psz < strchr(pszFuncProt, '('))
      {
         szRetType[0] = 0;
         strncat(szRetType, pszFuncProt, min(pszFuncProt - psz, sizeof(szRetType)-1));
         pszFuncProt = psz;
         strtrim(szRetType);
      }
      else {
         szRetType[0] = 0;
      }
   }
   // skip whitespaces and new line characters
   while(*pszFuncProt == ' ' || *pszFuncProt == '\n' || *pszFuncProt == '\r')
      pszFuncProt++;


   // Skip inline, far, __fastcall and other keywords before function name
   // --------------------------------------------------------------------
   {
      char *psz1 = strchr(pszFuncProt, '(');
      char *psz2;

      if(psz1)
      {
         // temporary put string terminator
         *psz1 = 0;

         psz2 = strrchr(pszFuncProt, ' ');
         if(psz2)
         {
            // if space is just before (
            if(*(psz2+1) == 0)
            {
               while(psz2 != pszFuncProt && *psz2 == ' ')
                  psz2--;
               while(psz2 != pszFuncProt && *psz2 != ' ')
                  psz2--;
            }
            pszFuncProt = psz2;
         }
         // return (
         *psz1 = '(';
      }
   }

   // skip whitespaces and new line characters
   while(*pszFuncProt == ' ' || *pszFuncProt == '\n' || *pszFuncProt == '\r')
      pszFuncProt++;

   // skip '*' and '&' before function name
   if(*pszFuncProt == '*' || *pszFuncProt == '&')
      pszFuncProt++;

   // get the function name
   // ---------------------
   {
      char *psz1 = strchr(pszFuncProt, '(');
      char *psz2 = strchr(pszFuncProt, ')');

      // make sure that there is a '(' and ')'
      if(psz1 == NULL || psz2 == NULL || psz2 < psz1)
      {
         MsgBox("There is syntax error in the function prototype", MB_OK);
         return;
      }

      szFuncName[0] = 0;
      strncat(szFuncName, pszFuncProt, min(psz1-pszFuncProt, sizeof(szFuncName)));
      // remove trailing blanks and new line characters from function name
      if(szFuncName[0] != 0)
      {
         psz1 = szFuncName + strlen(szFuncName) - 1;
         while(psz1 != szFuncName && (*psz1 == ' ' || *psz1 == '\n' || *psz1 == '\r'))
         {
            *psz1 = 0;
            psz1--;
         }
      }

      pszFuncProt = strchr(pszFuncProt, '(') + 1;
   }

   // skip whitespaces and new line characters
   while(*pszFuncProt == ' ' || *pszFuncProt == '\n' || *pszFuncProt == '\r')
      pszFuncProt++;


   // get the function parameters
   // ---------------------------
   {
      char *psz1;

      // Count number of parameters
      psz1 = pszFuncProt;
      nParams = 1;
      while(*psz1) {
         if(*psz1 == ',')
            nParams++;
         psz1++;
      }
      if(nParams > 10) {
         MsgBox("Sorry, too many parameters", MB_OK);
         return;
      }

      // If we have more than one parameter
      if(nParams > 1)
      {
         // we have more than one parameter
         int nParamIdx;

         nParamIdx = 0;
         while(strchr(pszFuncProt, ','))
         {
            // skip whitespaces and new line characters
            while(*pszFuncProt == ' ' || *pszFuncProt == '\n' || *pszFuncProt == '\r')
               pszFuncProt++;

            psz1 = strchr(pszFuncProt, ',');
            szParams[nParamIdx][0] = 0;
            strncat(szParams[nParamIdx], pszFuncProt, min((psz1 - pszFuncProt), 64-1));
            strtrim(szParams[nParamIdx]);
            // remove trailing blanks and new line characters from parameter
            if(szParams[nParamIdx][0] != 0)
            {
               psz1 = szParams[nParamIdx] + strlen(szParams[nParamIdx]) - 1;
               while(psz1 != szParams[nParamIdx] && (*psz1 == ' ' || *psz1 == '\n' || *psz1 == '\r'))
               {
                  *psz1 = 0;
                  psz1--;
               }
            }

            pszFuncProt = strchr(pszFuncProt, ',') + 1;
            nParamIdx++;
         }
      }

      // skip whitespaces and new line characters
      while(*pszFuncProt == ' ' || *pszFuncProt == '\n' || *pszFuncProt == '\r')
         pszFuncProt++;

      psz1 = strrchr(pszFuncProt, ')');
      if(psz1 == NULL) {
         MsgBox("Function prototype have syntax error", MB_OK);
         return;
      }
      szParams[nParams-1][0] = 0;
      strncat(szParams[nParams-1], pszFuncProt, min(psz1 - pszFuncProt, 64-1));
      strtrim(szParams[nParams-1]);
      // remove trailing blanks and new line characters from parameter
      if(szParams[nParams-1][0] != 0)
      {
         psz1 = szParams[nParams-1] + strlen(szParams[nParams-1]) - 1;
         while(psz1 != szParams[nParams-1] && (*psz1 == ' ' || *psz1 == '\n' || *psz1 == '\r'))
         {
            *psz1 = 0;
            psz1--;
         }
      }
      // check if it is void parameter
      if(nParams == 1)
      {
         if(szParams[0][0] == 0 || stricmp(szParams[0], "void") == 0)
            nParams = 0;
      }

      // we now have function parameters. Separate type from name
      // with | character
      {
         int i;

         for(i = 0; i < nParams; i++)
         {
            if(szParams[i][0] != 0)
            {
               psz1 = szParams[i] + strlen(szParams[i]) - 1;
               while(psz1 != szParams[i] &&
                    (*psz1 == '_' || isalnum((unsigned char)*psz1)))
               {
                  psz1--;
               }
               if(psz1 != szParams[i])
               {
                  memmove(psz1+3, psz1+1, strlen(psz1+1)+1);
                  *(psz1+1) = '|';
                  *(psz1+2) = ' ';
               }
            }
         }
      }
   }

   // now we have return type, function name and parameters


   // Create comment
   // --------------
   {
      // it is very hard to determine how memory we need so
      // we will allocate 8K which will be enought in any case.
      pszComment = (char *)malloc(8192);
      if(pszComment == NULL) {
         MsgBox("Not enought memory", MB_OK);
         return;
      }

      if(nLangID == LANG_JAVA)
      {
         *pszComment = 0;
         AppendSpaces(pszComment, nCommentIndent);
         strcat(pszComment, "/**\r\n");
         AppendSpaces(pszComment, nCommentIndent);
         strcat(pszComment, " * ");
         strcat(pszComment, "TYPE DESCRIPTION HERE"); // do not change this string
         strcat(pszComment, "\r\n");
         AppendSpaces(pszComment, nCommentIndent);
         strcat(pszComment, " *\r\n");
         // parameters
         if(nParams)
         {
            int nMaxParamLen;
            int i;

            for(i = 0; i < nParams; i++)
            {
               char *psz = strchr(szParams[i], '|');
               if(psz)
               {
                  AppendSpaces(pszComment, nCommentIndent);
                  strcat(pszComment, " * @param  ");
                  strcat(pszComment, psz+1);
                  strcat(pszComment, "\r\n");
               }
            }
         }
         AppendSpaces(pszComment, nCommentIndent);
         strcat(pszComment, " */\r\n");
      }
      else
      {
         strcpy(pszComment, "/*-------------------------------------------------------------------\r\n");
         AppendSpaces(pszComment, (strlen(pszComment)-2 - strlen(szFuncName))/2);
         strcat(pszComment, szFuncName);
         strcat(pszComment, "\r\n");
         if(strstr(szFuncName, "::"))
         {
            // C++ class member
            strcat(pszComment, " @mfunc\r\n\r\n");
         }
         else
         {
            // C function
            strcat(pszComment, " @func\r\n\r\n");
         }
         strcat(pszComment, "   The ");
         if(strstr(szFuncName, "::"))
            strcat(pszComment, strstr(szFuncName, "::")+2); // C++ member
         else
            strcat(pszComment, szFuncName); // C function
         strcat(pszComment, " function ");
         strcat(pszComment, "TYPE DESCRIPTION HERE"); // do not change this string
         strcat(pszComment, "\r\n\r\n");
         // parameters
         if(nParams)
         {
            int nMaxParamLen;
            int i;

            // determine longest parameter
            nMaxParamLen = 0;
            for(i = 0; i < nParams; i++)
            {
               if(strlen(szParams[i]) > nMaxParamLen)
                  nMaxParamLen = strlen(szParams[i]);
            }
            for(i = 0; i < nParams; i++)
            {
               strcat(pszComment, "   @parm ");
               strcat(pszComment, szParams[i]);
               AppendSpaces(pszComment, nMaxParamLen - strlen(szParams[i]));
               strcat(pszComment, " | .\r\n");
            }
            strcat(pszComment, "\r\n");
         }

         strcat(pszComment, "-------------------------------------------------------------------*/");
         strcat(pszComment, "\r\n\r\n");
      }
   }

   // we now have function description in the pszComment string

   // Insert comment
   // --------------
   {
      CURSORPOS CursPos;

      // Get selection begin position
      Gte_GetSelBeginPos(hdoc, &CursPos.x, &CursPos.y);
      // Remove selection and position at the begin of the selection
      Gte_SetCursorPos(hdoc, 1, CursPos.y, FALSE);
      // Insert comment
      Gte_InsertText(hdoc, pszComment);
      // Return to the position where we insert text
      Gte_SetCursorPos(hdoc, CursPos.x, CursPos.y, FALSE);
      // find and select "TYPE DESCRIPTION HERE" that we insert in
      // the comment
      Gte_Find(hdoc, "TYPE DESCRIPTION HERE", CursPos.x, CursPos.y,
               FALSE, FALSE, FALSE, TRUE);
      // Scroll window if necessary to make cursor visible
      Gte_FixWindowPos(hdoc);
   }

   free(pszComment);
}

/*-------------------------------------------------------------------
                ReplaceTabsWithSpaces

 Replaces tabs with spaces.
-------------------------------------------------------------------*/

void ReplaceTabsWithSpaces(char *psz)
{
   while(*psz) {
      if(*psz == '\t')
         *psz = ' ';
      psz++;
   }
}


/*-------------------------------------------------------------------
                   AppendSpaces

  Appends nNumOfSpaces at the end of the string
-------------------------------------------------------------------*/

void AppendSpaces(char *psz, int nNumOfSpaces)
{
   int nStrLen;

   if(psz == NULL || nNumOfSpaces <= 0)
      return;

   nStrLen = strlen(psz);
   memset(psz + nStrLen, ' ', nNumOfSpaces);
   psz[nStrLen + nNumOfSpaces] = 0;
}

