// This file is part of BOINC.
// http://boinc.berkeley.edu
// Copyright (C) 2008 University of California
//
// BOINC is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License
// as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
//
// BOINC is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with BOINC.  If not, see <http://www.gnu.org/licenses/>.

// The part of BOINC's graphics utilities that uses GLUT char-drawing

#ifdef _WIN32
#include "boinc_win.h"
#else
#include "config.h"
#endif

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

#ifdef _WIN32
#ifdef __cplusplus
extern "C" {
#include "jpeglib.h"
}
#else
#include "jpeglib.h"
#endif
#endif

#ifndef _WIN32
extern "C"{
#include <jpeglib.h>
}
#endif

#include "boinc_gl.h"
#include "boinc_glut.h"

#include "filesys.h"
#include "str_util.h"
#include "str_replace.h"

#include "gutil.h"

#define STROKE_SCALE    120
    // GLUT stroke characters are about 120 units high

GLfloat text_width(const char* text) {
    GLfloat sum = 0;
    for (const char* p = text; *p; p++) {
        sum += glutStrokeWidth(GLUT_STROKE_ROMAN, *p);
    }
    return sum/STROKE_SCALE;
}

static void draw_text_line_aux(const char *text) {
    for (const char* p = text; *p; p++) {
        glutStrokeCharacter(GLUT_STROKE_ROMAN, *p);
    }
}

static void draw_text_start(GLfloat* pos, GLfloat char_height, GLfloat line_width) {
    glLineWidth(line_width);
    glPushMatrix();
    glTranslatef(pos[0], pos[1], pos[2]);
	glRasterPos3d(pos[0],pos[1],pos[2]);
    float w = char_height/STROKE_SCALE;
    glScalef(w, w, w);
}

static void draw_text_end() {
    glPopMatrix();
}

// draw a line of text in the XY plane at the given starting position,
// character height, and line width.
void draw_text_simple(const char* text, float line_width, float char_height)
{
    glLineWidth(line_width);
    float w = char_height/STROKE_SCALE;
    glScalef(w, w, w);
	draw_text_line_aux(text);
}

void draw_text_line(
    GLfloat* _pos, GLfloat char_height, GLfloat line_width, const char *text,
    int justify
) {
    GLfloat pos[3];
    GLfloat w;

    memcpy(pos, _pos, sizeof(pos));
    switch(justify) {
    case TEXT_LEFT:
        break;
    case TEXT_CENTER:
        w = text_width(text);
        pos[0] -= w/2;
        break;
    case TEXT_RIGHT:
        w = text_width(text);
        pos[0] -= w;
        break;
    }
    draw_text_start(pos, char_height, line_width);
    draw_text_line_aux(text);
    draw_text_end();
}

// draw rotated text
void draw_rotated_text(
	GLfloat* pos, GLfloat height, GLfloat width, GLfloat /*spacing*/, const char *text,
	GLfloat rotation, GLfloat* rotation_vector)
{
	draw_text_start(pos, height, width);
	glRotatef(rotation,rotation_vector[0],rotation_vector[1],rotation_vector[2]);
    draw_text_line_aux(text);
    draw_text_end();
}

// draw multiple lines of text
//
void draw_text(
    GLfloat* _pos, GLfloat char_height, GLfloat line_width,
    GLfloat line_spacing, const char* text
) {
    char* q, *p;
    char buf[4096];
    GLfloat pos[3];
    memcpy(pos, _pos, sizeof(pos));
    strlcpy(buf, text, 4096);

    p = buf;
    while (*p) {
        q = strchr(p, '\n');
        if (q) *q = 0;
        draw_text_start(pos, char_height, line_width);
        draw_text_line_aux(p);
        draw_text_end();
        pos[1] -= line_spacing;
        if (!q) break;
        p = q+1;
    }
}

void draw_text_new_3d(
	GLfloat* _pos, GLfloat /*char_height*/, GLfloat /*line_width*/,
    GLfloat line_spacing, const char* text
) {
	char* q, *p;
    char buf[4096];
    GLfloat pos[3];
    memcpy(pos, _pos, sizeof(pos));
    strlcpy(buf, text, 4096);

    p = buf;
	glPushMatrix();
    glTranslatef(pos[0], pos[1], pos[2]);
    while (*p) {
        q = strchr(p, '\n');
        if (q) *q = 0;
		glRasterPos3d(pos[0],pos[1],pos[2]);
		print_text(p);
        pos[1] -= line_spacing;
        if (!q) break;
        p = q+1;
    }
	glPopMatrix();
}

void draw_text_new(
    GLfloat* _pos, GLfloat /*char_height*/, GLfloat /*line_width*/,
    GLfloat line_spacing, const char* text
) {
	char *q, *p;
	char buf[4096];
	GLfloat pos[3];
	memcpy(pos,_pos,sizeof(pos));
    strlcpy(buf, text, 4096);
	p=buf;
	int viewport[4];
	get_viewport(viewport);
	while(*p) {
		q = strchr(p, '\n');
        if (q) *q = 0;
		glRasterPos3d(pos[0],pos[1],pos[2]);
		print_text(p);
        pos[1] -= line_spacing;
        if (!q) break;
        p = q+1;
	}
}

void draw_text_right(
    GLfloat* _pos, GLfloat char_height, GLfloat line_width,
    GLfloat line_spacing, const char* text
) {
	char *q, *p;
	char buf[4096];
	GLfloat pos[3];
	memcpy(pos,_pos,sizeof(pos));
	float orig = pos[0];
    strlcpy(buf, text, 4096);
	p=buf;
	float w;

	while (*p) {
		q = strchr(p, '\n');
        if (q) *q = 0;
        w = text_width(p)/66.5f;
        pos[0] -= w;
	    draw_text_start(pos, char_height, line_width);
	    draw_text_line_aux(p);
	    draw_text_end();
		pos[1] -= line_spacing;
		pos[0]=orig;
        if (!q) break;
        p = q+1;
	}
}


MOVING_TEXT_PANEL::MOVING_TEXT_PANEL() :
	theta(0), dtheta(0), color(0), char_height(0), line_width(0), line_spacing(0), margin(0)
{
	int i;
	for (i=0;i<3;i++) {
		base_pos[i]=size[i]=0;
	}
	for (i=0;i<PANEL_MAX_LINES;i++) {
		memset(&(text[i][0]),0,256);
	}
}

void MOVING_TEXT_PANEL::init(
    float* p, float* s, COLOR& c, double d, double ch, double lw, double ls, double m
) {
    memcpy(pos, p, sizeof(pos));
    memcpy(base_pos, p, sizeof(base_pos));
    memcpy(size, s, sizeof(size));
    color = c;
    theta = 0;
    dtheta = d;
    char_height = ch;
    line_width = lw;
    line_spacing = ls;
    margin = m;
    memset(text, 0, sizeof(text));
}

// draw a rectangle of the given color in the XY plane
// and draw the given test in it
//
void MOVING_TEXT_PANEL::draw() {
    COLOR side_color = color;
    GLfloat pos0[3], pos1[3], pos2[3], pos3[3];
    memcpy(pos0, pos, sizeof(pos0));
    memcpy(pos1, pos, sizeof(pos0));
    pos1[0] += size[0];
    memcpy(pos2, pos1, sizeof(pos0));
    pos2[1] += size[1];
    memcpy(pos3, pos2, sizeof(pos0));
    pos3[0] -= size[0];
    mode_unshaded();
    glColor4fv(&color.r);
    glBegin(GL_QUADS);
    glVertex3fv(pos0);
    glVertex3fv(pos1);
    glVertex3fv(pos2);
    glVertex3fv(pos3);

    // draw flanges
    //
    side_color.r /= 2;
    side_color.g /= 2;
    side_color.b /= 2;
    glColor4fv(&side_color.r);
    GLfloat posa0[3], posa1[3], posa2[3], posa3[3];
    memcpy(posa0, pos0, sizeof(pos0));
    memcpy(posa1, pos1, sizeof(pos0));
    memcpy(posa2, pos2, sizeof(pos0));
    memcpy(posa3, pos3, sizeof(pos0));
    posa0[2] -= .2;
    posa1[2] -= .2;
    posa2[2] -= .2;
    posa3[2] -= .2;
    glVertex3fv(pos0);
    glVertex3fv(pos1);
    glVertex3fv(posa1);
    glVertex3fv(posa0);
    glVertex3fv(pos1);
    glVertex3fv(pos2);
    glVertex3fv(posa2);
    glVertex3fv(posa1);
    glVertex3fv(pos2);
    glVertex3fv(pos3);
    glVertex3fv(posa3);
    glVertex3fv(posa2);
    glVertex3fv(pos3);
    glVertex3fv(pos0);
    glVertex3fv(posa0);
    glVertex3fv(posa3);
    glEnd();

    pos3[0] += margin;
    pos3[1] -= (margin+char_height);
    pos3[2] += 0.01;
    glColor3f(1, 1, 1);
    for (int i=0; i<PANEL_MAX_LINES; i++) {
        if (strlen(text[i])) {
            draw_text(pos3, char_height, line_width, line_spacing, text[i]);
        }
        pos3[1] -= line_spacing;
    }
}

void MOVING_TEXT_PANEL::set_text(int lineno, const char* str) {
	char buf[8192];
	strlcpy(buf, str, sizeof(buf));
	char* p = buf;
    char* q = strchr(p, '\n');
    while (p) {
        if (q) *q = 0;
        strlcpy(text[lineno++], p, 256);
        if (!q) break;
        p = q+1;
        q = strchr(p, '\n');
    }
}

void MOVING_TEXT_PANEL::move(double dt) {
    pos[0] = base_pos[0] + sin(theta);
    pos[1] = base_pos[1];
    pos[2] = base_pos[2] + cos(theta);
    theta += dtheta*dt;
}

void MOVING_TEXT_PANEL::get_pos(int lineno, float* p) {
    memcpy(p, pos, sizeof(pos));
    p[0] += margin;
    p[1] += (size[1] - margin - lineno*line_spacing);
}

static int compare_tp(const void* p1, const void* p2) {
    const MOVING_TEXT_PANEL* tp1=(const MOVING_TEXT_PANEL*)p1, *tp2 = (const MOVING_TEXT_PANEL*)p2;
    if (tp1->pos[2] > tp2->pos[2]) return 1;
    if (tp2->pos[2] > tp1->pos[2]) return -1;
    return 0;
}

void MOVING_TEXT_PANEL::sort(MOVING_TEXT_PANEL* tp, int n) {
    qsort(tp, n, sizeof(MOVING_TEXT_PANEL), compare_tp);
}

