/***************************************************************************
 *   Copyright (C) 2005 by Thierry CHARLES   *
 *   thierry@les-charles.net   *
 *                                                                         *
 *   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; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
#include "renderer_panelelt.h"

#include <wx/textctrl.h>
#include <wx/stattext.h>
#include <wx/timer.h>
#include <wx/msgdlg.h>
#include <wx/filedlg.h>

#include "components/framework/tapplicationpanel.h"
#include "components/framework/tapplication.h"
#include "components/framework/tmainwindow.h"
#include "components/stdgui/tpanel.h"
#include "components/stdgui/tbitmap.h"
#include "components/stdgui/tgenbutton.h"
#include "xpe_components/res_id.h"
#include "xpe_components/mainwindow.h"
#include "xpe_components/editor/editor_panelelement.h"

#include "xpe.h"

#include "lib/lib_file.h"
#include "lib/tprocess.h"
#include "lib/lib_string.h"
#include "lib/lib_logging.h"

#include "bmp_id.h"


const TBitmap * RendererPanelElement::getDefaultIcon()
{
    return GET_BMP(BMP_POVRAY_ID);
}

RendererPanelElement::RendererPanelElement(TApplicationPanel * _owner, int iID)
    : TPanelElement(_owner, iID, wxT(""), wxT("")),
    iniChooserBtn(NULL),
    iniProfileCombo(NULL),
    options(NULL),
    console(NULL),
    renderBtn(NULL),
    stopBtn(NULL),
    povProcess(NULL),
    timer(NULL),
    menu(NULL),
    iStartMenuId(0),
    menuStart(NULL),
    iStopMenuId(0),
    menuStop(NULL)
{
    this->setName(wxTr("Povray output"));
    this->setTooltip(wxTr("Look at povray output here"));
    this->setExtensivity(1);
    this->setBitmap(getDefaultIcon());

    this->timer = new wxTimer(this,-1);
    this->Connect(wxEVT_TIMER,wxTimerEventHandler(RendererPanelElement::onTimer),NULL,this);

    RendererRes * res = static_cast<RendererRes *>(CurrentApplication()->getRessource(POV_RENDERER_RESSOURCE_ID));
    if(!res)
    {
        res = new RendererRes();
        CurrentApplication()->publishRessource(POV_RENDERER_RESSOURCE_ID,res);
        res->setRenderer(RendererRes::getInitialisationValue());
    }
}

RendererPanelElement::~RendererPanelElement()
{
    if(this->timer)
        delete this->timer;
    if(this->menu)
    {
        if(CurrentApplication()->getMainWindow())
        {
            int iMenuIndex = CurrentApplication()->getMainWindow()->GetMenuBar()->FindMenu(this->menu->GetTitle());
            CurrentApplication()->getMainWindow()->GetMenuBar()->Remove(iMenuIndex);
            delete this->menu;
        }
        else
        {
            LOG_MESSAGE("Didn't destroy rendering menu because main window is no more accessible. This will result in memory loss", logging::_WARN_);
        }
    }
}

/** construit le panneau conteneur et son contenu (appellé automatiquement par getContentPanel et détruit automatiquement par le destructeur) */
TPanel * RendererPanelElement::buildContentPanel()
{
    TPanel * contentPanel = new TPanel(this->getOwner()->getContentPanel());
    wxSizer * sizer = new wxBoxSizer( wxHORIZONTAL );
    contentPanel->SetSizer(sizer);

    TPanel * panel = new TPanel(contentPanel);
    sizer = new wxBoxSizer( wxVERTICAL );
    panel->SetSizer(sizer);

    TPanel * subPanel = new TPanel(panel);
    sizer = new wxBoxSizer( wxHORIZONTAL );
    subPanel->SetSizer(sizer);

    this->iniChooserBtn = new TGenButton(subPanel);
    this->iniChooserBtn->setBitmap(CurrentApplication()->getBitmap(BMP_FILE_INI_ID));
    this->iniChooserBtn->SetToolTip(wxTr("Choose ini file to use for rendering"));
    this->iniChooserBtn->setToggleable(true);
    sizer->Add(this->iniChooserBtn,0,wxEXPAND);
    this->iniChooserBtn->addButtonListener(this);

    this->iniProfileCombo = new wxComboBox(subPanel, -1, wxTr("default"), wxDefaultPosition, wxDefaultSize, 0, NULL, wxCB_READONLY );
    sizer->Add(this->iniProfileCombo,0,wxEXPAND);

    wxStaticText * label = new wxStaticText(subPanel,-1,wxTr("Povray options :"));
    sizer->Add(label,0,wxCENTER);

    this->options = new wxTextCtrl(subPanel, -1, wxT(""), wxDefaultPosition, wxDefaultSize, wxTE_NOHIDESEL);
    sizer->Add(this->options,1,wxEXPAND);
    this->options->SetValue(this->sOptionsBuf);

    sizer = panel->GetSizer();
    sizer->Add(subPanel,0,wxEXPAND);

    this->console = new wxTextCtrl(panel, -1, wxT(""), wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_TAB|wxTE_MULTILINE|wxTE_NOHIDESEL|wxTE_BESTWRAP|wxTE_READONLY);
    wxFont font = wxFont(this->console->GetFont().GetPointSize(),wxFONTFAMILY_MODERN,wxFONTSTYLE_NORMAL,wxFONTWEIGHT_NORMAL);
    this->console->SetFont(font);
    sizer->Add(this->console,1,wxEXPAND);

    sizer = contentPanel->GetSizer();
    sizer->Add(panel,1,wxEXPAND);

    panel = new TPanel(contentPanel);
    sizer = new wxBoxSizer( wxVERTICAL );
    panel->SetSizer(sizer);

    this->menu = new wxMenu();

    this->renderBtn = new TGenButton(panel);
    this->renderBtn->setBitmap(GET_BMP(BMP_PLAY_ID));
    this->renderBtn->SetToolTip(wxTr("Start rendering"));
    this->renderBtn->addButtonListener( this );
    sizer->Add(this->renderBtn,0,wxEXPAND);

    this->iStartMenuId = static_cast<XPE *>(CurrentApplication())->getUniqueMenuId();
    this->menuStart = new wxMenuItem(this->menu,this->iStartMenuId, wxTr("Start rendering\tALT+G"));
    this->menuStart->SetBitmap(*GET_BMP(BMP_PLAY_ID));
    this->menu->Append(this->menuStart);
    CurrentApplication()->getMainWindow()->Connect( this->iStartMenuId, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(RendererPanelElement::onMenuEvent ), NULL, this );

    this->stopBtn = new TGenButton(panel);
    this->stopBtn->setBitmap(GET_BMP(BMP_STOP_ID));
    this->stopBtn->SetToolTip(wxTr("Stop rendering"));
    this->stopBtn->addButtonListener( this );
    sizer->Add(this->stopBtn,0,wxEXPAND);
    this->stopBtn->Enable(false);

    this->iStopMenuId = static_cast<XPE *>(CurrentApplication())->getUniqueMenuId();
    this->menuStop = new wxMenuItem(this->menu,this->iStopMenuId, wxTr("Stop rendering\tALT+G"));
    this->menuStop->SetBitmap(*GET_BMP(BMP_STOP_ID));
    this->menu->Append(this->menuStop);
    this->menuStop->Enable(false);
    CurrentApplication()->getMainWindow()->Connect( this->iStopMenuId, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(RendererPanelElement::onMenuEvent ), NULL, this );

    wxMenuBar * menuBar = CurrentApplication()->getMainWindow()->GetMenuBar();
    menuBar->Insert(menuBar->GetMenuCount()-1, this->menu, wxTr("Rendering"));

    sizer->AddStretchSpacer(1);

    sizer = contentPanel->GetSizer();
    sizer->Add(panel,0,wxEXPAND);

    return contentPanel;
}

/** click menu */
void RendererPanelElement::onMenuEvent(wxCommandEvent & evt)
{
    if(evt.GetId() == this->iStartMenuId)
        this->buttonActivated(this->renderBtn);
    else
        this->buttonActivated(this->stopBtn);
}

void RendererPanelElement::buttonActivated(TGenButton * btn)
{
    if(btn == this->renderBtn)
    {
        bool bForceFile = true;
        if(this->iniChooserBtn->isToggled())
        {
            TIniProfile &  profile = this->iniFileData.defaultProfile;
            TIniProfileMap::const_iterator it = this->iniFileData.iniProfiles.find(this->iniProfileCombo->GetValue());
            if(it != this->iniFileData.iniProfiles.end())
            {
                profile = (*it).second;
            }

            wxString sInputFile = profile.getEntry(wxT("Input_File_Name"),0);
            bForceFile = sInputFile.size() == 0;
        }
        this->render(bForceFile);
        this->renderBtn->Enable(this->povProcess == NULL);
        this->menuStart->Enable(this->povProcess == NULL);
    }
    else if(btn == this->stopBtn)
    {
        if(this->povProcess)
        {
            this->povProcess->stop( true );
            delete this->povProcess;
            this->povProcess = NULL;
        }
        this->timer->Stop();
        this->stopBtn->Enable(false);
        this->menuStop->Enable(false);
        this->renderBtn->Enable(true);
        this->menuStart->Enable(true);
    }
}

void RendererPanelElement::buttonToggled(TGenButton * btn)
{
    if(btn == this->iniChooserBtn)
    {
        if(btn->isToggled())
        {
            wxFileDialog * dlg = new wxFileDialog(this->getContentPanel(), wxTr("Choose a file"),
                    libfile::dirname(this->sIniFile), libfile::basename(this->sIniFile),
                    wxSTr("Povray ini") + wxT(" (*.ini)|*.ini|"),
                          wxOPEN|wxFILE_MUST_EXIST);
            int iRes = dlg->ShowModal();
            if(iRes == wxID_OK)
            {
                this->sIniFile = dlg->GetPath();
            }
            else
            {
                btn->setToggled(false);
            }

            delete dlg;
        }
        this->updateIniProfiles();
    }
}

void RendererPanelElement::updateIniProfiles()
{
    this->iniProfileCombo->Clear();
    this->iniProfileCombo->Append(wxTr("default"));
    this->iniProfileCombo->SetSelection(0);
    this->iniProfileCombo->Enable(false);
    if(this->iniChooserBtn->isToggled())
    {
        this->iniChooserBtn->SetToolTip(wxString::Format(wxTr("Using %s for rendering"), this->sIniFile.c_str()));

        std::ifstream in_stream;
        in_stream.open((const char *)this->sIniFile.fn_str(), std::ios::in);
        if(in_stream.good())
        {
            this->iniFileData.fromStream(in_stream);
            in_stream.close();

            this->iniProfileCombo->Enable(true);
            this->iniProfileCombo->Append(this->iniFileData.getProfilesList());
        }
        else
            this->iniChooserBtn->setToggled(false);
    }
    else
    {
        this->iniChooserBtn->SetToolTip(wxTr("Choose ini file to use for rendering"));
        this->iniFileData.clear();
    }
}

/** lance le rendu */
void RendererPanelElement::render(bool bForceFile)
{
    this->renderBtn->Enable(false);
    this->menuStart->Enable(false);

    if(!static_cast<XPEMainWindow *>(CurrentApplication()->getMainWindow())->askForModifiedFilesSave())
        return;

    wxString sIniToRender;
    wxString sFileToRender;
    wxString sInternalOpts;
    wxString sPath;

    EditorPanelElement * editor = static_cast<EditorPanelElement *>( CurrentApplication()->getMainWindow()->getCenterPanel()->getFirstVisibleElement());
    if(!editor)
        return;
    sFileToRender = editor->getFilename();
    sPath = libfile::dirname(sFileToRender);

    if(this->iniChooserBtn->isToggled())
    {
        sIniToRender = this->sIniFile;
        if(bForceFile)
            sFileToRender = wxString(wxT("+I")) + sFileToRender;
        else
        {
            sFileToRender = wxT("");
            sPath = libfile::dirname(sIniToRender);
        }

        if(this->iniProfileCombo->GetValue() != wxTr("default"))
        {
            sIniToRender += wxT("[");
            sIniToRender += this->iniProfileCombo->GetValue();
            sIniToRender += wxT("]");
        }
    }

    wxString sPovBin;
    RendererRes * res = static_cast<RendererRes *>(CurrentApplication()->getRessource(POV_RENDERER_RESSOURCE_ID));
    if(res)
        sPovBin = res->getRenderer();

    if(!sPovBin.length())
        return;


    this->console->Clear();

    this->povProcess = new TProcess();
    this->povProcess->setWorkingDirectory( sPath );

#ifdef __WXMSW__
    sFileToRender.Replace(wxT("/"),wxT("\\"));
    sIniToRender.Replace(wxT("/"),wxT("\\"));
#endif

#ifndef __WXMSW__
    if(res->useTransparencyFix())
        sInternalOpts += wxT("-visual DirectColor ");
#endif

    wxString sCommandLine = wxString(wxT("\"")) + sPovBin + wxT("\" ") + sInternalOpts + this->options->GetValue() + ( sIniToRender.size() > 0 ? wxT(" \"") + sIniToRender + wxT("\"") : wxT("")) + ( sFileToRender.size() > 0 ? wxT(" \"") + sFileToRender + wxT("\"") : wxT(""));
#ifdef __WXMSW__
    sCommandLine += wxT(" /nr");
    if(this->options->GetValue().Find(wxT("+P")) < 0)
        sCommandLine += wxT(" /exit");
#endif
    this->povProcess->setCommandLine(sCommandLine);

    if(!this->povProcess->start())
    {
        delete this->povProcess;
        this->povProcess = NULL;
        return;
    }

    this->getOwner()->setElementVisible( this->getID(), true );
    this->timer->Start(1000);
    this->stopBtn->Enable(true);
    this->menuStop->Enable(true);
}

/** timer tick */
void RendererPanelElement::onTimer(wxTimerEvent& event)
{
    if(!this->povProcess)
        return;

    wxString sIn;

#ifdef __WXMSW__
    if(!this->console->GetLineText(0).length())
        sIn = wxTr("The rendering is running in Povray, the output is not accessible here.");
#endif

    if(this->povProcess->isErrorAvailable())
    {
        sIn += this->povProcess->GetErrorData();
    }

    if(this->povProcess->isOutputAvailable())
    {
        sIn += this->povProcess->GetOutputData();
    }

    if(sIn.length())
        this->filterToConsole(sIn);

    if(!this->povProcess->isRunning())
    {
        this->analyseConsoleErrors();
        this->buttonActivated(this->stopBtn);
    }
}

/** filtre des données brutes issues de la console et les affiche */
void RendererPanelElement::filterToConsole(const wxString & sIn)
{
    wxString sOut = this->console->GetLineText(this->console->GetNumberOfLines()-1);
    for(uint i = 0 ; i < sIn.length() ; i++)
    {
        unsigned char c = (unsigned char)sIn.GetChar(i);
        if( c == 13 )
        {
            int iCRIdx = sOut.Find('\n',true);
            if(iCRIdx)
                sOut.Truncate(iCRIdx + 1);
            else
                sOut.Clear();
        }
        if( c != '\n' && ((/*c >= 0x00 && */c < 0x20) || (c >= 0x7F && c < 0xA0)))
            continue;
        sOut += c;
    }
    this->console->Freeze();
    int iPos = this->console->GetLastPosition() -
                    this->console->GetLineLength(this->console->GetNumberOfLines()-1);
    this->console->Replace(iPos,this->console->GetLastPosition(),wxString());
    this->console->AppendText(sOut);
    this->console->Thaw();
}


#define XML_PARAM_NODE_EXEC "Executable"
#define XML_PARAM_NODE_TRANSP_FIX "TransparencyFix"
#define XML_PARAM_NODE_OPTS "Options"
#define XML_PARAM_NODE_USE_INI "UseIni"
#define XML_PARAM_NODE_INI_FILE "IniFile"
#define XML_PARAM_NODE_SELECTED_PROFILE "SelectedProfile"

/** charge les parametres du composant a partir des informations contenues dans le noeud passé en paramètre */
bool RendererPanelElement::loadParameters(TiXmlElement * parametersNode)
{
    TPanelElement::loadParameters(parametersNode);

    TiXmlElement * elt = parametersNode->FirstChildElement(XML_PARAM_NODE_EXEC);
    if(!elt)
        return false;

    RendererRes * res = static_cast<RendererRes *>(CurrentApplication()->getRessource(POV_RENDERER_RESSOURCE_ID));
    if(!res)
    {
        res = new RendererRes();
        CurrentApplication()->publishRessource(POV_RENDERER_RESSOURCE_ID,res);
    }
    res->setRenderer(ISO2WX(elt->GetText()));

    elt = parametersNode->FirstChildElement(XML_PARAM_NODE_OPTS);
    if(elt)
    {
        this->sOptionsBuf = ISO2WX(elt->GetText());
        if(this->options)
            this->options->SetValue(this->sOptionsBuf);
    }

    elt = parametersNode->FirstChildElement(XML_PARAM_NODE_TRANSP_FIX);
    if(elt)
    {
        wxString s = ISO2WX(elt->GetText());
        res->setUseTransparencyFix(s == wxString(wxT("true")));
    }

    elt = parametersNode->FirstChildElement(XML_PARAM_NODE_USE_INI);
    if(elt)
    {
        wxString s = ISO2WX(elt->GetText());
        TGenButton * btn = this->iniChooserBtn;
        this->iniChooserBtn = NULL; // désactive la gstion d'évènements
        btn->setToggled(s == wxString(wxT("true")));
        this->iniChooserBtn = btn;
    }

    elt = parametersNode->FirstChildElement(XML_PARAM_NODE_INI_FILE);
    if(elt)
    {
        this->sIniFile = ISO2WX(elt->GetText());
    }

    this->updateIniProfiles();

    elt = parametersNode->FirstChildElement(XML_PARAM_NODE_SELECTED_PROFILE);
    if(elt)
    {
        this->iniProfileCombo->SetValue(ISO2WX(elt->GetText()));
    }

    return true;
}

/** renvoie les parametres du composant sous la forme d'un noeud xml */
TiXmlElement * RendererPanelElement::getParameters()
{
    TiXmlElement * root = TPanelElement::getParameters();

    RendererRes * res = static_cast<RendererRes *>(CurrentApplication()->getRessource(POV_RENDERER_RESSOURCE_ID));
    if(res)
    {
        TiXmlElement * elt = new TiXmlElement(XML_PARAM_NODE_EXEC);
        TiXmlText * txt = new TiXmlText((const char *)res->getRenderer().fn_str());
        txt->SetCDATA( true );

        elt->LinkEndChild( txt );
        root->LinkEndChild( elt );


        elt = new TiXmlElement(XML_PARAM_NODE_TRANSP_FIX);
        txt = new TiXmlText(res->useTransparencyFix() ? "true" : "false");

        elt->LinkEndChild( txt );
        root->LinkEndChild( elt );
    }

    {
        if(this->options)
            this->sOptionsBuf = this->options->GetValue();
        TiXmlElement * elt = new TiXmlElement(XML_PARAM_NODE_OPTS);
        TiXmlText * txt = new TiXmlText((const char *)this->sOptionsBuf.fn_str());
        txt->SetCDATA( true );

        elt->LinkEndChild( txt );
        root->LinkEndChild( elt );
    }

    if(this->iniChooserBtn)
    {
        TiXmlElement * elt = new TiXmlElement(XML_PARAM_NODE_USE_INI);
        TiXmlText * txt = new TiXmlText(this->iniChooserBtn->isToggled() ? "true" : "false");
        txt->SetCDATA( true );

        elt->LinkEndChild( txt );
        root->LinkEndChild( elt );
    }

    {
        TiXmlElement * elt = new TiXmlElement(XML_PARAM_NODE_INI_FILE);
        TiXmlText * txt = new TiXmlText((const char *)this->sIniFile.fn_str());
        txt->SetCDATA( true );

        elt->LinkEndChild( txt );
        root->LinkEndChild( elt );
    }

    if(this->iniProfileCombo)
    {
        TiXmlElement * elt = new TiXmlElement(XML_PARAM_NODE_SELECTED_PROFILE);
        TiXmlText * txt = new TiXmlText((const char *)this->iniProfileCombo->GetValue().fn_str());
        txt->SetCDATA( true );

        elt->LinkEndChild( txt );
        root->LinkEndChild( elt );
    }

    return root;
}

/** analyse le contenu de la console pour trouver d'éventuelles erreurs */
void RendererPanelElement::analyseConsoleErrors()
{
    long iLastSignialedLine = -1;
    wxString sLastSignialedFile;
    wxString sError;
    bool bReadingError = false;

    for(int i = 0 ; i < this->console->GetNumberOfLines() ; i++)
    {
        wxString sLine =  this->console->GetLineText(i);
        if(sLine.GetChar(0) == 10)
            sLine.Clear();
        if(!bReadingError)
        {
            if(sLine.StartsWith(wxT("File: ")))
            {
                int iEndIdx = sLine.Find(wxT("  Line: "));
                sLastSignialedFile = sLine.Mid(6,iEndIdx-6);
                sLine.Mid(iEndIdx+8).ToLong(&iLastSignialedLine);

                if((!libfile::isAbsolute(sLastSignialedFile)) && this->povProcess)
                {
                    sLastSignialedFile = this->povProcess->getWorkingDirectory() + wxT("/") + sLastSignialedFile;
                }
            }
            else if (sLine.StartsWith(wxT("Parse Error: ")))
            {
                sError = sLine.Mid(13);
                bReadingError = true;
            }
        }
        else
        {
            if(sLine.Trim().length())
            {
                sError += wxT("\n");
                sError += sLine;
            }
            else
            {
                bReadingError = false;
                break;
            }
        }
    }

    if(sError.length())
    {
        XPEMainWindow * mainwindow = static_cast<XPEMainWindow *>(CurrentApplication()->getMainWindow());
        EditorPanelElement * elt = dynamic_cast<EditorPanelElement *>(mainwindow->getTabFromFile(sLastSignialedFile));
        if(!elt)
        {
            mainwindow->loadFile(sLastSignialedFile);
            elt = dynamic_cast<EditorPanelElement *>(mainwindow->getTabFromFile(sLastSignialedFile));
        }
        if(elt)
        {
            mainwindow->getCenterPanel()->setElementVisible(elt->getID(),true);
            elt->getEditor()->SetFocus();
            elt->getEditor()->getCursor()->setPosition(TPoint(0,iLastSignialedLine-1));
        }
        wxMessageBox(sError,wxTr("Rendering error"),wxOK|wxICON_ERROR,mainwindow);
    }
}

/** renvoie la valeur par défaut de l'exécutable povray */
wxString RendererRes::getInitialisationValue()
{
    wxString sPovrayPath = static_cast<XPE *>(CurrentApplication())->getPovrayInstallPath();
    if(sPovrayPath.length())
    {
#ifdef __WXMSW__
        sPovrayPath += wxT("/bin/pvengine.exe");
#else
        if(libfile::isFile(sPovrayPath + wxT("/bin/povray")))
            sPovrayPath += wxT("/bin/povray");
        else
            sPovrayPath += wxT("/bin/megapov");
#endif
    }

    if(!sPovrayPath.length())
    {
#ifdef __WXMSW__
        sPovrayPath = wxT("pvengine.exe");
#else
        sPovrayPath = wxT("povray");
#endif
    }

    return sPovrayPath;
}

