/*******************************************************************************
 * pvedit.cpp
 *
 * This file contains editor support code.
 *
 * Author: Christopher J. Cason.
 *
 * ---------------------------------------------------------------------------
 * Persistence of Vision Ray Tracer ('POV-Ray') version 3.7.
 * Copyright 1991-2013 Persistence of Vision Raytracer Pty. Ltd.
 *
 * POV-Ray is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * POV-Ray 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * ---------------------------------------------------------------------------
 * POV-Ray is based on the popular DKB raytracer version 2.12.
 * DKBTrace was originally written by David K. Buck.
 * DKBTrace Ver 2.0-2.12 were written by David K. Buck & Aaron A. Collins.
 * ---------------------------------------------------------------------------
 * $File: //depot/public/povray/3.x/windows/pvedit.cpp $
 * $Revision: #1 $
 * $Change: 6069 $
 * $DateTime: 2013/11/06 11:59:40 $
 * $Author: chrisc $
 *******************************************************************************/

#define POVWIN_FILE
#define _WIN32_IE COMMONCTRL_VERSION

#include <windows.h>
#include <shellapi.h>
#include <string.h>
#include <sys/stat.h>
#include "pvengine.h"
#include "pvedit.h"
#include "backend/parser/reswords.h"

// this must be the last file included
#include "syspovdebug.h"

namespace pov_frontend
{
  extern struct ProcessOptions::INI_Parser_Table RenderOptions_INI_Table[];
}

namespace povwin
{

using namespace pov;
  
HINSTANCE               hLibPovEdit ;

extern bool             debugging ;
extern HWND             main_window ;
extern bool             use_editors;
extern bool             editors_enabled;

static HWND (*CreateTabWindow) (HWND ParentWindow, HWND StatusWindow, const char *BinariesPath, const char *DocumentsPath) ;
static DWORD (*GetDLLVersion) (void) ;
static void (*SetWindowPosition) (int x, int y, int w, int h) ;
static void (*SetMessageWindow) (HWND MessageWindow) ;
static void (*RestoreState) (int RestoreFiles) ;
static void (*SaveState) (void) ;
static bool (*SelectFile) (char *FileName) ;
static bool (*BrowseFile) (bool CreateNewWindow) ;
static bool (*LoadFile) (char *FileName) ;
static bool (*CloseFile) (char *FileName) ;
static bool (*SaveFile) (char *FileName) ;
static bool (*ExternalLoadFile) (char *ParamString) ;
static DWORD (*GetTab) (void) ;
static DWORD (*GetFlags) (void) ;
static char *(*GetFilename) (void) ;
static void (*NextTab) (bool Forward) ;
static bool (*CanClose) (bool AllFiles) ;
static bool (*SaveModified) (char *FileName) ;
static bool (*ShowMessages) (bool on) ;
static void (*DispatchMenuId) (DWORD id) ;
static HMENU (*GetMenuHandle) (int which) ;
static void (*SetNotifyBase) (HWND WindowHandle, int MessageBase) ;
static void (*UpdateMenus) (HMENU MenuHandle) ;
static void (*GetContextHelp) (void) ;
static void (*SetTabFocus) (void) ;
static bool (*ShowParseError) (const char *FileName, const char *Message, int Line, int Col) ;
static bool (*PassOnMessage) (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, DWORD *rVal) ;
static void (*SetKeywords) (LPCSTR SDLKeywordList, LPCSTR INIKeywords) ;
static const char **(*GetWindowList) (void);

void                    **FunctionPtrs [] =
                        {
                         (void **) &GetWindowList,
                         (void **) &SetWindowPosition,
                         (void **) &SetMessageWindow,
                         (void **) &RestoreState,
                         (void **) &SaveState,
                         (void **) &SelectFile,
                         (void **) &BrowseFile,
                         (void **) &LoadFile,
                         (void **) &CloseFile,
                         (void **) &SaveFile,
                         (void **) &ExternalLoadFile,
                         (void **) &GetTab,
                         (void **) &GetFlags,
                         (void **) &GetFilename,
                         (void **) &NextTab,
                         (void **) &CanClose,
                         (void **) &SaveModified,
                         (void **) &ShowMessages,
                         (void **) &DispatchMenuId,
                         (void **) &GetMenuHandle,
                         (void **) &SetNotifyBase,
                         (void **) &UpdateMenus,
                         (void **) &GetContextHelp,
                         (void **) &SetTabFocus,
                         (void **) &ShowParseError,
                         (void **) &PassOnMessage,
                         (void **) &SetKeywords,
                         (void **) &CreateTabWindow,
                         NULL
                        } ;

char                    *FunctionNames [] =
                        {
                         "GetWindowList",
                         "SetWindowPosition",
                         "SetMessageWindow",
                         "RestoreState",
                         "SaveState",
                         "SelectFile",
                         "BrowseFile",
                         "LoadFile",
                         "CloseFile",
                         "SaveFile",
                         "ExternalLoadFile",
                         "GetTab",
                         "GetFlags",
                         "GetFilename",
                         "NextTab",
                         "CanClose",
                         "SaveModified",
                         "ShowMessages",
                         "DispatchMenuId",
                         "GetMenuHandle",
                         "SetNotifyBase",
                         "UpdateMenus",
                         "GetContextHelp",
                         "SetTabFocus",
                         "ShowParseError",
                         "PassOnMessage",
                         "SetKeywords",
                         "CreateTabWindow",
                         NULL
                        } ;

bool LoadEditorDLL (char *path, bool errorOK)
{
  int                   DllVersion ;
  int                   err ;
  char                  str [2048] ;
  char                  **s ;
  void                  ***f ;
  struct stat           statbuf ;

   if (!editors_enabled)
    return (false) ;
  if (debugging)
    debug_output ("Trying to load editor DLL from '%s' [%s]\n", path, stat (path, &statbuf) != 0 ? "missing" : "found") ;
  if (hLibPovEdit != NULL)
    FreeLibrary(hLibPovEdit);
  if ((hLibPovEdit = LoadLibraryEx (path, 0, LOAD_WITH_ALTERED_SEARCH_PATH)) == NULL)
  {
    err = GetLastError () ;
    if (debugging)
      debug_output ("Could not load editor DLL '%s', error code is %08lx\n", path, err) ;
    if (!errorOK)
    {
#ifdef _DEBUG
      sprintf (str,
               "Editor DLL initialisation failed [LoadLibrary failed, code is %08lx]\n\n"
               "Debug build: if you have renamed cmedit as cmeditd, keep in mind that it "
               "will still look for povcmax.dll, not povcmaxd.dll. Also, for debug builds "
               "the build output directory is checked for the DLL's before the install dir.\n\n"
               "Check the README in the source for instructions on how to copy the DLL's from "
               "the official distribution to the naming convention used by debug builds.",
               err) ;
      PovMessageBox (str, "POV-Ray Editor error") ;
      PovMessageBox ("See the 'Internal Editor Reference' section in the help file for\n"
                     "instructions on how to correct this or turn editor loading off.", "Important!") ;
#else
      if (MessageBox (main_window,
                      "POV-Ray could not load its internal editor. This is to be expected "
                      "if you installed the AGPL-licenced distribution, as the editor DLL "
                      "is not included. If you do not wish to use the editor you can turn "
                      "it off (and stop this message appearing) by unchecking 'Use Editor' "
                      "under the 'Other Settings' sub-menu of the Options menu.\n\n"
                      "If you wish to enable the editor, clicking 'Yes' below will take "
                      "you to its download page.",
                      "Could not load internal editor - download it?",
                      MB_ICONQUESTION | MB_YESNO) == IDYES)
                        ShellExecute (NULL, NULL, "http://www.povray.org/download/wineditdll/" POV_RAY_VERSION COMPILER_VER "." PVENGINE_VER, NULL, NULL, SW_SHOWNORMAL) ;
#endif
    }
    return (false) ;
  }

  GetDLLVersion = (DWORD (*) (void)) GetProcAddress (hLibPovEdit, "GetDLLVersion") ;
  if (GetDLLVersion == NULL)
  {
    if (debugging)
      debug_output ("Could not resolve GetDLLVersion, error code is %08lx\n", GetLastError ()) ;
    if (!errorOK)
      PovMessageBox ("Editor DLL initialization failed [could not resolve GetDLLVersion]\n\n"
                     "See the 'Built-In Editors' section in the help file",
                     "POV-Ray Editor Error") ;
    return (false) ;
  }
  if ((DllVersion = GetDLLVersion ()) != EDITDLLVERSION)
  {
    sprintf (str, "ERROR : Wrong editor DLL version [expected %04x, got %04x].", EDITDLLVERSION, DllVersion) ;
    if (debugging)
      debug_output ("%s\n", str) ;
    if (!errorOK)
    {
      MessageBox (NULL, str, "POV-Ray Editor Error", MB_ICONSTOP) ;
      PovMessageBox ("Editor switched off.\n\nSee the 'Built-In Editors' section in the help file", "Important!") ;
    }
    return (false) ;
  }
  for (s = FunctionNames, f = FunctionPtrs ; *s ; s++, f++)
  {
    **f = (void *) GetProcAddress (hLibPovEdit, *s) ;
    if (**f == NULL)
    {
      if (debugging)
        debug_output ("Could not get address of '%s', error code is %08lx\n", *s, GetLastError ()) ;
      if (!errorOK)
      {
        sprintf (str, "Editor DLL initialization failed:\nCould not resolve %s.", *s) ;
        PovMessageBox (str, "Editor Error") ;
        PovMessageBox ("See the 'Built-In Editors' section in the help file", "Important!") ;
      }
      return (false) ;
    }
  }
  if (debugging)
    debug_output ("Loaded editor DLL '%s'\n", path) ;
  return (true) ;
}

char *Get_Reserved_Words (const char *additional_words)
{
  int length = 0 ;
  int i ;

  for (i = 0; i < LAST_TOKEN; i++)
  {
    if (!isalpha (Reserved_Words [i].Token_Name [0]))
      continue ;
    if (strchr (Reserved_Words [i].Token_Name, ' ') != NULL)
      continue ;
    length += (int)strlen (Reserved_Words[i].Token_Name) + 1 ;
  }

  length += (int)strlen (additional_words) + 2;

  char *result = (char *) malloc (++length) ;
  strcpy (result, additional_words) ;
  strcat (result, "#\n");
  char *s = result + strlen (additional_words) + 2;

  for (i = 0 ; i < LAST_TOKEN ; i++)
  {
    if (!isalpha (Reserved_Words [i].Token_Name [0]))
      continue ;
    if (strchr (Reserved_Words [i].Token_Name, ' ') != NULL)
      continue ;
    s += sprintf (s, "%s\n", Reserved_Words[i].Token_Name) ;
  }
  *--s = '\0' ;

  return (result) ;
}

char *Get_INI_Keywords(void)
{
  int length = 0 ;

  for (struct pov_base::ProcessOptions::INI_Parser_Table *op = pov_frontend::RenderOptions_INI_Table; op->keyword != NULL; op++)
    length += strlen(op->keyword) + 1;
  char *result = (char *) malloc (++length) ;
  char *s = result;
  for (struct pov_base::ProcessOptions::INI_Parser_Table *op = pov_frontend::RenderOptions_INI_Table; op->keyword != NULL; op++)
    s += sprintf (s, "%s\n", op->keyword) ;
  *--s = '\0' ;
  return (result) ;
}

char *Get_User_Keywords(const char *DocumentsPath)
{
  char        str[_MAX_PATH];
  char        *s;
  FILE        *f;
  string      result;

  sprintf(str, "%sini\\user-keywords.txt", DocumentsPath);
  if ((f = fopen(str, "rt")) == NULL)
    return _strdup("");
  while (fgets(str, sizeof (str), f) != NULL)
  {
    s = clean(str);
    if (*s != '\0' && *s != '#')
    {
      // don't allow any words with spaces
      if (strchr(s, ' ') != NULL)
        continue;
      result += s;
      result += '\n';
    }
  }
  fclose (f) ;
  return _strdup(result.c_str());
}

HWND InitialiseEditor (HWND ParentWindow, HWND StatusWindow, const char *BinariesPath, const char *DocumentsPath)
{
  HWND        hwnd ;

  if (!editors_enabled || !use_editors)
    return (NULL) ;

  char *s1 = Get_User_Keywords(DocumentsPath);
  char *s2 = Get_Reserved_Words(s1) ;
  char *s3 = Get_INI_Keywords() ;
  EditSetKeywords (s2, s3) ;
  free(s1) ;
  free(s2);
  free(s3);
  if ((hwnd = CreateTabWindow (ParentWindow, StatusWindow, BinariesPath, DocumentsPath)) == NULL)
  {
    PovMessageBox ("TabWindow error: see the 'Built-In Editors' section in the help file", "Important!") ;
    return (NULL) ;
  }
  return (hwnd) ;
}

void SetEditorPosition (int x, int y, int w, int h)
{
  if (!use_editors)
    return ;
  SetWindowPosition (x, y, w, h) ;
}

void EditSetMessageWindow (HWND MessageWindow)
{
  if (!use_editors)
    return ;
  SetMessageWindow (MessageWindow) ;
}

void EditRestoreState (int RestoreFiles)
{
  if (!use_editors)
    return ;
  RestoreState (RestoreFiles) ;
}

void EditSaveState (void)
{
  if (!use_editors)
    return ;
  SaveState () ;
}

bool EditSelectFile (char *FileName)
{
  if (!use_editors)
    return (false) ;
  return (SelectFile (FileName)) ;
}

bool EditBrowseFile (bool CreateNewWindow)
{
  if (!use_editors)
    return (false) ;
  return (BrowseFile (CreateNewWindow)) ;
}

// NULL means create a new, untitled, editor window.
bool EditOpenFile (char *FileName)
{
  if (!use_editors)
    return (false) ;
  return (LoadFile (FileName)) ;
}

// NULL means currently selected file
bool EditCloseFile (char *FileName)
{
  if (!use_editors)
    return (false) ;
  return (CloseFile (FileName)) ;
}

// NULL means currently selected file
bool EditSaveFile (char *FileName)
{
  if (!use_editors)
    return (false) ;
  return (SaveFile (FileName)) ;
}

bool EditExternalOpenFile (char *ParamString)
{
  if (!use_editors)
    return (false) ;
  return (ExternalLoadFile (ParamString)) ;
}

DWORD EditGetTab (void)
{
  if (!use_editors)
    return (0) ;
  return (GetTab ()) ;
}

DWORD EditGetFlags (void)
{
  if (!use_editors)
    return (EDIT_MSG_SELECTED) ;
  return (GetFlags ()) ;
}

char *EditGetFilename (bool IncludeModifiedIndicator)
{
  char                  *s;
  static char           str [_MAX_PATH + 2] ;

  if (!use_editors)
    return (NULL) ;
  s = GetFilename();
  if (IncludeModifiedIndicator == false || s == NULL || *s == '\0')
    return s;
  if ((GetFlags() & EDIT_CURRENT_MODIFIED) == 0)
    return s;
  strcpy(str, s);
  strcat(str, "*");
  return str;
}

void EditNextTab (bool Forward)
{
  if (!use_editors)
    return ;
  NextTab (Forward) ;
}

bool EditCanClose (bool AllFiles)
{
  if (!use_editors)
    return (true) ;
  return (CanClose (AllFiles)) ;
}

bool EditSaveModified (char *FileName)
{
  if (!use_editors)
    return (true) ;
  return (SaveModified (FileName)) ;
}

bool EditShowMessages (bool on)
{
  // return true if the caller should show the message window themselves
  if (!use_editors)
    return (true) ;
  return (ShowMessages (on)) ;
}

void EditDispatchMenuId (DWORD id)
{
  if (!use_editors)
    return ;
  DispatchMenuId (id) ;
}

void EditUpdateMenus (HMENU MenuHandle)
{
  if (!use_editors)
    return ;
  UpdateMenus (MenuHandle) ;
}

void EditSetFocus (void)
{
  if (!use_editors)
    return ;
  SetTabFocus () ;
}

HMENU EditGetMenu (int which)
{
  if (!use_editors)
    return (NULL) ;
  return (GetMenuHandle (which)) ;
}

void EditSetNotifyBase (HWND WindowHandle, int MessageBase)
{
  if (!use_editors)
    return ;
  SetNotifyBase (WindowHandle, MessageBase) ;
}

void EditContextHelp (void)
{
  if (!use_editors)
    return ;
  GetContextHelp () ;
}

bool EditShowParseError (const char *FileName, const char *Message, int Line, int Col)
{
  if (!use_editors)
    return (false) ;
  return (ShowParseError (FileName, Message, Line, Col)) ;
}

bool EditPassOnMessage (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, DWORD *rVal)
{
  if (!use_editors)
    return (false) ;
  return (PassOnMessage (hwnd, message, wParam, lParam, rVal)) ;
}

void EditUnload (void)
{
}

bool EditEnabled (void)
{
  return (use_editors) ;
}

void EditSetState (bool on)
{
  use_editors = on ;
}

void EditSetKeywords (LPCSTR SDLKeywordList, LPCSTR INIKeywordList)
{
  SetKeywords (SDLKeywordList, INIKeywordList) ;
}

const char **EditGetWindowList (void)
{
  if (!use_editors)
    return NULL;
  return GetWindowList();
}

}
