/*
 * FTGLDemo - advanced demo for FTGL, the OpenGL font library
 *
 * Copyright (c) 2001-2004 Henry Maddocks <ftgl@opengl.geek.nz>
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include "config.h"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#if defined HAVE_GL_GLUT_H
#   include <GL/glut.h>
#elif defined HAVE_GLUT_GLUT_H
#   include <GLUT/glut.h>
#else
#   error GLUT headers not present
#endif

#include <FTGL/ftgl.h>

#include "tb.h"

// YOU'LL PROBABLY WANT TO CHANGE THESE
#if defined FONT_FILE
    char const *defaultFonts[] = { FONT_FILE };
    const int NumDefaultFonts = 1;
#elif defined __APPLE_CC__
    char const *defaultFonts[] = { "/System/Library/Fonts/Helvetica.dfont",
                                   "/System/Library/Fonts/Geneva.dfont" };
    const int NumDefaultFonts = 2;
#elif defined WIN32
    char const *defaultFonts[] = { "C:\\WINNT\\Fonts\\arial.ttf" };
    const int NumDefaultFonts = 1;
#else
    // Put your font files here if configure did not find any.
    char const *defaultFonts[] = { };
    const int NumDefaultFonts = 0;
#endif

/* Set this to 1 to build a Mac os app (ignore the command line args). */
#ifndef IGNORE_ARGV
#   define IGNORE_ARGV 0
#endif /* IGNORE_ARGV */

#define EDITING 1
#define INTERACTIVE 2

#define FTGL_BITMAP 0
#define FTGL_PIXMAP 1
#define FTGL_OUTLINE 2
#define FTGL_POLYGON 3
#define FTGL_EXTRUDE 4
#define FTGL_TEXTURE 5
const int NumStyles = 6;

char const * const *fontfiles;
int current_font = FTGL_EXTRUDE;

GLint w_win = 640, h_win = 480;
int mode = INTERACTIVE;
int carat = 0;

FTSimpleLayout simpleLayout;
FTLayout *layouts[] = { &simpleLayout, NULL };
int currentLayout = 0;
const int NumLayouts = 2;

const float InitialLineLength = 300.0f;

const float OX = -100;
const float OY = 200;

//wchar_t myString[16] = { 0x6FB3, 0x9580};
char myString[4096];

int totalFonts;
static FTFont** fonts;
static FTPixmapFont* infoFont;

void SetCamera(void);

inline int GetStyle()
{
    return current_font % NumStyles;
}

inline int GetFace()
{
    return current_font / NumStyles;
}

void setUpLighting()
{
    // Set up lighting.
    float light1_ambient[4]  = { 1.0, 1.0, 1.0, 1.0 };
    float light1_diffuse[4]  = { 1.0, 0.9, 0.9, 1.0 };
    float light1_specular[4] = { 1.0, 0.7, 0.7, 1.0 };
    float light1_position[4] = { -1.0, 1.0, 1.0, 0.0 };
    glLightfv(GL_LIGHT1, GL_AMBIENT,  light1_ambient);
    glLightfv(GL_LIGHT1, GL_DIFFUSE,  light1_diffuse);
    glLightfv(GL_LIGHT1, GL_SPECULAR, light1_specular);
    glLightfv(GL_LIGHT1, GL_POSITION, light1_position);
    glEnable(GL_LIGHT1);

    float light2_ambient[4]  = { 0.2, 0.2, 0.2, 1.0 };
    float light2_diffuse[4]  = { 0.9, 0.9, 0.9, 1.0 };
    float light2_specular[4] = { 0.7, 0.7, 0.7, 1.0 };
    float light2_position[4] = { 1.0, -1.0, -1.0, 0.0 };
    glLightfv(GL_LIGHT2, GL_AMBIENT,  light2_ambient);
    glLightfv(GL_LIGHT2, GL_DIFFUSE,  light2_diffuse);
    glLightfv(GL_LIGHT2, GL_SPECULAR, light2_specular);
    glLightfv(GL_LIGHT2, GL_POSITION, light2_position);
    //glEnable(GL_LIGHT2);

    float front_emission[4] = { 0.3, 0.2, 0.1, 0.0 };
    float front_ambient[4]  = { 0.2, 0.2, 0.2, 0.0 };
    float front_diffuse[4]  = { 0.95, 0.95, 0.8, 0.0 };
    float front_specular[4] = { 0.6, 0.6, 0.6, 0.0 };
    glMaterialfv(GL_FRONT, GL_EMISSION, front_emission);
    glMaterialfv(GL_FRONT, GL_AMBIENT, front_ambient);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, front_diffuse);
    glMaterialfv(GL_FRONT, GL_SPECULAR, front_specular);
    glMaterialf(GL_FRONT, GL_SHININESS, 16.0);
    glColor4fv(front_diffuse);

    glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_FALSE);
    glEnable(GL_CULL_FACE);
    glColorMaterial(GL_FRONT, GL_DIFFUSE);
    glEnable(GL_COLOR_MATERIAL);

    glEnable(GL_LIGHTING);
    glShadeModel(GL_SMOOTH);
}


void setUpFonts(int numFontFiles)
{
    // The total number of fonts is styles * faces
    totalFonts = numFontFiles*NumStyles;

    // Allocate an array to hold all fonts
    fonts = new FTFont *[totalFonts];

    // Instantiate and configure named fonts
    for(int i = 0; i < numFontFiles; i++)
    {
        fonts[i*NumStyles + FTGL_BITMAP] = new FTBitmapFont(fontfiles[i]);
        fonts[i*NumStyles + FTGL_PIXMAP] = new FTPixmapFont(fontfiles[i]);
        fonts[i*NumStyles + FTGL_OUTLINE] = new FTOutlineFont(fontfiles[i]);
        fonts[i*NumStyles + FTGL_POLYGON] = new FTPolygonFont(fontfiles[i]);
        fonts[i*NumStyles + FTGL_EXTRUDE] = new FTExtrudeFont(fontfiles[i]);
        fonts[i*NumStyles + FTGL_TEXTURE] = new FTTextureFont(fontfiles[i]);

        for(int x = 0; x < NumStyles; ++x)
        {
            int j = i * NumStyles + x;

            if(fonts[j]->Error())
            {
                fprintf(stderr, "Failed to open font %s\n", fontfiles[i]);
                exit(1);
            }

            if(!fonts[j]->FaceSize(24))
            {
                fprintf(stderr, "Failed to set size\n");
                exit(1);
            }

            fonts[j]->Depth(20);
            fonts[j]->CharMap(ft_encoding_unicode);
        }
    }

    infoFont = new FTPixmapFont(fontfiles[0]);

    if(infoFont->Error())
    {
        fprintf(stderr, "Failed to open font %s\n", fontfiles[0]);
        exit(1);
    }

    infoFont->FaceSize(18);

    strcpy(myString, "OpenGL is a powerful software interface for graphics "
           "hardware that allows graphics programmers to produce high-quality "
           "color images of 3D objects. abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL"
           "MNOPQRSTUVWXYZ0123456789");
}


void renderFontmetrics()
{
    FTBBox bbox;
    float x1, y1, z1, x2, y2, z2;

    // If there is a layout, use it to compute the bbox, otherwise query as
    // a string.
    if(layouts[currentLayout])
        bbox = layouts[currentLayout]->BBox(myString);
    else
        bbox = fonts[current_font]->BBox(myString);

    x1 = bbox.Lower().Xf(); y1 = bbox.Lower().Yf(); z1 = bbox.Lower().Zf();
    x2 = bbox.Upper().Xf(); y2 = bbox.Upper().Yf(); z2 = bbox.Upper().Zf();

    // Draw the bounding box
    glDisable(GL_LIGHTING);
    glDisable(GL_TEXTURE_2D);
            glEnable(GL_LINE_SMOOTH);
            glEnable(GL_BLEND);
            glBlendFunc(GL_SRC_ALPHA, GL_ONE); // GL_ONE_MINUS_SRC_ALPHA

    glColor3f(0.0, 1.0, 0.0);
    // Draw the front face
    glBegin(GL_LINE_LOOP);
        glVertex3f(x1, y1, z1);
        glVertex3f(x1, y2, z1);
        glVertex3f(x2, y2, z1);
        glVertex3f(x2, y1, z1);
    glEnd();
    // Draw the back face
    if((GetStyle() == FTGL_EXTRUDE) && (z1 != z2))
    {
        glBegin(GL_LINE_LOOP);
            glVertex3f(x1, y1, z2);
            glVertex3f(x1, y2, z2);
            glVertex3f(x2, y2, z2);
            glVertex3f(x2, y1, z2);
        glEnd();
        // Join the faces
        glBegin(GL_LINES);
            glVertex3f(x1, y1, z1);
            glVertex3f(x1, y1, z2);

            glVertex3f(x1, y2, z1);
            glVertex3f(x1, y2, z2);

            glVertex3f(x2, y2, z1);
            glVertex3f(x2, y2, z2);

            glVertex3f(x2, y1, z1);
            glVertex3f(x2, y1, z2);
        glEnd();
    }

    // Render layout-specific metrics
    if(!layouts[currentLayout])
    {
        // There is no layout. Draw the baseline, Ascender and Descender
        glBegin(GL_LINES);
            glColor3f(0.0, 0.0, 1.0);
            glVertex3f(0.0, 0.0, 0.0);
            glVertex3f(fonts[current_font]->Advance(myString), 0.0, 0.0);
            glVertex3f(0.0, fonts[current_font]->Ascender(), 0.0);
            glVertex3f(0.0, fonts[current_font]->Descender(), 0.0);
        glEnd();
    }
    else if (layouts[currentLayout]
              && (dynamic_cast <FTSimpleLayout *>(layouts[currentLayout])))
    {
        float lineWidth = ((FTSimpleLayout *)layouts[currentLayout])->GetLineLength();

        // The layout is a SimpleLayout.  Render guides that mark the edges
        // of the wrap region.
        glColor3f(0.5, 1.0, 1.0);
        glBegin(GL_LINES);
            glVertex3f(0, 10000, 0);
            glVertex3f(0, -10000, 0);
            glVertex3f(lineWidth, 10000, 0);
            glVertex3f(lineWidth, -10000, 0);
        glEnd();
    }

    // Draw the origin
    glColor3f(1.0, 0.0, 0.0);
    glPointSize(5.0);
    glBegin(GL_POINTS);
        glVertex3f(0.0, 0.0, 0.0);
    glEnd();
}


void renderFontInfo()
{
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D(0, w_win, 0, h_win);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    // draw mode
    glColor3f(1.0, 1.0, 1.0);
    glRasterPos2f(20.0f , h_win - (20.0f + infoFont->Ascender()));

    switch(mode)
    {
        case EDITING:
            infoFont->Render("Edit Mode");
            break;
        case INTERACTIVE:
            break;
    }

    // draw font type
    glRasterPos2i(20 , 20);
    switch(GetStyle())
    {
        case FTGL_BITMAP:
            infoFont->Render("Bitmap Font");
            break;
        case FTGL_PIXMAP:
            infoFont->Render("Pixmap Font");
            break;
        case FTGL_OUTLINE:
            infoFont->Render("Outline Font");
            break;
        case FTGL_POLYGON:
            infoFont->Render("Polygon Font");
            break;
        case FTGL_EXTRUDE:
            infoFont->Render("Extruded Font");
            break;
        case FTGL_TEXTURE:
            infoFont->Render("Texture Font");
            break;
    }

    glRasterPos2f(20.0f , 20.0f + infoFont->Ascender() - infoFont->Descender());
    infoFont->Render(fontfiles[GetFace()]);

    // If the current layout is a SimpleLayout, output the alignemnt mode
    if(layouts[currentLayout]
        && (dynamic_cast <FTSimpleLayout *>(layouts[currentLayout])))
    {
        glRasterPos2f(20.0f , 20.0f + 2*(infoFont->Ascender() - infoFont->Descender()));
        // Output the alignment mode of the layout
        switch (((FTSimpleLayout *)layouts[currentLayout])->GetAlignment())
        {
            case FTGL::ALIGN_LEFT:
                infoFont->Render("Align Left");
                break;
            case FTGL::ALIGN_RIGHT:
                infoFont->Render("Align Right");
                break;
            case FTGL::ALIGN_CENTER:
                infoFont->Render("Align Center");
                break;
            case FTGL::ALIGN_JUSTIFY:
                infoFont->Render("Align Justified");
                break;
        }
    }
}


void do_display (void)
{
    switch(GetStyle())
    {
        case FTGL_BITMAP:
        case FTGL_PIXMAP:
        case FTGL_OUTLINE:
            break;
        case FTGL_POLYGON:
            glDisable(GL_BLEND);
            setUpLighting();
            break;
        case FTGL_EXTRUDE:
            glEnable(GL_DEPTH_TEST);
            glDisable(GL_BLEND);
            setUpLighting();
            break;
        case FTGL_TEXTURE:
            glEnable(GL_TEXTURE_2D);
            glDisable(GL_DEPTH_TEST);
            setUpLighting();
            glNormal3f(0.0, 0.0, 1.0);
            break;

    }

    glColor3f(1.0, 1.0, 1.0);
    // If you do want to switch the color of bitmaps rendered with glBitmap,
    // you will need to explicitly call glRasterPos (or its ilk) to lock
    // in a changed current color.

    // If there is an active layout use it to render the font
    if (layouts[currentLayout])
    {
        layouts[currentLayout]->Render(myString);
    }
    else
    {
        fonts[current_font]->Render(myString);
    }

    renderFontmetrics();
    renderFontInfo();
}


void display(void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    SetCamera();

    switch(GetStyle())
    {
        case FTGL_BITMAP:
        case FTGL_PIXMAP:
            glRasterPos2i((long)(w_win / 2 + OX), (long)(h_win / 2 + OY));
            glTranslatef(w_win / 2 + OX, h_win / 2 + OY, 0.0);
            break;
        case FTGL_OUTLINE:
        case FTGL_POLYGON:
        case FTGL_EXTRUDE:
        case FTGL_TEXTURE:
         glTranslatef(OX, OY, 0);
            tbMatrix();
            break;
    }

    glPushMatrix();

    do_display();

    glPopMatrix();

    glutSwapBuffers();
}


void myinit(int numFontFiles)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glClearColor(0.13, 0.17, 0.32, 0.0);
    glColor3f(1.0, 1.0, 1.0);

    glEnable(GL_CULL_FACE);
    glFrontFace(GL_CCW);

    glEnable(GL_DEPTH_TEST);

    glEnable(GL_POLYGON_OFFSET_LINE);
    glPolygonOffset(1.0, 1.0); // ????

    SetCamera();

    tbInit(GLUT_LEFT_BUTTON);
    tbAnimate(GL_FALSE);

    setUpFonts(numFontFiles);

    // Configure the SimpleLayout
    simpleLayout.SetLineLength(InitialLineLength);
    simpleLayout.SetFont(fonts[current_font]);
}


void parsekey(unsigned char key, int x, int y)
{
    switch (key)
    {
    case 27:
        exit(0);
        break;
    case 13:
        if(mode == EDITING)
        {
            mode = INTERACTIVE;
        }
        else
        {
            mode = EDITING;
            carat = 0;
        }
        break;
    case '\t':
        // If current layout is a SimpleLayout, change its alignment properties
        if(layouts[currentLayout]
             && (dynamic_cast <FTSimpleLayout *>(layouts[currentLayout])))
        {
            FTSimpleLayout *l = (FTSimpleLayout *)layouts[currentLayout];
            // Decrement the layout
            switch (l->GetAlignment())
            {
            case FTGL::ALIGN_LEFT:
                l->SetAlignment(FTGL::ALIGN_RIGHT);
                break;
            case FTGL::ALIGN_RIGHT:
                l->SetAlignment(FTGL::ALIGN_CENTER);
                break;
            case FTGL::ALIGN_CENTER:
                l->SetAlignment(FTGL::ALIGN_JUSTIFY);
                break;
            case FTGL::ALIGN_JUSTIFY:
                l->SetAlignment(FTGL::ALIGN_LEFT);
                break;
            }
        }
        break;
    default:
        if(mode == INTERACTIVE)
        {
            myString[0] = key;
            myString[1] = 0;
        }
        else
        {
            myString[carat] = key;
            myString[carat + 1] = 0;
            carat = carat > 2000 ? 2000 : carat + 1;
        }
        break;
    }

    glutPostRedisplay();
}


void parseSpecialKey(int key, int x, int y)
{
    FTSimpleLayout *l = NULL;

    // If the currentLayout is a SimpleLayout store a pointer in l
    if(layouts[currentLayout]
        && (dynamic_cast <FTSimpleLayout *>(layouts[currentLayout])))
    {
        l = (FTSimpleLayout *)layouts[currentLayout];
    }

    switch (key)
    {
    case GLUT_KEY_UP:
        current_font = (GetFace()*NumStyles + (current_font + 1)%NumStyles)%totalFonts;
        break;
    case GLUT_KEY_DOWN:
        current_font = (GetFace()*NumStyles + (current_font + NumStyles - 1)%NumStyles)%totalFonts;
        break;
    case GLUT_KEY_LEFT:
        fonts[current_font]->FaceSize(fonts[current_font]->FaceSize() - 1);
        break;
    case GLUT_KEY_RIGHT:
        fonts[current_font]->FaceSize(fonts[current_font]->FaceSize() + 1);
        break;
    case GLUT_KEY_PAGE_UP:
        current_font = (current_font + NumStyles)%totalFonts;
        break;
    case GLUT_KEY_PAGE_DOWN:
        current_font = (current_font + totalFonts - NumStyles)%totalFonts;
        break;
    case GLUT_KEY_HOME:
        currentLayout = (currentLayout + 1)%NumLayouts;
        break;
    case GLUT_KEY_END:
        currentLayout = (currentLayout + NumLayouts - 1)%NumLayouts;
        break;
    case GLUT_KEY_F1:
    case GLUT_KEY_F10:
        // If the current layout is simple decrement its line length
        if (l) l->SetLineLength(l->GetLineLength() - 10.0f);
        break;
    case GLUT_KEY_F2:
    case GLUT_KEY_F11:
        // If the current layout is simple increment its line length
        if (l) l->SetLineLength(l->GetLineLength() + 10.0f);
        break;
    }

    // If the current layout is a SimpleLayout, update its font.
    if(l)
    {
        l->SetFont(fonts[current_font]);
    }

    glutPostRedisplay();
}


void motion(int x, int y)
{
    tbMotion(x, y);
}

void mouse(int button, int state, int x, int y)
{
    tbMouse(button, state, x, y);
}

void myReshape(int w, int h)
{
    glMatrixMode (GL_MODELVIEW);
    glViewport (0, 0, w, h);
    glLoadIdentity();

    w_win = w;
    h_win = h;
    SetCamera();

    tbReshape(w_win, h_win);
}

void SetCamera(void)
{
    switch(GetStyle())
    {
        case FTGL_BITMAP:
        case FTGL_PIXMAP:
            glMatrixMode(GL_PROJECTION);
            glLoadIdentity();
            gluOrtho2D(0, w_win, 0, h_win);
            glMatrixMode(GL_MODELVIEW);
            glLoadIdentity();
            break;
        case FTGL_OUTLINE:
        case FTGL_POLYGON:
        case FTGL_EXTRUDE:
        case FTGL_TEXTURE:
            glMatrixMode (GL_PROJECTION);
            glLoadIdentity ();
            gluPerspective(90, (float)w_win / (float)h_win, 1, 1000);
            glMatrixMode(GL_MODELVIEW);
            glLoadIdentity();
            gluLookAt(0.0, 0.0, (float)h_win / 2.0f, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
            break;
    }
}


int main(int argc, char *argv[])
{
    int numFontFiles;

    if((argc >= 2) && !IGNORE_ARGV)
    {
        fontfiles = (char const * const *)argv + 1;
        numFontFiles = argc - 1;
    }
    else
    {
        fontfiles = defaultFonts;
        numFontFiles = NumDefaultFonts;
    }

    if(!fontfiles[0])
    {
        fprintf(stderr, "At least one font file must be specified on the command line\n");
        exit(1);
    }

    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DEPTH | GLUT_RGB | GLUT_DOUBLE | GLUT_MULTISAMPLE);
    glutInitWindowPosition(50, 50);
    glutInitWindowSize(w_win, h_win);
    glutCreateWindow("FTGL TEST");
    glutDisplayFunc(display);
    glutKeyboardFunc(parsekey);
    glutSpecialFunc(parseSpecialKey);
    glutMouseFunc(mouse);
    glutMotionFunc(motion);
    glutReshapeFunc(myReshape);
    glutIdleFunc(display);

    myinit(numFontFiles);

    glutMainLoop();

    return 0;
}

