/*
 * File: graphics.c
 * Last modified on Mon Jul 25 11:53:41 1994 by eroberts
 * -----------------------------------------------------
 * This file is the top-level file in the implementation of the
 * graphics.h interface for X windows.  The complete implementation
 * also includes the following subsidiary modules:
 *
 *   glibrary.h  Various low-level mathematical functions
 *   xmanager.h  Mediates communication with the X operations
 *   xdisplay.h  Performs the actual drawing operations
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <sys/time.h>
#include <sys/types.h>

#include "genlib.h"
#include "gcalloc.h"
#include "simpio.h"
#include "strlib.h"
#include "extgraph.h"
#include "glibrary.h"
#include "xmanager.h"
#include "xcompat.h"

/*
 * Parameters
 * ----------
 * DefaultSize -- Default point size
 */

#define DefaultSize 12

/*
 * Type: graphicsStateT
 * --------------------
 * This structure holds the variables that make up the graphics state.
 */

typedef struct graphicsStateT {
    double cx, cy;
    string font;
    int size;
    bool erase;
    struct graphicsStateT *link;
} *graphicsStateT;

/*
 * Type: regionStateT
 * ------------------
 * The region assembly process has the character of a finite state
 * machine with the following four states:
 *
 *   NoRegion       Region has not yet been started
 *   RegionStarting Region is started but no line segments yet
 *   RegionActive   First line segment appears
 *   PenHasMoved    Pen has moved during definition
 *
 * The current state determines whether other operations are legal
 * at that point.
 */

typedef enum {
    NoRegion, RegionStarting, RegionActive, PenHasMoved
} regionStateT;

/*
 * Global variables
 * ----------------
 * initialized    TRUE if initialization has been done
 * windowTitle    Current window title (initialized statically)
 * cmdBuffer      Static buffer for sending commands
 * regionState    Current state of the region
 * stateStack     Stack of graphicStateT blocks
 * cx, cy         Current coordinates     | These four variable
 * eraseMode      Setting of erase flag   | consititute the
 * textFont       Current font            | current graphics
 * pointSize      Current point size      | state
 */

static bool initialized = FALSE;
static string windowTitle = "Graphics Window";

static char cmdBuffer[CommandBufferSize];

static regionStateT regionState;

static graphicsStateT stateStack;

static double cx, cy;
static bool eraseMode;
static string textFont;
static int pointSize;

/* Private function prototypes */

static void InitCheck(void);
static void InitGraphicsState(void);

/* Exported entries */

/* Section 1 -- Basic functions from graphics.h */

/*
 * Function: InitGraphics
 * ----------------------
 * The implementation below hides considerable complexity underneath
 * the InitXHandler call.  If you are trying to modify or maintain
 * this implementation, it is important to understand how that
 * function is implemented.  For details, see the xhandler.c
 * implementation.
 */

void InitGraphics(void)
{
    if (initialized) {
        SendCommand(ClearCmd, "");
    } else {
        ProtectVariable(stateStack);
        ProtectVariable(windowTitle);
        ProtectVariable(textFont);
        InitXManager(windowTitle);
        initialized = TRUE;
    }
    InitGraphicsState();
    SetFont("Default");
}

void MovePen(double x, double y)
{
    InitCheck();
    if (regionState == RegionActive) regionState = PenHasMoved;
    cx = x;
    cy = y;
}

void DrawLine(double dx, double dy)
{
    InitCheck();
    switch (regionState) {
      case NoRegion: case RegionActive:
        break;
      case RegionStarting:
        regionState = RegionActive;
        break;
      case PenHasMoved:
        Error("Region segments must be contiguous");
    }
    sprintf(cmdBuffer, "%g %g %g %g", cx, cy, dx, dy);
    SendCommand(LineCmd, cmdBuffer);
    cx += dx;
    cy += dy;
}

void DrawArc(double r, double start, double sweep)
{
    DrawEllipticalArc(r, r, start, sweep);
}

double GetWindowWidth(void)
{
    string line;
    double width;

    InitCheck();
    SendCommand(GetWidthCmd, cmdBuffer);
    line = GetResponse();
    width = StringToReal(line);
    FreeBlock(line);
    return (width);
}

double GetWindowHeight(void)
{
    string line;
    double height;

    InitCheck();
    SendCommand(GetHeightCmd, cmdBuffer);
    line = GetResponse();
    height = StringToReal(line);
    FreeBlock(line);
    return (height);
}

double GetCurrentX(void)
{
    InitCheck();
    return (cx);
}

double GetCurrentY(void)
{
    InitCheck();
    return (cy);
}

/* Section 2 -- Elliptical arcs */

void DrawEllipticalArc(double rx, double ry,
                       double start, double sweep)
{
    double x, y;

    InitCheck();
    switch (regionState) {
      case NoRegion: case RegionActive:
        break;
      case RegionStarting:
        regionState = RegionActive;
        break;
      case PenHasMoved:
        Error("Region segments must be contiguous");
    }
    x = cx + rx * cos(Radians(start + 180));
    y = cy + ry * sin(Radians(start + 180));
    sprintf(cmdBuffer, "%g %g %g %g %g %g", x, y, rx, ry, start, sweep);
    SendCommand(ArcCmd, cmdBuffer);
    cx = x + rx * cos(Radians(start + sweep));
    cy = y + ry * sin(Radians(start + sweep));
}

/* Section 3 -- Graphical structures */

void StartFilledRegion(double grayScale)
{
    InitCheck();
    if (regionState != NoRegion) {
        Error("Region is already in progress");
    }
    if (grayScale < 0 || grayScale > 1) {
        Error("Gray scale for regions must be between 0 and 1");
    }
    regionState = RegionStarting;
    sprintf(cmdBuffer, "%g", grayScale);
    SendCommand(StartRegionCmd, cmdBuffer);
}

void EndFilledRegion(void)
{
    InitCheck();
    if (regionState == NoRegion) {
        Error("EndFilledRegion without StartFilledRegion");
    }
    regionState = NoRegion;
    SendCommand(EndRegionCmd, "");
}

/* Section 4 -- String functions */

void DrawTextString(string text)
{
    InitCheck();
    if (regionState != NoRegion) {
        Error("Text strings are illegal inside a region");
    }
    if (strlen(text) > MaxTextString) {
        Error("Text string too long");
    }
    sprintf(cmdBuffer, "%g %g %s", cx, cy, text);
    SendCommand(TextCmd, cmdBuffer);
    cx += TextStringWidth(text);
}

double TextStringWidth(string text)
{
    string line;
    double result;

    InitCheck();
    if (strlen(text) > MaxTextString) {
        Error("Text string too long");
    }
    sprintf(cmdBuffer, "%s", text);
    SendCommand(WidthCmd, cmdBuffer);
    line = GetResponse();
    result = StringToReal(line);
    FreeBlock(line);
    return (result);
}

void SetFont(string font)
{
    InitCheck();
    if (strlen(font) > MaxFontName) Error("Font name too long");
    textFont = font;
    SetPointSize(pointSize);
}

string GetFont(void)
{
    InitCheck();
    return (CopyString(textFont));
}

void SetPointSize(int size)
{
    string line;

    InitCheck();
    sprintf(cmdBuffer, "%d %s", size, textFont);
    SendCommand(SetFontCmd, cmdBuffer);
    line = GetResponse();
    pointSize = atoi(line);
    textFont = CopyString(line + FindChar(' ', line, 0) + 1);
}

int GetPointSize(void)
{
    InitCheck();
    return (pointSize);
}

/* Section 5 -- Miscellaneous functions */

void SetEraseMode(bool mode)
{
    InitCheck();
    eraseMode = mode;
    sprintf(cmdBuffer, "%d", (int) mode);
    SendCommand(SetEraseCmd, cmdBuffer);
}

bool GetEraseMode(void)
{
    InitCheck();
    return (eraseMode);
}

void SetWindowTitle(string title)
{
    windowTitle = CopyString(title);
    if (initialized) {
        sprintf(cmdBuffer, "%s", windowTitle);
        SendCommand(SetTitleCmd, cmdBuffer);
    }
}

string GetWindowTitle(void)
{
    return (CopyString(windowTitle));
}

void UpdateDisplay(void)
{
    int cnt;

    InitCheck();
    SendCommand(UpdateCmd, "");
}

void Pause(double seconds)
{
    if (initialized) UpdateDisplay();
    usleep((unsigned) (seconds * 1000000));
}

void SaveGraphicsState(void)
{
    graphicsStateT sb;

    InitCheck();
    sb = New(graphicsStateT);
    sb->cx = cx;
    sb->cy = cy;
    sb->font = textFont;
    sb->size = pointSize;
    sb->erase = eraseMode;
    sb->link = stateStack;
    stateStack = sb;
}

void RestoreGraphicsState(void)
{
    graphicsStateT sb;

    InitCheck();
    if (stateStack == NULL) {
        Error("RestoreGraphicsState called before SaveGraphicsState");
    }
    sb = stateStack;
    cx = sb->cx;
    cy = sb->cy;
    textFont = sb->font;
    pointSize = sb->size;
    eraseMode = sb->erase;
    stateStack = sb->link;
    FreeBlock(sb);
}

/* Private functions */

/*
 * Function: InitCheck
 * Usage: InitCheck();
 * -------------------
 * This function merely ensures that the package has been
 * initialized before the client functions are called.
 */

static void InitCheck(void)
{
    if (!initialized) Error("InitGraphics has not been called");
}

/*
 * Function: InitGraphicsState
 * Usage: InitGraphicsState();
 * ---------------------------
 * This function initializes the graphics state elements to
 * their default values.
 */

static void InitGraphicsState(void)
{
    cx = cy = 0;
    eraseMode = FALSE;
    textFont = "Default";
    pointSize = DefaultSize;
    stateStack = NULL;
    regionState = NoRegion;
}
