//-----------------------------------------------------------------------------
// NastVisualContextGnuplot.cpp
//-----------------------------------------------------------------------------

#include "NastVisualContextGnuplot.h"
#include "NastDebug.h"
#include "NastArray.h"
#include "NastGrid2d.h"
#include "NastStaggeredGrid2d.h"

#ifndef _MSC_VER
#   include <unistd.h>		      // getpid
#   include <strings.h>            // strlen, strcpy
#else
#   include <float.h>           // limits
#   define getpid _getpid
#   include <string.h>            // strlen, strcpy
#   include <process.h>           // getpid
#endif

#include <sys/stat.h>		      // mkfifo
#include <signal.h>		      // kill, SIGKILL
#include <limits.h>		      // INT_MIN, INT_MAX


//-----------------------------------------------------------------------------
//                               Einstellungen
//-----------------------------------------------------------------------------
//  Die beiden named-pipes werden mit Namen szNameData.pid.counter und 
//  szNameProg.pid.counter und 
//  z.B. /tmp/gnuplot.data.12943.1

const char szData[] = "/tmp/gnuplot.data.%d.%d";
const char szProg[] = "/tmp/gnuplot.prog.%d.%d";
const char szGnuplotProg[] = "gnuplot"; // Name von gnuplot

// ----------------------------------------------------------------------------
//                             Hilfsfunktionen
// ----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
//                          Konstruktor + Destruktor
//-----------------------------------------------------------------------------

//  Defaultconstr.
CNastVisualContextGnuplot::CNastVisualContextGnuplot( const char *szTitle )
    :m_pipeProg(0),
     m_pipeData(0),
     m_pid(0),
     m_szTitle(0)
{
    NAST_ASSERT( szTitle );

    // Titel merken
    m_szTitle = new char[ strlen(szTitle) ];
    strcpy( m_szTitle, szTitle );
    
    //  Es werden zwei named-pipes erzeugt. Einer fuer die Daten und einer
    //  um die Befehle zu uebertragen.

    //  Man sollte in seinen .Xdefaults eine Zeile mit
    //  gnuplot.raise: off
    //  einfuegen, damit das Fenster in dem gerade gezeichnet
    //  wird nicht jedes mal automatisch in den Vordergrund kommt.

#ifdef _MSC_VER
    NAST_FATAL_ERROR("not implemented");
#else
    m_szNameData[0] = '\0';
    m_szNameProg[0] = '\0';

    // Zaehler, der bei jedem Aufruf erhoeht wird
    static int nCount = 0;	
    ++nCount;

    // Filenamen erzeugen
    sprintf( m_szNameProg, szProg, getpid(), nCount );

    // so jetzt koennen die pipes generiert werde
    if( mkfifo( m_szNameProg, 0666) )
    {
        NAST_FATAL_ERROR("konnte keine temporaeren Dateien erzeugen");
    }

    // jetzt kann gnuplot gestartet werden
    int pid = fork();
    if( pid == -1)
        NAST_FATAL_ERROR("konnte gnuplot nicht starten");

    if( pid == 0 )		// child ?
    {
        execlp( szGnuplotProg, "gnuplot", m_szNameProg, NULL );
        NAST_FATAL_ERROR("konnte gnuplot nicht starten");
    }
    m_pid = pid;

    // pipe fuer die Kommandos oeffnen
    m_pipeProg = fopen( m_szNameProg, "w" );
#endif

    // Wenn die pipes nocht erzeugt werden konnten muss das Programm
    // abgebrochen werden sonst krachts bei der ersten Ausgabe
    if( m_pipeProg == 0 )
    {
        NAST_FATAL_ERROR("konnte keine temporaeren Dateien erzeugen");
    }
}

//  Destruktor
CNastVisualContextGnuplot::~CNastVisualContextGnuplot()
{
    NAST_ASSERT_VALID( this );
#ifdef _MSC_VER

#else
    // gnuplot beenden
    kill( m_pid, SIGHUP );

    // Files schliessen
    closeData();

    fclose( m_pipeProg );
    m_pipeData = 0;
    m_pipeProg = 0;

    // pipes entfernen
    unlink( m_szNameProg );
    m_szNameProg[0]='\0';

    // wenn gnuplot immer noch nicht beendet ist killen
    kill( m_pid, SIGKILL );

#endif
    // Speicher fuer den Titel wieder freigeben
    delete [] m_szTitle;
    m_szTitle = 0;
}

//-------------------------------------------------------------------------
//                            Memberfunktionen
//-------------------------------------------------------------------------


//  vorerst wird die Funktion fuer doubles verwendet
void 
CNastVisualContextGnuplot::show( const CNastArray<int> &arrY, 
        			 const char *szComment /* = 0 */,
                                 double time /* = 0.0 */   )
{
    CNastArray<double> arrTmp;
    for( int i = 0; i < arrY.size(); i++)
    {
        arrTmp.add( arrY[i] );
    }
    show( arrTmp, szComment, time );
}


void 
CNastVisualContextGnuplot::show( const CNastArray<double> &arrY, 
        			 const char *szComment /* = 0 */,
                                 double time /* = 0.0 */   )
{
    NAST_ASSERT_VALID( this );

    createData();		// Datenpipe erzeugen

    fprintf( m_pipeProg, "reset \n" );

    if( szComment && time != 0.0 ) //  Kommentar und Zeitangabe ?
        fprintf( m_pipeProg, "plot '%s' title '%s %s Zeit:%f'\n", m_szNameData, title(), szComment, time );
    else if( szComment ) 
        fprintf( m_pipeProg, "plot '%s' title '%s %s'\n", m_szNameData, title(), szComment );
    else
        fprintf( m_pipeProg, "plot '%s' title '%s'\n", m_szNameData, title() );


    fflush( m_pipeProg );	// Befehle uebergeben, sonst blockiert openData
    openData();			// Datenpipe oeffnen

    for( int i = 0; i < arrY.size(); i++)
    {
        fprintf( m_pipeData, "%f \n", arrY[i] );
    }
    fprintf( m_pipeData, "e \n" );

    closeData();		// Datenpipe wieder schliessen
}

void 
CNastVisualContextGnuplot::show( const CNastArray<double> &arrX, 
        			 const CNastArray<double> &arrY,
        			 const char *szComment /* = 0 */,
                                 double time /* = 0.0 */   )
{
    NAST_ASSERT_VALID( this );
    NAST_ASSERT( arrX.size() == arrY.size() );
    int i;

    if( arrX.size() < 2)
        return;

    // das das Array mit den X-Werten unsortiert sein kann
    // min und max bestimmen (wegen range fuer gnuplot)
    double xmin = FLT_MAX;
    double xmax =-FLT_MAX;

    for( i = 0; i < arrX.size(); i++)
    {
        if( arrX[i] > xmax )  xmax = arrX[i];
        if( arrX[i] < xmin )  xmin = arrX[i];
    }
    createData();		// Datenpipe erzeugen

    fprintf( m_pipeProg, "reset \n" );

    if( szComment && time != 0.0 ) //  Kommentar und Zeitangabe ?
        fprintf( m_pipeProg, "plot [%f:%f] '%s' title '%s %s Zeit:%f' \n", 
        	 xmin,
        	 xmax,
        	 m_szNameData, 
        	 title(), 
        	 szComment,
        	 time );
    else if( szComment ) 
        fprintf( m_pipeProg, "plot [%f:%f] '%s' title '%s %s' \n", 
        	 xmin,
        	 xmax,
        	 m_szNameData, 
        	 title(), 
        	 szComment );
    else
        fprintf( m_pipeProg, "plot [%f:%f] '%s' title '%s' \n", 
        	 xmin,
        	 xmax,
        	 m_szNameData, 
        	 title() );
    fflush( m_pipeProg );	// Befehle uebergeben, sonst blockiert openData
    openData();			// Datenpipe oeffnen

    //  Daten rausschreiben
    for( i = 1; i < arrY.size(); i++)
    {
        fprintf( m_pipeData, "%f %f\n", arrX[i], arrY[i] );
    }
    
    closeData();		// Datenpipe schliessen und entfernen
}

//-------------------------------------------------------------------------
//  Matrizen werden in Binaerformat uebergeben. 
//  (siehe auch gnuplot-Sources binary.c fwrite_matrix(...) )
//-------------------------------------------------------------------------

void 
CNastVisualContextGnuplot::show( const CNastGrid2d &grid,
        			 const char *szComment /* = 0 */,
                                 double time /* = 0.0 */   )
{
    NAST_ASSERT_VALID( this );
    createData();		// Datenpipe erzeugen
    fprintf( m_pipeProg, "reset \n" );
    fprintf( m_pipeProg, "set data style lines \n" );    

    if( szComment && time != 0.0 ) //  Kommentar und Zeitangabe ?
        fprintf( m_pipeProg, "set title \'%s %s Zeit:%f\' \n", title(), szComment, time );    
    else if( szComment )           // nur Kommentar ?
        fprintf( m_pipeProg, "set title \'%s %s\' \n", title(), szComment );
    else
        fprintf( m_pipeProg, "set title \'%s\' \n", title() );

    fprintf( m_pipeProg, "set cntrparam linear\n" );        
    fprintf( m_pipeProg, "set contour \n" );
    fprintf( m_pipeProg, "set surface \n" );      
    fprintf( m_pipeProg, "set xlabel 'x' \n" );
    fprintf( m_pipeProg, "set ylabel 'y' \n" );
    fprintf( m_pipeProg, "splot '%s' binary \n", m_szNameData );
    fflush( m_pipeProg );	// Befehle uebergeben, sonst blockiert openData
    openData();			// Datenpipe oeffnen

    
    //  Damit die Kommunikation schnell erfolgen kann wird ein grosser Datenblock
    //  erzeugt und auf einmal rausgeschrieben 

    //  Groesse des Datenblocks berechnen
    const  int nSize = 1 + grid.sizeJ() + grid.sizeI() * (grid.sizeJ()+1);
    int ii = 0;			// Index im Datenblock
    float *arrData = new float[nSize];
    
    //  Anzahl der Spalten
    arrData[ii++] = (float)(grid.sizeI());

    //  Namen der Spalten
    int i,j;
    for( i = 0; i < grid.sizeI(); i++ )
    {
        arrData[ii++]= (float)(grid.box().minX() + i * grid.dx());
    }
    
    // die eigenlichen Daten
    for( j = 0; j < grid.sizeJ(); j++ )
    {
        // Name der Zeile
        arrData[ii++]= (float)(grid.box().minY() + j * grid.dy());
        
        // die Zeile selbst
        for( i = 0; i < grid.sizeI(); i++ )
        {
            arrData[ii++] = (float)(grid( i, j ));
        }
    }
    
    NAST_ASSERT( ii == nSize );

    // den gesamten Block auf einmal rausschreiben
    if( fwrite( arrData, sizeof(float), nSize, m_pipeData) != (size_t)nSize)
    {
        NAST_FATAL_ERROR( "Fehler beim Schreiben in Datenpipe");
    }

    //  Datenblock wieder freigeben
    delete [] arrData;
    arrData = 0;
    closeData();    
}


void 
CNastVisualContextGnuplot::show( const CNastStaggeredGrid2d &grid,
        			 const char* szComment, /* =0 */
        			 double time /* = 0.0 */       )
{
    NAST_ASSERT_VALID( this );

    createData();		// Datenpipe erzeugen
    fprintf( m_pipeProg, "reset \n" );
    fprintf( m_pipeProg, "set multiplot \n" );
    fprintf( m_pipeProg, "set origin 0.0, 0.0\n");
    fprintf( m_pipeProg, "set size   0.5, 1.0\n");    
    fprintf( m_pipeProg, "set data style lines \n" );    

    fprintf( m_pipeProg, "set xrange[0:%d] \n", grid.sizeI());
    fprintf( m_pipeProg, "set yrange[0:%d] \n", grid.sizeJ());
    
    if( szComment && time != 0.0 ) //  Kommentar und Zeitangabe ?
        fprintf( m_pipeProg, "set title \'%s %s Zeit:%f\' \n", title(), szComment, time );    
    else if( szComment )           // nur Kommentar ?
        fprintf( m_pipeProg, "set title \'%s %s\' \n", title(), szComment );
    else
        fprintf( m_pipeProg, "set title \'%s\' \n", title() );

    fprintf( m_pipeProg, "set cntrparam linear\n" );        
    fprintf( m_pipeProg, "set contour \n" );
    fprintf( m_pipeProg, "set surface \n" );      
    fprintf( m_pipeProg, "set xlabel 'x' \n" );
    fprintf( m_pipeProg, "set ylabel 'y' \n" );
    fprintf( m_pipeProg, "plot '%s' w vec\n", m_szNameData );
    fflush( m_pipeProg );	// Befehle uebergeben, sonst blockiert openData
    openData();			// Datenpipe oeffnen

    // ----------------------------------------------------------------------
    //  Leider habe ich keine Beschreibung gefunden, ob gnuplot bei 
    //  Vektoren auch Binaerfiles lesen kann und  wie diese dann ausschauen
    //  sollen. Also wird der Textmodus benutzt.
    //  evtl. ist auch noch eine Skalierung noetig.
    // ----------------------------------------------------------------------

    // ----------------------------------------------------------------------
    // Zuerst die Geschwindigkeitswerte
    const CNastVector2d vecX( grid.dx(), 0 );
    const CNastVector2d vecY( 0, grid.dy() );

    int i,j;
    for( i = 0; i < grid.sizeI(); i++)
        for( j = 0; j < grid.sizeJ(); j++)
        {
            // Zellmittelpunkt ermitteln (mit Gitter fuer den Druck)
            const CNastPoint2d  pntPos( grid.gridP().pos( i+1, j+1) );

            // Geschwindigkeitswerte im Zellmittelpunkt erfragen
            CNastVector2d vecVel( grid.velocity( pntPos ));

            // IDEE
            // vielleicht laesst sich die Skalierung etwas gluecklicher
            // gestalten 
            vecVel *= 20;

            // und die Daten rausschreiben
            fprintf( m_pipeData, 
        	     "%d %d %f %f\n", 
        	     i,
        	     j,
        	     vecVel.x(),
        	     vecVel.y() );
        	     
        }
    closeData();

    // ----------------------------------------------------------------------
    //  Danach die Druckwerte darstellen
    createData();
    fprintf( m_pipeProg, "set origin 0.5, 0.0\n");
    fprintf( m_pipeProg, "set data style lines \n" );    
    fprintf( m_pipeProg, "set cntrparam linear\n" );        
    fprintf( m_pipeProg, "set contour \n" );
    fprintf( m_pipeProg, "set surface \n" );      
//     fprintf( m_pipeProg, "set view 0,0,1\n");
    fprintf( m_pipeProg, "set xlabel \n" );
    fprintf( m_pipeProg, "set ylabel \n" );

    fprintf( m_pipeProg, "splot '%s' binary \n", m_szNameData );
    fprintf( m_pipeProg, "set nomultiplot \n" );
    fflush( m_pipeProg );	// Befehle uebergeben, sonst blockiert openData

    openData();

    //  Groesse des Datenblocks berechnen
    const unsigned int nSize = 1 + grid.sizeI() + (grid.sizeI() + 1)* (grid.sizeJ());
    int ii = 0;			// Index im Datenblock
    float *arrData = new float[nSize];

    //  Danach die Druckwerte darstellen
    
    //  Anzahl der Spalten
    arrData[ii++] = (float)(grid.sizeI());

    //  Namen der Spalten
    for( i = 0; i < grid.sizeI(); i++ )
    {
        arrData[ii++] = float( i ); // Name der Zeile
    }
    
    // die eigenlichen Daten
    for( j = 0; j < grid.sizeJ(); j++ )
    {
        arrData[ii++] = float( j ); // Name der Zeile
        
        // die Zeile selbst
        for( i = 0; i < grid.sizeI(); i++ )
        {
            arrData[ii++] = (float)(grid.gridP()(i,j));
        }
    }
    
    NAST_ASSERT( nSize - ii == 0 );

    // den gesamten Block auf einmal rausschreiben
    if( fwrite( arrData, sizeof(float), nSize, m_pipeData) != nSize)
    {
        NAST_FATAL_ERROR( "Fehler beim Schreiben in Datenpipe");
    }

    //  Datenblock wieder freigeben
    delete [] arrData;
    arrData = 0;

    //  und Pipes wieder schliessen
    fflush( m_pipeProg );	// Befehle uebergeben, sonst blockiert openData
    closeData();    
}

//-------------------------------------------------------------------------
//                            Hilfsfunktionen
//-------------------------------------------------------------------------

//  oeffnet ein neues Datenpipe falls noch nicht vorhanden
void CNastVisualContextGnuplot::createData()
{
    static int nCount = 0;
    if( m_pipeData == 0)
    {
        sprintf( m_szNameData, szData, getpid(), ++nCount );	

#ifndef _MSC_VER
        if( mkfifo( m_szNameData, 0666) )
        {
            NAST_FATAL_ERROR("konnte keine temporaeren Dateien erzeugen");
        }
#else 
        NAST_ASSERT_DUMP(false,"Funktion nich fuer NT implementiert");
#endif
    }
}

//  oeffnet ein neues Datenpipe falls noch nicht vorhanden
void CNastVisualContextGnuplot::openData()
{
    NAST_ASSERT( strlen(m_szNameData) > 0); // Name muss schon erzeugt sein

    if( m_pipeData == 0)
    {
        m_pipeData = fopen( m_szNameData, "w" );
        if( m_pipeData == 0)
        {
            NAST_FATAL_ERROR("konnte Datenpipe nicht oeffnen");
        }
    }
}

// schliesst Datenpipe falls sie noch nicht geschlossen ist
void CNastVisualContextGnuplot::closeData()
{
    if( m_pipeData )
    {
        if( fclose( m_pipeData ) )
        {
            NAST_FATAL_ERROR("konnte Datenpipe nicht schliessen");
        }
        m_pipeData = 0;

        unlink( m_szNameData );
        m_szNameData[0] = '\0';
    }
}

// ----------------------------------------------------------------------------
//                                    Debug
// ----------------------------------------------------------------------------

void 
CNastVisualContextGnuplot::debugDump( CNastDumpContext &dumpContext ) const
{
    CNastVisualContext::debugDump( dumpContext );
    dumpContext << "CNastVisualContextGnuplot" << "\n";
}

void 
CNastVisualContextGnuplot::assertValid() const
{
    // zuerst einmal AssertValid der Basisklasse aufrufen
    CNastVisualContext::assertValid(); 
    NAST_ASSERT( m_pipeProg );    
    NAST_ASSERT( m_szNameData );
    NAST_ASSERT( m_szNameProg );
    NAST_ASSERT( m_pid );
}


