//**************************************************************************//
//* FRIDA: fast reliable interactive data analysis                         *//
//* dualplot.cpp: different mechanisms for screen and paper output         *//
//* (C) Joachim Wuttke 1990-, v2(C++) 2001-                                *//
//* http://frida.sourceforge.net                                           *//
//**************************************************************************//

#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <math.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <boost/format.hpp>

#include "mystd.h"
#include "plotaux.h"
#include "coord.h"
#include <ask_simple_value.h>
#include <ask_and_callback.h>
#include "dualplot.h"

using boost::format;

//**************************************************************************//
//*  CAxis                                                                 *//
//**************************************************************************//

//! Set invalid limits.
//  This means: next plot call will automatically recalculate them.

void CAxis::setAuto()
{
    inf = -INFINITY;
    sup = +INFINITY;
}

//! Check and set limits.

void CAxis::setLimits( string axnam, double _inf, double _sup )
{
    if( _inf>=_sup )
        throw "Cannot set " + axnam + " plot range " +
            strg(_inf) + " .. " + strg(_sup);
    if( logflag && _inf<=0 )
        throw "Cannot set log " + axnam + " plot range " +
            strg(_inf) + " .. " + strg(_sup);
    inf = _inf;
    sup = _sup;
}

//! Set lin or log, reset ranges to auto.

void CAxis::setLog( bool _log )
{
    logflag = _log;
    setAuto();
}

//! Parse string to set bounds.

void CAxis::set_from_string( string in )
{
    if ( in=="*" ) {
        setAuto();
        return;
    }
    string s1, s2;
    mystd::string_extract_word( in, &s1, &s2 );
    if ( !mystd::any2dbl(s1,&inf) )
        throw string( "invalid lower bound, expecting real number" );
    if ( !mystd::any2dbl(s2,&sup) )
        throw string( "invalid upper bound, expecting real number" );
}

//! Replace limits by rounded values.

void CAxis::setRoundedLimits( string axnam, double _inf, double _sup )
{
    static double relmargin = 0.05;
    static double digits = 2;

    if ( _inf==-INFINITY || _sup==+INFINITY )
        throw "unexpected inf limits when rounding " + axnam + " plot range";
    if( _inf>=_sup )
        throw "Cannot round " + axnam + " plot range " +
            strg(_inf) + " .. " + strg(_sup);
    if( logflag && _inf<=0 )
        throw "Cannot round log " + axnam + " plot range " +
            strg(_inf) + " .. " + strg(_sup);

    inf = _inf;
    sup = _sup;
                      
    if ( inf==sup ) {
        if( logflag ){
            inf /= 10;
            sup *= 10;
        } else {
            inf -= 1;
            sup += 1;
        }
        return;
    } 

    double inf2, sup2;
    double mydigits = digits;
    if ( !logflag ) { // lin limits
        double margin = relmargin * (sup - inf);
        do {
            if (mydigits>=8.5) return; // do not round
            sup2 = mystd::round_decimal( sup+margin, mydigits );
            inf2 = mystd::round_decimal( inf-margin, mydigits );
            if(sup<0 && sup2>0) sup2 = 0;
            if(inf2<0 && inf>0) inf2 = 0;
            mydigits += 0.5;
        } while (!((inf2<inf) && (sup<sup2)));
    } else { // log limits
        double ratio = sup / inf;
        double margin = exp( relmargin*log(ratio) );
        do {
            if (mydigits>=8.5) return; // do not round
            sup2 = mystd::round_decimal( sup*margin, mydigits );
            inf2 = mystd::round_decimal( inf/margin, mydigits );
            mydigits += 0.5;
        } while ( !((inf2<inf) && (sup<sup2)) );
    }
    setLimits( axnam, inf2, sup2 );
}	

//! Have finite limits been set ?

bool CAxis::finite() const
{
    return inf!=-INFINITY && sup!=+INFINITY;
}

//! Is value val contained in this range?

bool CAxis::contains(double val)  const
{
    return inf<=val && val<=sup;
}

//! Map application scale (inf..sup) to linear plot scale (0..1).

double CAxis::value2plotcoord( double v ) const
{
    if ( !finite() )
        throw string( "undefined plot range" );
    if ( logflag ) {
        if( inf<0 || v<0 )
            throw string( "negative value in log range" );
        return log(v/inf) / log(sup/inf);
    } else {
        return (v-inf) / (sup-inf);
    }
}

//! Map linear plot scale (0..1) to application scale (inf..sup).

double CAxis::plotcoord2value( double c ) const
{
    if ( !finite() )
        throw string( "undefined plot range" );
    if ( logflag ) {
        return inf * exp( c*log(sup/inf) );
    } else {
        return inf + c*(sup-inf);
    }
}

//! String representation.

string CAxis::str() const
{
    if( inf==-INFINITY || sup== +INFINITY )
        return "*";
    else
        return strg(inf) + " " + strg(sup);
}

//! Query and check range.

void CAxis::Ask( string quest )
{
    string def, in;
    // query input
    def = str();
    in = sask( quest, def );
    if        (in==def)
        return;
    if (in!="")
        set_from_string( in );
    // check new range
    if (logflag && inf<=0 && inf!=-INFINITY) {
        setAuto();
        throw string( "log scale requires range above 0" );
    }
    if (!logflag && ( ( inf!=-INFINITY && inf<-3e38 ) ||
                      ( sup!=+INFINITY && sup>+3e38 ) ) ) {
        setAuto();
        throw string( "lin scale exceeds typical PS implementation range "
                      "(abs<3e38)" );
    }
}

//! Describe range, for use in table of plot frames.

string CAxis::info() const
{
    string ret;
    ret  =   logflag ? "log" : "lin";
    ret += " " + strg(inf);
    ret += " " + strg(sup);
    return ret;
}

//! Convert value -> plot_coordinate.

double CAxis::pc( double v ) const
{
    return CPLOT_PSMAX * value2plotcoord( v );
}


//**************************************************************************//
//*  CPlot                                                                 *//
//**************************************************************************//

//! Constructor for plot window: setup for gnuplot and postscript.

CPlot::CPlot( uint _iPlot, bool _logx, bool _logy ) :
    iPlot( _iPlot ), X( _logx ), Y( _logy ), maxpoints(12000)
{
    // Start gnuplot. Use input redirection so that gnuplot receives
    // commands from a gp_fifo which is created here.

    string fn_fifo = string("/tmp/gnuplot-") + getenv( "LOGNAME" );

    mystd::system( "rm -f "+fn_fifo );
    if (mkfifo( fn_fifo.c_str(), 0666 )) {
        printf("SEVERE SYSTEM ERROR cannot make fifo %s"
               " - will not be able to print\n", fn_fifo.c_str() );
        return;
    }

    mystd::system( "gnuplot -title " + strg(iPlot) + " -noraise" +
                   " < " + fn_fifo + " &" );

    // we use open instead of fopen or ofstream,
    // because we need non-blocking mode.
    if (!(gp_fifo = open(fn_fifo.c_str(), O_WRONLY))) {
        printf("SEVERE SYSTEM ERROR cannot open gp_fifo"
               " - will not be able to print\n");
        return;
    }
    fcntl(gp_fifo,F_SETFL,O_NONBLOCK);

    gp_write( string("set terminal x11") );

    ps_fnum=0;
}

//! Clear plot frame and buffer.

void CPlot::clearFrame()
{
    // clear gnuplot tmp files
    gp_fno = 0;
    gp_fnames = "";

    // reset buffers for postscript output
    string cmd;
    ps_accu.clear();
    ps_accu.push_back( "\n%% output created by frida2\n");
    ps_snum = 0;
    ps_pnum = 0;
    ps_cnum = 0;
    ps_Doc.clear();
}

//! Change one setup parameter per command.

void CPlot::setAux( string cmd )
{
    if      ( cmd=="gnp" )
        maxpoints = iask("Max # points in plot", maxpoints);
    else if ( cmd=="gxl" ) {
        X.setLog( !X.logflag );
        printf( "set x %s\n", X.logflag ? "log" : "lin" ); }
    else if ( cmd=="gxl+" )
        X.setLog( true );
    else if ( cmd=="gxl-" )
        X.setLog( false );
    else if ( cmd=="gyl" ) {
        Y.setLog( !Y.logflag );
        printf( "set y %s\n", Y.logflag ? "log" : "lin" ); }
    else if ( cmd=="gyl+" )
        Y.setLog( true );
    else if ( cmd=="gyl-" )
        Y.setLog( false );
    else if ( cmd=="gxf" ) {
        X.force = !X.force;
        printf( "force x %s\n", X.force ? "on" : "off" ); }
    else if ( cmd=="gxf+" )
        X.force = true;
    else if ( cmd=="gxf-" )
        X.force = false;
    else if ( cmd=="gyf" ) {
        Y.force = !Y.force;
        printf( "force y %s\n", Y.force ? "on" : "off" ); }
    else if ( cmd=="gyf+" )
        Y.force = true;
    else if ( cmd=="gyf-" )
        Y.force = false;
    else
        throw "unknown command " + cmd;
}

//! Plot coordinate frame (axes, ticks, labels).

void CPlot::plotFrame()
{
    gp_write( "set nologscale" );
    string whichlog="";
    if (X.logflag) whichlog += "x";
    if (Y.logflag) whichlog += "y";
    if (whichlog!="")
        gp_write( "set logscale " + whichlog );

    // wups:
    snprintf( outlin, CPLOT_LINSIZ, "\n%d %g %g xSetCoord\n", 
              X.logflag, X.inf, X.sup );
    ps_accu.push_back( outlin );
    snprintf( outlin, CPLOT_LINSIZ, "%d %g %g ySetCoord\n", 
              Y.logflag, Y.inf, Y.sup );
    ps_accu.push_back( outlin );
    snprintf( outlin, CPLOT_LINSIZ, "%% %d %g %g %d zSetCoord\n\n",
              0, 0., 0., 0 );
    ps_accu.push_back( outlin );

    int ntack, ntpt;
    double *tack, ticklim[2];

    ps_accu.push_back( "\n/xPlotFrame {" );
    if ( X.logflag && X.inf<= 0 )
        throw "BUG: x log incompatible with limits " + X.str();
    plotaux::calc_ticks( X.logflag, X.inf, X.sup, 
                         &ntack, &tack, &ntpt, ticklim );
    ps_ticktack(ntack, tack, ntpt, ticklim, &X);
    free(tack);
    snprintf( outlin, CPLOT_LINSIZ-4, "   {(%s", X.C.ps_str().c_str() );
    strncat( outlin, ")}\n", CPLOT_LINSIZ );
    ps_accu.push_back( outlin );
    ps_accu.push_back( "      0 10   0  0     0  90 "
                       "OneAxx Axx Tic xTacL xNumL %% low x axis\n" );
    ps_accu.push_back( "      0 10   0 10     0 270 "
                       "OneAxx Axx Tic xTacH       %% top x axis\n" );
    ps_accu.push_back( "      xCL\n" );
    ps_accu.push_back( "} def\n" );
    
    ps_accu.push_back( "\n/yPlotFrame {" );
    if ( Y.logflag && Y.inf<= 0 )
        throw "BUG: y log incompatible with limits " + Y.str();
    plotaux::calc_ticks( Y.logflag, Y.inf, Y.sup, 
                         &ntack, &tack, &ntpt, ticklim );
    ps_ticktack(ntack, tack, ntpt, ticklim, &Y);
    free(tack);
    snprintf( outlin, CPLOT_LINSIZ, "   {(%s", Y.C.ps_str().c_str() );
    strncat( outlin, ")}\n", CPLOT_LINSIZ );
    ps_accu.push_back( outlin );
    ps_accu.push_back( "      0 10   0  0    90   0 "
                       "OneAxx Axx Tic yTacL yNumL %% left y axis\n" );
    ps_accu.push_back( "      0 10  10  0    90 180 "
                       "OneAxx Axx Tic yTacH % yNumH %% right yaxis\n" );
    ps_accu.push_back( "      yCL\n" );
    ps_accu.push_back( "} def\n" );
    ps_accu.push_back( "\n%% modeDD\nplotbefore\n" );
}

//! Plot one spectrum.

void CPlot::addSpec( bool as_line, int style_no,
                     const vector<double>& xp,
                     const vector<double>& yp, const vector<double>& dyp,
                     const vector<double>& z,
                     string xco, string yco, string info
    )
{
#define NCOLOR 6
    static int color[NCOLOR] = { 0x880000, 0x008800, 0x000088,
                                 0x006666, 0x660066, 0x666600 };
    // Checks:
    uint np=xp.size();
    if ( !np )
        throw string( "PROG ERR NPLot::Line x.size=0" );
    if ( np!=yp.size() )
        throw string( "PROG ERR NPLot::Line x.size<>y.size" );

    // Prepare for live display, to be shown by showSpecs():
    string gp_fnam = str( format( "/tmp/%s-%d-%03d.gnu" )
                          % getenv("LOGNAME") % iPlot % gp_fno++ );
    if (gp_fnames!="") gp_fnames += ", ";
    gp_fnames += string("\"") + gp_fnam + "\" notitle";
    if ( as_line )
        gp_fnames += str( format( " with lines lt 1 lc rgb \"#%6x\"" )
                          % color[ style_no % NCOLOR ] );
    else if( dyp.size() )
        gp_fnames += " with errorbars";
    FILE *gp_fd;
    if (!(gp_fd = fopen(gp_fnam.c_str(), "w")))
        throw "cannot save gnuplot data to " + gp_fnam;
    uint nout = 0;
    for (uint i=0; i<np; i++){
        if( xp[i]<X.inf || xp[i]>X.sup )
            throw "Plot::Line: x["+strg(i)+"]="+strg(xp[i])+" out of range";
        if( yp[i]<Y.inf || yp[i]>Y.sup )
            throw "Plot::Line: y["+strg(i)+"]="+strg(yp[i])+" out of range";
        if( dyp.size() )
            fprintf(gp_fd, "%16.8g %16.8g %16.8g\n", xp[i], yp[i], dyp[i] );
        else
            fprintf(gp_fd, "%16.8g %16.8g\n", xp[i], yp[i]);
        nout++;
    }
    fclose(gp_fd);
    if( !nout ){
        cout << "no points in frame: " << info << "\n";
        return;
    }

    // Postscript copy:
    snprintf( outlin, CPLOT_LINSIZ, "\n%3u [", ++ps_snum );
    ps_accu.push_back( outlin );
    for (uint i=0; i<z.size(); i++){
        snprintf( outlin, CPLOT_LINSIZ, " %12g", z[i]);
        ps_accu.push_back( outlin );
    }
    snprintf( outlin, CPLOT_LINSIZ, " ] zValues\n" );
    ps_accu.push_back( outlin );
    if ( as_line )
        snprintf( outlin, CPLOT_LINSIZ, "%2d cstyle", 1 );
    else
        snprintf( outlin, CPLOT_LINSIZ, "%2d pstyle", style_no+1 );
    ps_accu.push_back( outlin );
    snprintf( outlin, CPLOT_LINSIZ-2, " %% (%s -> %s)",
              xco.c_str(), yco.c_str() );
    strncat( outlin, "\n", CPLOT_LINSIZ );
    ps_accu.push_back( outlin );
    for (uint i=0; i<np; i++) {
        snprintf( outlin, CPLOT_LINSIZ,
                  "%7.3f%7.3f%7.3f t%c %% %14.7g wx %14.7g wy\n",
                  X.pc(xp[i]), Y.pc(yp[i]), 
                  dyp.size() ? Y.pc(dyp[i]) : 0,
                  i==0 ? 'i' : i==np-1 ? 'f' : ' ',
                  xp[i], yp[i] );
        ps_accu.push_back( outlin );
    }
}

//! Live display as prepared by addSpec.

void CPlot::showSpecs()
{
    if ( gp_fnames!="" )
        gp_write( "plot " + 
                  str( format( "[%12.8g:%12.8g] [%12.8g:%12.8g] " )
                       % X.inf % X.sup % Y.inf % Y.sup ) +
                  gp_fnames );
}

//! Add documentation lines to postscript output.

void CPlot::plotDoclines( const vector<string>& lDoc )
{
    for (uint i=0; i<lDoc.size(); i++)
        ps_Doc.push_back(lDoc[i]);
}

//! Write buffered plot to postscript file.

void CPlot::writePostscript( bool full_outfile )
{
    string ps_outdir, ps_head, ps_dict;

    // read configuration parameters:
    ps_outdir = NRead::wofmac( "\\psdir" );
    ps_head = NRead::wofmac( "\\pshead" );
    if ( full_outfile )
        ps_dict = NRead::wofmac( "\\psdict" );

    // construct output file name:
    FILE *pssav;
    string flong, cmd, outf;
    while(1) {
        if (ps_fnum>=999)
            throw string( "graph file number overflow" );
        outf = ps_outdir + str( format( "l%d." ) % ++ps_fnum ) +
            ( full_outfile ? "ps" : "psa" );
        if (!(pssav = mystd::glob_fopen(outf.c_str(), "", "", "r")))
            break; // legal exit
        fclose(pssav);
    }
    cout << "save plot in " << outf << "\n";

    // copy headers to output file:
    cmd = string("cat ") + ( full_outfile ? ps_dict : "" ) + " " +
        ps_head + " > " + outf + "\n";
    mystd::system( cmd );
    
    // a redundant check of existence of the outfile:
    if (!(pssav = mystd::glob_fopen(outf.c_str(), "", "", "r", &flong))) {
        throw "have not created file " + outf;
        return;
    }
    fclose(pssav);

    // append specific output to output file:
    if (!(pssav = fopen(flong.c_str(), "a+"))) {
        printf ("cannot write (append) to  file %s\n", flong.c_str());
        return;
    }
    for( uint i=0; i<ps_accu.size(); ++i ){
        // fprintf does not work here because output line may contain "%"
        fwrite( ps_accu[i].c_str(), 1, ps_accu[i].size(), pssav );
    }

    // additional output (do not append this to ps_accu to allow
    // further incrementation of ps_accu):
    fprintf(pssav, "\nblack 0 -4 13 newlist\n");
    for (uint i=0; i<ps_Doc.size(); i++) 
        fprintf(pssav, "{(%s)} infline\n", ps_Doc[i].c_str());
	
    fprintf(pssav, 
            "\n{(%s)}  /filename exch def 10 -2.8 18 showfilename\n\n"
            "EndFrame\n", outf.c_str() );

    // output completed:
    fclose(pssav);
}

//! Endless loop: query for gnuplot commands.

void CPlot::feedGnuplot()
{
    string cmd;
    cout << "entering mygnuplot - to leave, type q\n";
    while(1) {
        cmd = sask("mygnuplot> ");
        if (cmd.substr(0,1)=="q") return;
        gp_write(cmd);
    }
}

//! Info line to characterize this plot window.

string CPlot::info() const
{
    string ret;
    ret  =   "x: " + X.info();
    ret += "  y: " + Y.info();
    return ret;
}

//! Send one line to gnuplot fifo.

void CPlot::gp_write( string in )
{
    string out = in + "\n";
    // printf( "DEBUG GNUPLOT '%s'\n", out.c_str() );
    if( write( gp_fifo, out.c_str(), out.size() ) <= 0 )
        throw string( "could not write to gp_fifo" );
}

//! Format ticks and tacks for postscript file.

void CPlot::ps_ticktack(uint ntack, double *tack, int ntpt, double *ticklim,
			CAxis *A)
{
    uint i;
    ps_accu.push_back( "[\n" );
    if (A->logflag && ( tack[0]<1e-3 || tack[ntack-1]>1e3 )) {
        for (i=0; i<ntack; i++) {
            snprintf( outlin, CPLOT_LINSIZ,
                      "   %9.6f {(10)(%d)sp()} %%{(%g)}\n", 
                      A->pc(tack[i]), (int)(log10(tack[i])),
                      (float) tack[i]);
            ps_accu.push_back( outlin );
        }
    } else {
        for (i=0; i<ntack; i++) {
            snprintf( outlin, CPLOT_LINSIZ, "   %9.6f {(%g)}\n", 
                      A->pc(tack[i]), (float) tack[i]);
            ps_accu.push_back( outlin );
        }
    }
    ps_accu.push_back( "   ] SetTacVec\n" );
    snprintf( outlin, CPLOT_LINSIZ, "   %g %g %d %d SetTicVec%s\n", 
              A->pc(ticklim[0]), A->pc(ticklim[1]), ntack+2, ntpt,
              (A->logflag? "Log" : "Lin"));
    ps_accu.push_back( outlin );
}