//-----------------------------------------------------------------------------
// Velmi jednoduchy prohlizec souboru typu SLD (Slide)
//
// Autor: Pavel Tisnovsky
//
//-----------------------------------------------------------------------------

#ifdef __BORLANDC__
#include <windows.h>
#endif

#include <GL/glut.h>                            // hlavickovy soubor funkci GLUTu a OpenGL
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <ctype.h>

#define WINDOW_TITLE    "simple SVG viewer"     // titulek okna
#define WINDOW_WIDTH    800                     // pocatecni velikost okna
#define WINDOW_HEIGHT   600

// typ prikazu
typedef enum {
    ABSOLUTE_VECTOR,
    OFFSET_VECTOR,
    SOLID_FILL,
    END_POINT,
    NEW_COLOR
} Command;

typedef struct AbsoluteVector {
    short int x1;
    short int y1;
    short int x2;
    short int y2;
} AbsoluteVector;

typedef struct OffsetVector {
    signed char x1;
    signed char y1;
    signed char x2;
    signed char y2;
} OffsetVector;

typedef struct SolidFill {
} SolidFill;

typedef struct EndPoint {
    signed char x;
    signed char y;
} EndPoint;

typedef struct NewColor {
    unsigned char color;
} NewColor;

// jeden zaznam ziskany ze souboru HPGL
typedef struct Item {
    Command cmd;                // prikaz
    struct Item *next;          // vazba v linearnim seznamu
    union {                     // anonymni unie
        AbsoluteVector *absoluteVector;
        OffsetVector   *offsetVector;
        SolidFill      *solidFill;
        EndPoint       *endPoint;
        NewColor       *newColor;
    };
} Item;

double scale=1.0;
double xpos=0.0;
double ypos=0.0;
int redraw=1;

Item *p_first;                  // ukazatele v linearnim seznamu
Item *p_last;

// informace o okne
struct {
    int width;
    int height;
} window={WINDOW_WIDTH, WINDOW_HEIGHT};

// informace o mysi
struct {
    int status;
    int xtran0, xtran1, xtran2;
    int ytran0, ytran1, ytran2;
    int ztran0, ztran1, ztran2;
} mouse={0, 0, 0, 0, 0, 0, 0, 0, 0, 0};



//-----------------------------------------------------------------------------
// Inicializace linearniho seznamu
//-----------------------------------------------------------------------------
void initItem(void)
{
    p_first=NULL;
    p_last=NULL;
}



//-----------------------------------------------------------------------------
// Vlozeni polozky do linearniho seznamu
//-----------------------------------------------------------------------------
void addAbsoluteVector(AbsoluteVector *absoluteVector)
{
    Item *item=(Item*)malloc(sizeof(Item));
    item->cmd=ABSOLUTE_VECTOR;
    item->next=NULL;            // prvek bude umisten na konci seznamu
    item->absoluteVector=absoluteVector;
    // seznam je prazdny
    if (p_first==NULL) {
        p_first=item;           // prvni i posledni prvek
        p_last=item;            // seznamu jsou shodne
    }
    // pridani polozky do neprazdneho seznamu
    else {
        p_last->next=item;      // zretezeni prvku na konec seznamu
        p_last=item;            // aktualizace ukazatele na posledni prvek
    }
}



//-----------------------------------------------------------------------------
// Vlozeni polozky do linearniho seznamu
//-----------------------------------------------------------------------------
void addOffsetVector(OffsetVector *offsetVector)
{
    Item *item=(Item*)malloc(sizeof(Item));
    item->cmd=OFFSET_VECTOR;
    item->next=NULL;            // prvek bude umisten na konci seznamu
    item->offsetVector=offsetVector;
    // seznam je prazdny
    if (p_first==NULL) {
        p_first=item;           // prvni i posledni prvek
        p_last=item;            // seznamu jsou shodne
    }
    // pridani polozky do neprazdneho seznamu
    else {
        p_last->next=item;      // zretezeni prvku na konec seznamu
        p_last=item;            // aktualizace ukazatele na posledni prvek
    }
}



//-----------------------------------------------------------------------------
// Vlozeni polozky do linearniho seznamu
//-----------------------------------------------------------------------------
void addSolidFill(SolidFill *solidFill)
{
    Item *item=(Item*)malloc(sizeof(Item));
    item->cmd=SOLID_FILL;
    item->next=NULL;            // prvek bude umisten na konci seznamu
    item->solidFill=solidFill;
    // seznam je prazdny
    if (p_first==NULL) {
        p_first=item;           // prvni i posledni prvek
        p_last=item;            // seznamu jsou shodne
    }
    // pridani polozky do neprazdneho seznamu
    else {
        p_last->next=item;      // zretezeni prvku na konec seznamu
        p_last=item;            // aktualizace ukazatele na posledni prvek
    }
}



//-----------------------------------------------------------------------------
// Vlozeni polozky do linearniho seznamu
//-----------------------------------------------------------------------------
void addEndPoint(EndPoint *endPoint)
{
    Item *item=(Item*)malloc(sizeof(Item));
    item->cmd=END_POINT;
    item->next=NULL;            // prvek bude umisten na konci seznamu
    item->endPoint=endPoint;
    // seznam je prazdny
    if (p_first==NULL) {
        p_first=item;           // prvni i posledni prvek
        p_last=item;            // seznamu jsou shodne
    }
    // pridani polozky do neprazdneho seznamu
    else {
        p_last->next=item;      // zretezeni prvku na konec seznamu
        p_last=item;            // aktualizace ukazatele na posledni prvek
    }
}



//-----------------------------------------------------------------------------
// Vlozeni polozky do linearniho seznamu
//-----------------------------------------------------------------------------
void addNewColor(NewColor *newColor)
{
    Item *item=(Item*)malloc(sizeof(Item));
    item->cmd=NEW_COLOR;
    item->next=NULL;            // prvek bude umisten na konci seznamu
    item->newColor=newColor;
    // seznam je prazdny
    if (p_first==NULL) {
        p_first=item;           // prvni i posledni prvek
        p_last=item;            // seznamu jsou shodne
    }
    // pridani polozky do neprazdneho seznamu
    else {
        p_last->next=item;      // zretezeni prvku na konec seznamu
        p_last=item;            // aktualizace ukazatele na posledni prvek
    }
}



//-----------------------------------------------------------------------------
// Precteni SLD souboru
//-----------------------------------------------------------------------------
int readItems(char *fileName)
{
    char header[31];
    FILE *fin=fopen(fileName, "rb");

    if (!fin) return 0;
    // nacteni hlavicky
    //printf("%d\n", ftell(fin));
    fread(header, 31, 1, fin);
    //printf("%d\n", ftell(fin));
    puts(header);
    // smycka, ve ktere se zanalyzuje cely soubor typu SLD
    while(1) {
        int b1;
        int b2;
        b1=fgetc(fin);
        b2=fgetc(fin);
        // test na ukonceni smycky -> konec souboru
        if (b1==EOF || b2==EOF)
            break;
        printf("(%6d) %02x %02x\t", ftell(fin), b1, b2);
        if (b2>=0x00 && b2<=0x7f) {
            unsigned char array[6];
            AbsoluteVector *a=(AbsoluteVector*)malloc(sizeof(AbsoluteVector));
            puts("absolute vector");
            fread(array, 6, 1, fin);
            a->x1=(b2<<8)+b1;
            a->y1=array[0]+(array[1]<<8);
            a->x2=array[2]+(array[3]<<8);
            a->y2=array[4]+(array[5]<<8);
            addAbsoluteVector(a);
        }
        else {
            switch (b2) {
                case 0xfb:  // offset vektor
                    puts("offset");
                    signed char array[3];
                    OffsetVector *o=(OffsetVector*)malloc(sizeof(OffsetVector));
                    fread(array, 3, 1, fin);
                    o->x1=b1;
                    o->y1=array[0];
                    o->x2=array[1];
                    o->y2=array[2];
                    addOffsetVector(o);
                    break;
                case 0xfc:  // konec souboru
                    puts("end of file");
                    break;
                case 0xfd:  // vypln - neimplementovano
                    puts("solid fill");
                    fread(header, 4, 1, fin);
                    break;
                case 0xfe:  // koncovy bod vektoru (relativni)
                    puts("endpoint vector");
                    signed char y=fgetc(fin);
                    EndPoint *e=(EndPoint*)malloc(sizeof(EndPoint));
                    e->x=b1;
                    e->y=y;
                    addEndPoint(e);
                    break;
                case 0xff:
                    puts("new color");
                    NewColor *c=(NewColor*)malloc(sizeof(NewColor));
                    c->color=b1;
                    addNewColor(c);
                    break;
                default:
                    puts("undefined");
                    break;
            }
        }
    }
    fclose(fin);
    return 1;
}



//-----------------------------------------------------------------------------
// Prekresleni vektorove kresby
//-----------------------------------------------------------------------------
void redrawDrawing(double scale,                    // meritko obrazce
                   double xpos,                     // posun obrazce
                   double ypos)
{
    // velmi zjednodusena paleta
    static float palette[][3]={
        {1.0, 1.0, 1.0},
        {0.0, 0.0, 1.0},
        {0.0, 1.0, 0.0},
        {0.0, 1.0, 1.0},
        {1.0, 0.0, 0.0},
        {1.0, 0.0, 1.0},
        {1.0, 1.0, 0.0},
        {0.5, 0.5, 0.5},
    };
    Item *item=p_first;
    int x1=0, y1=0, x2=0, y2=0;
    float  r=1.0, g=1.0, b=1.0;                     // barva kresliciho pera
    float  scale2=mouse.ztran0/100.0+1.0;           // zmena meritka zadana mysi
    unsigned char c;

    // posun a zmena meritka kresby
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef(xpos, ypos, 0);                    // posun zadany klavesnici
    glTranslatef(mouse.xtran0, -mouse.ytran0, 0);   // posun zadany mysi
    // posunuti pocatku (pro zmenu meritka vuci stredu okna
    // je nutna mala programova uprava)
    glTranslatef(window.width/2, window.height/2, 0);
    glScalef(scale, scale, scale);                  // zmena meritka zadana klavesnici
    glScalef(scale2, scale2, scale2);               // zmena meritka zadana mysi
    glScalef(0.1, 0.1, 0.1);
    glTranslatef(-window.width/2, -window.height/2, 0);
    glColor3f(1.0, 1.0, 1.0);

    // projit celym seznamem a aplikovat v nem ulozene prikazy
    while (item!=NULL) {
        Command cmd=item->cmd;                      // nacist prikaz
        switch (cmd) {
            case ABSOLUTE_VECTOR:                   // absolutni vektor - dva body
                x1=item->absoluteVector->x1;
                y1=item->absoluteVector->y1;
                x2=item->absoluteVector->x2;
                y2=item->absoluteVector->y2;
                glBegin(GL_LINES);
                    glVertex2i((int)x1, (int)y1);
                    glVertex2i((int)x2, (int)y2);
                glEnd();
                break;
            case OFFSET_VECTOR:                     // relativni vektor - dva body
                x2=x1+item->offsetVector->x1;
                y2=y1+item->offsetVector->y1;
                x1=x1+item->offsetVector->x1;
                y1=y1+item->offsetVector->y1;
                glBegin(GL_LINES);
                    glVertex2i((int)x1, (int)y1);
                    glVertex2i((int)x2, (int)y2);
                glEnd();
                break;
            case END_POINT:                         // koncovy bod vektoru
                x2=x1+item->endPoint->x;
                y2=y1+item->endPoint->y;
                glBegin(GL_LINES);
                    glVertex2i((int)x1, (int)y1);
                    glVertex2i((int)x2, (int)y2);
                glEnd();
                x1=x2;
                y1=y2;
                break;
            case SOLID_FILL:                        // neimplementovano
                break;
            case NEW_COLOR:                         // nastaveni barvy
                c=item->newColor->color;
                r=palette[c & 0x07][0];
                g=palette[c & 0x07][1];
                b=palette[c & 0x07][2];
                glColor3f(r, g, b);
                break;
            default:                                // ostatni prikazy ignorujeme
                break;
        }
        item=item->next;                            // prechod na dalsi polozku
    }
}



//-----------------------------------------------------------------------------
// Funkce volana pro inicializaci vykreslovani
//-----------------------------------------------------------------------------
void onInit(void)
{
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);       // barva pozadi
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);      // mod ulozeni pixelu
    glPointSize(1.0f);
    glLineWidth(1.0f);
    glEnable(GL_POINT_SMOOTH);
    glEnable(GL_LINE_SMOOTH);
}



//-----------------------------------------------------------------------------
// Nastaveni souradneho systemu v zavislosti na velikosti okna
//-----------------------------------------------------------------------------
void onResize(int w, int h)                     // argumenty w a h reprezentuji novou velikost okna
{
    glViewport(0, 0, w, h);                     // viditelna oblast pres cele okno
    glMatrixMode(GL_PROJECTION);                // zacatek modifikace projekcni matice
    glLoadIdentity();                           // vymazani projekcni matice (=identita)
    glOrtho(0, w, 0, h, -1, 1);                 // mapovani abstraktnich souradnic do souradnic okna
    window.width=w;
    window.height=h;
}



//-----------------------------------------------------------------------------
// Tato callback funkce je zavolana pri kazdem prekresleni okna
//-----------------------------------------------------------------------------
void onDisplay(void)
{
    glClear(GL_COLOR_BUFFER_BIT);               // vymazani vsech bitovych rovin barvoveho bufferu
    glDrawBuffer(GL_BACK);                      // pixmapa se bude kreslit do zadniho barvoveho bufferu
    redrawDrawing(scale, xpos, ypos);
    glFlush();                                  // provedeni a vykresleni vsech zmen
    glutSwapBuffers();
}



//-----------------------------------------------------------------------------
// Tato callback funkce je zavolana pri stlaceni ASCII klavesy
//-----------------------------------------------------------------------------
#ifdef __BORLANDC__
#pragma option -w-par
#endif
void onKeyboard(unsigned char key, int x, int y)
{
    key=(key>='A' && key<='Z') ? key-'A'+'a': key;
    if (key>='0' && key<='9') { glutPostRedisplay(); } // nastaveni barvove palety
    switch (key) {
        case 27:               // pokud byla stlacena klavesa ESC, konec programu
        case 'q': exit(0); break; // totez co klavesa ESC
        default:           break;
    }
}
#ifdef __BORLANDC__
#pragma option -w+par
#endif



//-----------------------------------------------------------------------------
// Tato callback funkce je zavolana pri stlaceni non-ASCII klavesy
//-----------------------------------------------------------------------------
#ifdef __BORLANDC__
#pragma option -w-par
#endif
void onSpecial(int key, int x, int y)
{
    // posun kresby a zmena meritka
    switch (key) {
        case GLUT_KEY_LEFT:      xpos-=5;   redraw=1;  glutPostRedisplay(); break;
        case GLUT_KEY_RIGHT:     xpos+=5;   redraw=1;  glutPostRedisplay(); break;
        case GLUT_KEY_UP:        ypos+=5;   redraw=1;  glutPostRedisplay(); break;
        case GLUT_KEY_DOWN:      ypos-=5;   redraw=1;  glutPostRedisplay(); break;
        case GLUT_KEY_PAGE_UP:   scale*=1.1; redraw=1; glutPostRedisplay(); break;
        case GLUT_KEY_PAGE_DOWN: scale/=1.1; redraw=1; glutPostRedisplay(); break;
        default:                                                                                 break;
    }
}
#ifdef __BORLANDC__
#pragma option -w+par
#endif



//-----------------------------------------------------------------------------
// Tato callback funkce je zavolana pri stlaceni ci pusteni tlacitka mysi
//-----------------------------------------------------------------------------
void onMouse(int button, int state, int x, int y)
{
    switch (button) {
        case GLUT_LEFT_BUTTON:          // posun kresby
            if (state==GLUT_DOWN) {
                mouse.status=1;
                mouse.xtran1=x;
                mouse.ytran1=y;
            }
            else {
                mouse.status=0;
                mouse.xtran2=mouse.xtran0;
                mouse.ytran2=mouse.ytran0;
            }
            break;
        case GLUT_RIGHT_BUTTON:         // zmena meritka kresby
            if (state==GLUT_DOWN) {
                mouse.status=2;
                mouse.ztran1=y;
            }
            else {
                mouse.status=0;
                mouse.ztran2=mouse.ztran0;
            }
            break;
        default:
            break;
    }
}



//-----------------------------------------------------------------------------
// Tato callback funkce je zavolana pri posunu kurzoru mysi
//-----------------------------------------------------------------------------
void onMouseMotion(int x, int y)
{
    switch (mouse.status) {
        case 1:                 // posun kresby
            mouse.xtran0=mouse.xtran2+x-mouse.xtran1;
            mouse.ytran0=mouse.ytran2+y-mouse.ytran1;
            glutPostRedisplay();
            break;
        case 2:                 // zmena meritka kresby
            mouse.ztran0=mouse.ztran2+y-mouse.ztran1;
            glutPostRedisplay();
            break;
        default:
            break;
    }
}



//-----------------------------------------------------------------------------
// Hlavni funkce konzolove aplikace
//-----------------------------------------------------------------------------
int main(int argc, char **argv)
{
    glutInit(&argc, argv);                      // inicializace knihovny GLUT
    glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);
    glutCreateWindow(WINDOW_TITLE);             // vytvoreni okna pro kresleni
    glutReshapeWindow(WINDOW_WIDTH, WINDOW_HEIGHT);// zmena velikosti okna
    glutPositionWindow(100, 100);               // pozice leveho horniho rohu okna
    glutDisplayFunc(onDisplay);                 // registrace funkce volane pri prekreslovani okna
    glutReshapeFunc(onResize);                  // registrace funkce volane pri zmene velikosti okna
    glutKeyboardFunc(onKeyboard);               // registrace funkce volane pri stlaceni klavesy
    glutSpecialFunc(onSpecial);                 // registrace funkce volane pri stlaceni specialni klavesy
    glutMouseFunc(onMouse);                     // funkce volana pri stlaceni tlacitka mysi
    glutMotionFunc(onMouseMotion);              // funkce volana pri posunu mysi
    onInit();                                   // inicializace vykreslovani
    if (!readItems(argv[1])) return 0;          // nacteni celeho souboru s kresbou
    glutMainLoop();                             // nekonecna smycka, kde se volaji zaregistrovane funkce
    return 0;                                   // navratova hodnota vracena operacnimu systemu
}



//-----------------------------------------------------------------------------
// finito
//-----------------------------------------------------------------------------