/*
 * ScriptEdit.cpp
 *
 * Copyright (C) 2003, 2006 J. "MUFTI" Scheurich
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program (see the file "COPYING" for details); if 
 * not, write to the Free Software Foundation, Inc., 675 Mass Ave, 
 * Cambridge, MA 02139, USA.
 */

#include "ScriptEdit.h"
#include <errno.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>

#include "SFMFTypes.h"
#include "Field.h"
#include "EventIn.h"
#include "EventOut.h"
#include "ExposedField.h"
#include "Element.h"
#include "DuneApp.h"
#include "resource.h"

#include "NodePackagedShader.h"
#include "NodeShaderProgram.h"
#include "NodeShaderPart.h"

ObjectEdit::ObjectEdit(Node *node, SWND wnd, 
                       EditorReadyCallback editorReadyCallback, void *data)
{
    _node = node;
    _timer = NULL;
    _wnd = wnd;
    _editorReadyCallback = editorReadyCallback;
    _data = data;
#ifdef _WIN32
    _popUp = true;
#else
    _popUp = false;
#endif
}

static int
editorTimerCallback(void *data)
{
    ObjectEdit *objectEdit = (ObjectEdit *) data;
    objectEdit->OnTimer();
    return 0;
}

void
ObjectEdit::OnTimer(void)
{
    if (_timer) {
        swKillTimer(_timer);
        _timer = NULL;
        if (swCheckRunningProcess()) 
            _timer = swSetTimer(_wnd, 500, editorTimerCallback, this);
        else {
            if (readEditorfile())
                _editorReadyCallback(_data);
        } 
    }
}

bool ObjectEdit::write2file(int f, const void *data, size_t count)
{
    if (mywrite(f, data, count) < 0) {
        TheApp->MessageBoxPerror(_editorFile);
        return false;
    }
    return true;    
}

bool
ObjectEdit::edit(bool newFile, bool is4Kids)
{
    if (newFile) {
        if (needOpen()) {
            int f = -1;
            for (int i = 1; i <= 2; i++) {
                generateFilename(i == 2);
                f = open(_editorFile, O_WRONLY | O_CREAT,00666);   
                if (f != -1)
                    break;
                if (i == 2) {             
                    char format[256];
                    swLoadString(IDS_ERROR_SAVE_FILE_TO_EDIT, format, 255);
                    char msg[1024];
                    mysnprintf(msg, 1023, format, _editorFile, strerror(errno));
                    TheApp->MessageBox(msg);
                    return false;
                }
            }
            TheApp->AddToFilesToDelete(_editorFile);
            if (!writeFile(f))
                return false;
            if (swTruncateClose(f))
                return false;
        } else
            generateFilename(false);
    }

    generateCommand(is4Kids);
    if (_popUp) {
        if (system(_command) != 0)
            return false;
        readEditorfile();
        _editorReadyCallback(_data);
    } else {
        _timer = swSetTimer(_wnd, 500, editorTimerCallback, this);
        if (swCreateCheckableProcess(_command) != 0) {
            MyString object = "";
            object += _command;
            object += " ";
            object += _editorFile;
            TheApp->MessageBoxPerror(object);
            return false;
        }
    }
    return true;
}


ScriptEdit::ScriptEdit(NodeScript *node, SWND wnd, 
                       EditorReadyCallback editorReadyCallback, void *data) :
ObjectEdit(node, wnd, editorReadyCallback, data)
{
    _scriptNode = node;
    _urlData = NULL;
}

ScriptEdit::~ScriptEdit()
{
    delete _urlData;
}

void
ScriptEdit::generateFilename(bool secondTry)
{
    TheApp->checkAndRepairTextEditCommand();

    int texteditUseExtensionTxt;

    swTextEditGetSettingsUseExtensionTxt(TheApp->GetTextedit(),
                                         &texteditUseExtensionTxt);

    if (texteditUseExtensionTxt) {
        if (secondTry) 
            swGetTempPath(_editorFile,".dune_ecmascript",".txt",1024);
        else
            swGetTempFile(_editorFile,".dune_ecmascript", ".txt", 1024);
    } else {
        if (secondTry) 
            swGetTempPath(_editorFile,".dune_ecmascript",".js",1024);
        else
            swGetTempFile(_editorFile,".dune_ecmascript", ".js", 1024);
    }
}

bool
ScriptEdit::writeFile(int f)
{
    bool writeError = false;
    MFString *url = (MFString *) _scriptNode->url();
    bool hasUrl = true;
    if (url == NULL)
        hasUrl = false;
    if (url->getSize() == 0)
        hasUrl = false;
    if (hasUrl) {
        for (int i = 0; i < url->getSize(); i++) {
            char* string= (char*) ((const char*) url->getValue(i));
            if (!writeSFStringUrl(f, string))
               writeError = true;
            if (i < url->getSize()-1)
               if (!write2file(f, ",\n")) 
                  writeError = true;
        }
    } else
        if (!writeSFStringUrl(f, ""))
            writeError = true;
    if (writeError) {
        TheApp->MessageBoxPerror(_editorFile);
        return false;
    }
         
    return true;
}    

void
ScriptEdit::generateCommand(bool is4Kids)
{
    TheApp->checkAndRepairTextEditCommand();
    mysnprintf(_command, sizeof(_command), "%s %s", 
               swTextEditGetCommand(TheApp->GetTextedit()), _editorFile);
    
}

bool   
ScriptEdit::readEditorfile(void)
{
    return ecmaScriptReadEditorfile(_editorFile);     
}

// add javascript scheme if necessary and write to file to be edited

bool
ScriptEdit::writeSFStringUrl(int f, const char* string)  
{
    bool hasJavascript = false;
 
     Proto* proto = _scriptNode->getProto();
     if (proto == NULL)
         return false;
 
    if (!write2file(f, "\""))
        return false;
    if (strlen(string) == 0) {
        if (_scriptNode->getScene()->isX3d()) {
            if (!write2file(f, "ecmascript:\n\n"))
                 return false;        
        } else {
            if (!write2file(f, "javascript:\n\n"))
                 return false;        
        }
        if (!write2file(f, "// insert program code only into functions\n\n"))
            return false;        
        hasJavascript = true;
    } else if (isSortOfEcmascript(string)) {
        if (isJavascript(string)) {
            if (!write2file(f, "javascript:\n\n"))
                return false;        
            string += strlen("javascript:");
        } else if (isEcmascript(string)) {
            if (!write2file(f, "ecmascript:\n\n"))
                return false;        
            string += strlen("ecmascript:");
        }
        hasJavascript = true;
        if (string[0] == '\n')
            string++;
        if (string[0] == '\n')
            string++;
    }
    bool addComment = true;
    MyString javascript = "";
    InterfaceArray *interfaceData = _scriptNode->getInterfaceData();
    for (int j = 0; j < interfaceData->size(); j++) {
        int ind = interfaceData->get(j)->_elementIndex;
        MyString text = "";
        MyString cmptext = "";
        MyString extratext = "";  
        switch (interfaceData->get(j)->_elementEnum) {
          case EL_FIELD:
            {
              MyString name = proto->getField(ind)->getName(false); 
              int type = proto->getField(ind)->getType();
              text += " // field ";
              text += typeEnumToString(type);
              text += " ";
              text += name;
              text += " //";
              cmptext = text;
              extratext += "\n";
              if (addComment) {
                  extratext += typeDefaultValue(type)->
                               getEcmaScriptComment(name, EL_FIELD);
                  extratext += "\n";
              }
            }
            break;
          case EL_EVENT_OUT:
            {
              MyString name = proto->getEventOut(ind)->getName(false);
              int type = proto->getEventOut(ind)->getType();
              text += " // eventOut ";
              text += typeEnumToString(type);
              text += " ";
              text += name;
              text += " //";
              cmptext = text;
              extratext += "\n";
              if (addComment) {
                  extratext += typeDefaultValue(type)->
                               getEcmaScriptComment(name, EL_EVENT_OUT);
                  extratext += "\n";
              }
            }
            break;
          case EL_EVENT_IN:
            {
              MyString name = proto->getEventIn(ind)->getName(false);
              int type = proto->getEventIn(ind)->getType();
              text += " function ";
              text += name;
              cmptext = "";
              cmptext += text;
              text += "(";
              cmptext += " (";
              extratext = "value)\n    {";
              extratext += "\n    // value  "; 
              extratext += typeEnumToString(type);
              extratext += "\n";
              if (addComment) {
                  extratext += typeDefaultValue(type)->
                               getEcmaScriptComment("value", EL_EVENT_IN);
                  extratext += "\n";
              }
              extratext += "    }\n\n";
            }
            break;
        }
        // if not already in, attach to javascript code
        if (_scriptNode->strwhitespacestr(string, cmptext) == false) {
            javascript += text;
            javascript += extratext;
        }
    }  
    if (_scriptNode->getWantInitialize()) {
        _scriptNode->wantInitialize(false);
        MyString text = " function initialize(";
        MyString extratext = ")\n    {\n    \n    }\n\n";
        // if not already in, attach to javascript code
        if (_scriptNode->hasInitialize() == false) {
            javascript += text;
            javascript += extratext;
        }
    }
    if (_scriptNode->getWantEventsProcessed()) {
        _scriptNode->wantEventsProcessed(false);
        MyString text = " function eventsProcessed(";
        MyString extratext = ")\n    {\n    \n    }\n\n";
        // if not already in, attach to javascript code
        if (_scriptNode->hasEventsProcessed() == false) {
            javascript += text;
            javascript += extratext;
        }
    }
    if (_scriptNode->getWantShutdown()) {
        _scriptNode->wantShutdown(false);
        MyString text = " function shutdown(";
        MyString extratext = ")\n    {\n    \n    }\n\n";
        // if not already in, attach to javascript code
        if (_scriptNode->hasShutdown() == false) {
            javascript += text;
            javascript += extratext;
        }
    }
    if (hasJavascript)
        if (!write2file(f, javascript))
            return false;        
    if (!write2file(f, string))
        return false;
    if (!write2file(f, "\""))
        return false;
    return true;
}

bool
ScriptEdit::ecmaScriptReadEditorfile(char *fileName)
{
    return readQuotedEditorFile(fileName, _scriptNode, 
                                          _scriptNode->url_Field());
}

bool
ObjectEdit::readQuotedEditorFile(char *fileName, Node *node, int field)
{
    char *file = fileName; 
    if (file == NULL)
        file = _editorFile;
    int f = open(file, O_RDONLY, 00666);   
    if (f == -1) {
        TheApp->MessageBoxPerror(IDS_ERROR_READ_SCRIPT_DATA, file);
        return true;
    } 
    lseek(f, 0, SEEK_SET);
    _urlDataLength = lseek(f, 0, SEEK_END);
    lseek(f,0,SEEK_SET);
    _urlData = new char[_urlDataLength];
    int offset = 0;
    do {
        int bytes = read(f, _urlData + offset, _urlDataLength - offset);
#ifdef _WIN32
        // M$Windows do a CR-LF translation, number of read byte is reduced
        // under M$Windows bytes == 0 is EOF
        if (bytes == 0)
            _urlDataLength = offset;
#endif
        if (bytes < 0) {
            TheApp->MessageBox(IDS_PREVIOUS_EOF, _editorFile);
            return true;
        } 
        offset += bytes;
    } while (offset < _urlDataLength);  

    MFString* newUrl = new MFString();
    if (checkEditorData()) {
        if (_urlStartData.size() != _urlEndData.size()) {
            TheApp->MessageBox(IDS_MISSING_QUOTE_PAIR, _editorFile);
            edit(false);
            return false;
        } 
        for (int i = 0 ; i < _urlStartData.size(); i++) {
            _urlData[_urlEndData[i]] = '\0';
            MyString urlString = "";
            urlString += (&_urlData[_urlStartData[i]+1]);
            newUrl->setSFValue(i, new SFString(urlString));
        }       
        node->setField(field, newUrl);     
    } else {
        edit(false);
        return false;
    }
    return true;
}

// check for valid " qouting in data from editor
// valid is 
// 
//     blank_or_tab " some_text " 
// (multiple) continued with
//     blank_or_tab , blank_or_tab " some_text " 
//
// blank_or_tab and some_text may be empty

// \" is not counted as " 
static bool isDoubleQuoute(char* data, int offset)
{ 
    if (offset == 0)
       return data[offset] == '"';
    else
       return (data[offset] == '"') && (data[offset-1] != '\\');
}

static bool isBlankOrTab(char* data, int offset)
{
    return ((data[offset] == ' ') || (data[offset] == '\t'));
}

static bool isLineFeed(char* data, int offset)
{
    return (data[offset] == '\n') || (data[offset+1] == '\r');
}

static bool is2CharLineFeed(char* data, int offset)
{
  if ( ((data[offset] == '\n') && (data[offset+1] == '\r')) || 
       ((data[offset] == '\r') && (data[offset+1] == '\n')) )
      return true;
  else
      return false;
}

enum { blankOrTabMode, someTextMode };

// additionly to check for this tokens, ecmaScriptCheckEditorData
// also checks for beginning and end of the Script url field(s) (type MFString)

bool
ObjectEdit::checkEditorData(void)
{
    int mode = blankOrTabMode;
    int lineCount = 1;
    int charsPerLine = 0;
    bool beforeFirstDoubleQuoute = true;
    _urlStartData.resize(0);    
    _urlEndData.resize(0);    
    for (int offset = 0; offset < _urlDataLength; offset++) {
        charsPerLine++;           
        if (isLineFeed(_urlData,offset)) {
            if (is2CharLineFeed(_urlData, offset))
                offset++;
            lineCount++;
            charsPerLine = 0;
        } else if (mode == blankOrTabMode) {
            bool invalidChar = false;
            if (!isBlankOrTab(_urlData,offset))
                if (_urlData[offset] == ',') { 
                    if (beforeFirstDoubleQuoute)
                        invalidChar = true;
                } else 
                    if (isDoubleQuoute(_urlData,offset)) {
                        mode = someTextMode;
                        _urlStartData.append(offset);
                        }
                    else 
                       invalidChar = true;
            if (invalidChar) {        
                char format[256];
                swLoadString(IDS_MISSING_CLOSING_QUOTE, format, 255);
                char msg[1024];
                mysnprintf(msg, 1023, format, lineCount, charsPerLine);
                TheApp->MessageBox(msg);
                return false;
            }        
        } else 
            if (isDoubleQuoute(_urlData,offset)) {
                mode = blankOrTabMode;
                beforeFirstDoubleQuoute = false;
                _urlEndData.append(offset);
            }
    }
    if (mode == someTextMode) {
        char format[256];
        swLoadString(IDS_MISSING_CLOSING_QUOTE, format, 255);
        char msg[1024];
        mysnprintf(msg, 1023, format, lineCount, charsPerLine, _editorFile);
        TheApp->MessageBox(msg);
        return false;
    } else
        return true;   
}


ShaderEdit::ShaderEdit(Node *node, SWND wnd, 
                       EditorReadyCallback editorReadyCallback, void *data) :
ObjectEdit(node, wnd, editorReadyCallback, data)
{
    _node = node;
}

ShaderEdit::~ShaderEdit()
{
}

void
ShaderEdit::generateFilename(bool secondTry)
{
    _editorFile[0] = (char) 0;

    if (secondTry) 
        swGetTempPath(_editorFile,".dune_shaderprogram",".txt",1024);
    else
        swGetTempFile(_editorFile,".dune_shaderprogram", ".txt", 1024);
}

void
ShaderEdit::generateCommand(bool is4Kids)
{
   TheApp->checkAndRepairTextEditCommand();
   mysnprintf(_command, sizeof(_command), "%s %s", 
              swTextEditGetCommand(TheApp->GetTextedit()), _editorFile);
}

bool
ShaderEdit::writeFile(int f)
{
    bool writeError = false;
    MFString *url = NULL;
    switch(_node->getType()) {
      case X3D_PACKAGED_SHADER:
        url = ((NodePackagedShader *)_node)->url();
        break;
      case X3D_SHADER_PROGRAM:
        url = ((NodeShaderProgram *)_node)->url();
        break;
      case X3D_SHADER_PART:
        url = ((NodeShaderPart *)_node)->url();
        break;
    }

    bool hasUrl = true;
    if (url == NULL)
        hasUrl = false;
    if (url->getSize() == 0)
        hasUrl = false;
    if (hasUrl) {
        for (int i = 0; i < url->getSize(); i++) {
            if (!write2file(f, "\""))
                writeError = true;
            char* string= (char*) ((const char*) url->getValue(i));
            if (!write2file(f, string))
               writeError = true;
            if (!write2file(f, "\""))
                writeError = true;
            if (i < url->getSize()-1)
               if (!write2file(f, ",\n")) 
                  writeError = true;
        }
    } else
        if (!write2file(f, "\"\""))
    if (writeError) {
        TheApp->MessageBoxPerror(_editorFile);
        return false;
    }
         
    return true;
}    

bool
ShaderEdit::shaderReadEditorfile(char *fileName)
{    
    int field = -1;
    switch (_node->getType()) {
      case X3D_PACKAGED_SHADER:
        field = ((NodePackagedShader *)_node)->url_Field();
        break;
      case X3D_SHADER_PROGRAM:
        field = ((NodeShaderProgram *)_node)->url_Field();
        break;
      case X3D_SHADER_PART:
        field = ((NodeShaderPart *)_node)->url_Field();
        break;
    }
    if (field != -1)
        return readQuotedEditorFile(fileName, _node, field);
    return false;
}

bool   
ShaderEdit::readEditorfile(void)
{
    return shaderReadEditorfile(_editorFile);     
}

ImageTextureEdit::ImageTextureEdit(NodeImageTexture *node, SWND wnd, 
                       EditorReadyCallback editorReadyCallback, void *data) :
ObjectEdit(node, wnd, editorReadyCallback, data)
{
    _imageTextureNode = node;
}

ImageTextureEdit::~ImageTextureEdit()
{
}

void
ImageTextureEdit::generateFilename(bool secondTry)
{
    _editorFile[0] = (char) 0;
    MyString path = "";

    MFString *urls = _imageTextureNode->url();
    for (int i = 0; i < urls->getSize(); i++) {
        URL url(_imageTextureNode->getScene()->getURL(), urls->getValue(i));
        if (urls->getValue(i).length() == 0) continue;
        _imageTextureNode->getScene()->Download(url, &path);
        break;
    }
    if (strlen(path) < sizeof(_editorFile))
        strcpy(_editorFile, path);
}

void
ImageTextureEdit::generateCommand(bool is4Kids)
{
    if (is4Kids) {
        TheApp->checkAndRepairImageEdit4KidsCommand();
        mysnprintf(_command, sizeof(_command), "%s %s", 
                   swImageEdit4KidsGetSettings(TheApp->GetTextedit()), 
                                               _editorFile);
    } else {
        TheApp->checkAndRepairImageEditCommand();
        mysnprintf(_command, sizeof(_command), "%s %s", 
                   swImageEditGetSettings(TheApp->GetTextedit()), _editorFile);
    }
    
}

void
MovieTextureEdit::generateFilename(bool secondTry)
{
    _editorFile[0] = (char) 0;
    MyString path = "";

    MFString *urls = _movieTextureNode->url();
    for (int i = 0; i < urls->getSize(); i++) {
        URL url(_movieTextureNode->getScene()->getURL(), urls->getValue(i));
        if (urls->getValue(i).length() == 0) continue;
        _movieTextureNode->getScene()->Download(url, &path);
        break;
    }
    if (strlen(path) < sizeof(_editorFile))
        strcpy(_editorFile, path);
}

void
MovieTextureEdit::generateCommand(bool is4Kids)
{
    TheApp->checkAndRepairMovieEditCommand();
    mysnprintf(_command, sizeof(_command), "%s %s", 
               swMovieEditGetSettings(TheApp->GetTextedit()), _editorFile);
    
}

void
AudioClipEdit::generateFilename(bool secondTry)
{
    _editorFile[0] = (char) 0;
    MyString path = "";

    MFString *urls = _audioClipNode->url();
    for (int i = 0; i < urls->getSize(); i++) {
        URL url(_audioClipNode->getScene()->getURL(), urls->getValue(i));
        if (urls->getValue(i).length() == 0) continue;
        _audioClipNode->getScene()->Download(url, &path);
        break;
    }
    if (strlen(path) < sizeof(_editorFile))
        strcpy(_editorFile, path);
}

void
AudioClipEdit::generateCommand(bool is4Kids)
{
    TheApp->checkAndRepairSoundEditCommand();
    mysnprintf(_command, sizeof(_command), "%s %s", 
               swSoundEditGetSettings(TheApp->GetTextedit()), _editorFile);
    
}




