/**********
Copyright 1990 Regents of the University of California.  All rights reserved.
**********/


#include "ngspice/ngspice.h"
#include "ngspice/graph.h"
#include "ngspice/ftedev.h"
#include "ngspice/fteinput.h"
#include "ngspice/cpdefs.h"
#include "ngspice/ftedefs.h"

#ifdef TCL_MODULE
#include "ngspice/tclspice.h"
#endif

#include "display.h"
#include "variable.h"


static void gen_DatatoScreen(GRAPH *graph, double x, double y, int *screenx, int *screeny);
static int gen_Input(REQUEST *request, RESPONSE *response);
static int nop(void);
#define NOP ((void *)nop)
static int nodev(void);
#define NODEV ((void *)nodev)

#ifndef X_DISPLAY_MISSING
#include "plotting/x11.h"
#endif

#ifdef HAS_WINGUI      /* Graphic-IO under MS Windows */
#include "wdisp/windisp.h"
//#include "wdisp/winprint.h"
#endif

#include "plotting/plot5.h"
#include "postsc.h"
#include "hpgl.h"
#include "svg.h"

DISPDEVICE device[] = {

    { "error", 0, 0, 0, 0, 0, 0,
      (disp_fn_Init_t *) NOP, (disp_fn_NewViewport_t *) NOP,
      (disp_fn_Close_t *) NOP, (disp_fn_Clear_t *) NOP,
      (disp_fn_DrawLine_t *) NOP, (disp_fn_Arc_t *) NOP, (disp_fn_Text_t *) NOP,
      (disp_fn_DefineColor_t *) NOP, (disp_fn_DefineLinestyle_t *) NOP,
      (disp_fn_SetLinestyle_t *) NOP, (disp_fn_SetColor_t *) NOP, (disp_fn_Update_t *) NOP, (disp_fn_Finalize_t *) NOP,
      (disp_fn_Track_t *) NOP, (disp_fn_MakeMenu_t *) NOP, (disp_fn_MakeDialog_t *) NOP, gen_Input,
      (disp_fn_DatatoScreen_t *) NOP,},

#ifndef X_DISPLAY_MISSING
    { "X11", 0, 0, 1024, 864, 0, 0,
      X11_Init, X11_NewViewport,
      X11_Close, X11_Clear,
      X11_DrawLine, X11_Arc, X11_Text,
      X11_DefineColor, X11_DefineLinestyle,
      X11_SetLinestyle, X11_SetColor, X11_Update,  (disp_fn_Finalize_t*) NOP,
      (disp_fn_Track_t *) NODEV, (disp_fn_MakeMenu_t *) NODEV, (disp_fn_MakeDialog_t *) NODEV, X11_Input,
      gen_DatatoScreen,},
#endif

#ifdef HAS_WINGUI      /* Graphic-IO under MS Windows */
    { "Windows", 0, 0, 1000, 1000, 0, 0,
      WIN_Init, WIN_NewViewport,
      WIN_Close, WIN_Clear,
      WIN_DrawLine, WIN_Arc, WIN_Text,
      WIN_DefineColor, WIN_DefineLinestyle,
      WIN_SetLinestyle, WIN_SetColor, WIN_Update,  (disp_fn_Finalize_t*) NOP,
      (disp_fn_Track_t *) NODEV, (disp_fn_MakeMenu_t *) NODEV, (disp_fn_MakeDialog_t *) NODEV, gen_Input,
      gen_DatatoScreen, }, /* WIN_DiagramReady */

    /* Warning: name "WinPrint" do not change! */
    { "WinPrint", 0, 0, 1000, 1000, 0, 0,
      WPRINT_Init, WPRINT_NewViewport,
      WPRINT_Close, WPRINT_Clear,
      WPRINT_DrawLine, WPRINT_Arc, WPRINT_Text,
      WPRINT_DefineColor, WPRINT_DefineLinestyle,
      WPRINT_SetLinestyle, WPRINT_SetColor, WPRINT_Update,  (disp_fn_Finalize_t*) NOP,
      (disp_fn_Track_t *) NODEV, (disp_fn_MakeMenu_t *) NODEV, (disp_fn_MakeDialog_t *) NODEV, (disp_fn_Input_t *) NODEV,
      gen_DatatoScreen, }, /* WPRINT_DiagramReady */
#endif

#ifdef TCL_MODULE
    { "Tk", 0, 0, 1024, 864, 0, 0,
      sp_Tk_Init, sp_Tk_NewViewport,
      sp_Tk_Close, sp_Tk_Clear,
      sp_Tk_DrawLine, sp_Tk_Arc, sp_Tk_Text,
      sp_Tk_DefineColor, sp_Tk_DefineLinestyle,
      sp_Tk_SetLinestyle, sp_Tk_SetColor, sp_Tk_Update,  (disp_fn_Finalize_t*) NOP,
      (disp_fn_Track_t *) NODEV, (disp_fn_MakeMenu_t *) NODEV, (disp_fn_MakeDialog_t *) NODEV, (disp_fn_Input_t *) NODEV,
      gen_DatatoScreen, },
#endif

    { "plot5", 0, 0, 1000, 1000, 0, 0,
      Plt5_Init, Plt5_NewViewport,
      Plt5_Close, Plt5_Clear,
      Plt5_DrawLine, Plt5_Arc, Plt5_Text,
      (disp_fn_DefineColor_t *) NODEV, (disp_fn_DefineLinestyle_t *) NODEV,
      Plt5_SetLinestyle, Plt5_SetColor, Plt5_Update,  (disp_fn_Finalize_t*) NOP,
      (disp_fn_Track_t *) NODEV, (disp_fn_MakeMenu_t *) NODEV, (disp_fn_MakeDialog_t *) NODEV, (disp_fn_Input_t *) NODEV,
      gen_DatatoScreen, },

    { "postscript", 0, 0, 1000, 1000, 0, 0,
      PS_Init, PS_NewViewport,
      PS_Close, PS_Clear,
      PS_DrawLine, PS_Arc, PS_Text,
      (disp_fn_DefineColor_t *) NODEV, (disp_fn_DefineLinestyle_t *) NODEV,
      PS_SetLinestyle, PS_SetColor, PS_Update, PS_Finalize,
      (disp_fn_Track_t *) NODEV, (disp_fn_MakeMenu_t *) NODEV, (disp_fn_MakeDialog_t *) NODEV, (disp_fn_Input_t *) NODEV,
      gen_DatatoScreen, },

    { "svg", 0, 0, 1000, 1000, 0, 0,
      SVG_Init, SVG_NewViewport,
      SVG_Close, SVG_Clear,
      SVG_DrawLine, SVG_Arc, SVG_Text,
      (disp_fn_DefineColor_t*)NODEV, (disp_fn_DefineLinestyle_t*)NODEV,
      SVG_SetLinestyle, SVG_SetColor, SVG_Update, SVG_Finalize,
      (disp_fn_Track_t*)NODEV, (disp_fn_MakeMenu_t*)NODEV, (disp_fn_MakeDialog_t*)NODEV, (disp_fn_Input_t*)NODEV,
      gen_DatatoScreen, },

    { "hpgl", 0, 0, 1000, 1000, 0, 0,
      GL_Init, GL_NewViewport,
      GL_Close, GL_Clear,
      GL_DrawLine, GL_Arc, GL_Text,
      (disp_fn_DefineColor_t *) NODEV, (disp_fn_DefineLinestyle_t *) NODEV,
      GL_SetLinestyle, GL_SetColor, GL_Update,  (disp_fn_Finalize_t*) NOP,
      (disp_fn_Track_t *) NODEV, (disp_fn_MakeMenu_t *) NODEV, (disp_fn_MakeDialog_t *) NODEV, (disp_fn_Input_t *) NODEV,
      gen_DatatoScreen, },

    { "PrinterOnly", 0, 0, 24, 80, 0, 0,
      (disp_fn_Init_t *) NODEV, (disp_fn_NewViewport_t *)  NODEV,
      (disp_fn_Close_t *) NOP, (disp_fn_Clear_t *) NODEV,
      (disp_fn_DrawLine_t *) NODEV, (disp_fn_Arc_t *) NODEV, (disp_fn_Text_t *) NODEV,
      (disp_fn_DefineColor_t *) NODEV, (disp_fn_DefineLinestyle_t *) NODEV,
      (disp_fn_SetLinestyle_t *) NODEV, (disp_fn_SetColor_t *) NODEV, (disp_fn_Update_t *) NOP,  (disp_fn_Finalize_t*) NOP,
      (disp_fn_Track_t *) NODEV, (disp_fn_MakeMenu_t *) NODEV, (disp_fn_MakeDialog_t *) NODEV, gen_Input,
      (disp_fn_DatatoScreen_t *) NODEV, },

};


DISPDEVICE *dispdev = device + NUMELEMS(device) - 1;


DISPDEVICE *
FindDev(char *name)
{
    size_t i;

    for (i = 0; i < NUMELEMS(device); i++)
        if (strcmp(name, device[i].name) == 0)
            return (device + i);

    sprintf(ErrorMessage, "Can't find device %s.", name);
    internalerror(ErrorMessage);

    return (device + 0);
}


void
DevInit(void)
{

#ifndef X_DISPLAY_MISSING
    char buf[128];
#endif

    /* note: do better determination */

    /*
      dumb tradition that got passed on from gi_interface
      to do compile time determination
    */

    dispdev = NULL;

#ifndef X_DISPLAY_MISSING
    /* determine display type */
    if (getenv("DISPLAY") || cp_getvar("display", CP_STRING, buf, sizeof(buf)))
        dispdev = FindDev("X11");
#endif


#ifdef HAS_WINGUI
    if (!dispdev)
        dispdev = FindDev("Windows");
#endif


#ifdef TCL_MODULE
    dispdev = FindDev("Tk");
#endif


    if (!dispdev) {

#if !defined(HAS_WINGUI) && !defined(TCL_MODULE) && !defined(SHARED_MODULE) && (defined(_MSC_VER) || defined(__MINGW32__))
        /* console application under MS Windows */
        fprintf
            (cp_err,
             "Warning: no graphics interface!\n"
             " You may use command 'gnuplot'\n"
             " if GnuPlot is installed.\n");
#elif !defined(X_DISPLAY_MISSING)
        externalerror
            ("no graphics interface;\n"
             " please check if X-server is running,\n"
             " or ngspice is compiled properly (see INSTALL)");
#endif

        dispdev = FindDev("error");

    } else if (dispdev->Init()) {
        fprintf(cp_err, "Warning: can't initialize display device for graphics.\n");
        dispdev = FindDev("error");
    }
}


/* NewViewport is responsible for filling in graph->viewport */
int
NewViewport(GRAPH *pgraph)
{
    return dispdev->NewViewport (pgraph);
}


void
DevClose(void)
{
    dispdev->Close();
}


void
DevClear(void)
{
    dispdev->Clear();
}


void
DevDrawLine(int x1, int y1, int x2, int y2, bool isgrid)
{
    dispdev->DrawLine (x1, y1, x2, y2, isgrid);
}


void
DevDrawArc(int x0, int y0, int radius, double theta, double delta_theta, bool isgrid)
{
    dispdev->DrawArc (x0, y0, radius, theta, delta_theta, isgrid);
}


void DevDrawText(const char *text, int x, int y, int angle)
{
    dispdev->DrawText(text, x, y, angle);
}


void
DefineColor(int colorid, double red, double green, double blue)
{
    dispdev->DefineColor (colorid, red, green, blue);
}


void
DefineLinestyle(int linestyleid, int mask)
{
    dispdev->DefineLinestyle (linestyleid, mask);
}


void
SetLinestyle(int linestyleid)
{
    dispdev->SetLinestyle (linestyleid);
}


void
SetColor(int colorid)
{
    dispdev->SetColor(colorid);
}


void
DevUpdate(void)
{
    if (dispdev)
        dispdev->Update();
}

void
DevFinalize(void)
{
    if (dispdev)
        dispdev->Finalize();
}

/* note: screen coordinates are relative to window
   so need to add viewport offsets */
static void
gen_DatatoScreen(GRAPH *graph, double x, double y, int *screenx, int *screeny)
{
    double low, high;

    /* note: may want to cache datawindowsize/viewportsize */ /* done */

    /* note: think this out---Is 1 part of the viewport? Do we handle
       this correctly? */

    /* have to handle several types of grids */

    /* note: we can't compensate for X's demented y-coordinate system here
       since the grid routines use DevDrawLine w/o calling this routine */

    if ((graph->grid.gridtype == GRID_LOGLOG) ||
        (graph->grid.gridtype == GRID_YLOG))
    {
        low  = mylog10(graph->datawindow.ymin);
        high = mylog10(graph->datawindow.ymax);
        *screeny = (int)((mylog10(y) - low) / (high - low) * graph->viewport.height
                         + 0.5 + graph->viewportyoff);
    } else {
        *screeny = (int)(((y - graph->datawindow.ymin) / graph->aspectratioy)
                         + 0.5 + graph->viewportyoff);
    }

    if ((graph->grid.gridtype == GRID_LOGLOG) ||
        (graph->grid.gridtype == GRID_XLOG))
    {
        low  = mylog10(graph->datawindow.xmin);
        high = mylog10(graph->datawindow.xmax);
        *screenx = (int)((mylog10(x) - low) / (high - low) * graph->viewport.width
                         + 0.5 + graph ->viewportxoff);
    } else {
        *screenx = (int)((x - graph->datawindow.xmin) / graph->aspectratiox
                         + 0.5 + graph ->viewportxoff);
    }
}


void
DatatoScreen(GRAPH *graph, double x, double y, int *screenx, int *screeny)
{
    dispdev->DatatoScreen (graph, x, y, screenx, screeny);
}


void
Input(REQUEST *request, RESPONSE *response)
{
    dispdev->Input (request, response);
}


static int
gen_Input(REQUEST *request, RESPONSE *response)
{
    switch (request->option) {
    case char_option:
        if (response)
            response->option = request->option;
        break;
    default:
        /* just ignore, since we don't want a million error messages */
        if (response)
            response->option = error_option;
        break;
    }

    return (0);
}


/* no operation, do nothing */
static int
nop(void)
{
    return (1);  /* so NewViewport will fail */
}


static int
nodev(void)
{
    sprintf(ErrorMessage,
            "This operation is not defined for display type %s.",
            dispdev->name);
    internalerror(ErrorMessage);
    return (1);
}


void
SaveText(GRAPH *graph, char *text, int x, int y)
{
    struct _keyed *keyed = TMALLOC(struct _keyed, 1);

    if (!graph->keyed) {
        graph->keyed = keyed;
    } else {
        keyed->next = graph->keyed;
        graph->keyed = keyed;
    }

    keyed->text = TMALLOC(char, strlen(text) + 1);
    strcpy(keyed->text, text);

    keyed->x = x;
    keyed->y = y;

    keyed->colorindex = graph->currentcolor;
}


/* if given name of a hardcopy device, finds it and switches devices
   if given NULL, switches back */
int
DevSwitch(char *devname)
{
    static DISPDEVICE *lastdev = NULL;

    if (devname) {

        if (lastdev) {
            internalerror("DevSwitch w/o changing back");
            return (1);
        }

        lastdev = dispdev;
        dispdev = FindDev(devname);

        if (!strcmp(dispdev->name, "error")) {
            internalerror("no hardcopy device");
            /* undo */
            dispdev = lastdev;
            lastdev = NULL;
            return (1);
        }

        dispdev->Init();

    } else {

        if (dispdev)
            dispdev->Close();
        dispdev = lastdev;
        lastdev = NULL;

    }

    return (0);
}
