/****************************************************************************
 *
 * MODULE:       m.nviz.script  (nee d.nviz)
 * AUTHOR(S):    Bob Covill <bcovill@tekmap.ns.ca>
 * PURPOSE:      interactively create fly-through script for NVIZ
 * Functions:
 *   main --      parse parameters and get key frame coordinates
 *   do_profile-- calculate camera and eye coordinates from
 *                raster map
 *   move --      part of screen coords
 *   cont --      part of screen coords
 *   read_rast -- return camera and eye coordinates
 *
 * COPYRIGHT:    (C) 2000 by the GRASS Development Team
 *
 *               This program is free software under the GNU General Public
 *               License (>=v2). Read the file COPYING that comes with GRASS
 *               for details.
 *
 *****************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <grass/gis.h>
#include <grass/raster.h>
#include <grass/display.h>
#include "local.h"
#include <grass/glocale.h>

int cut_val, frame = 0;
int height_flag = 0;
double OLD_DEPTH;
char img_name[512];
int no_frames;
int cnt = 1;
int key_frames = 0;
int off_screen = 0;
double key_time = 0.;
FILE *fp, *fp2;

int main(int argc, char *argv[])
{
    const char *name;
    char outfile[GNAME_MAX];
    int fd /*, projection */;
    char buf[512], buf1[1024], buf2[1024];

    /* int screen_x, screen_y; */
    int i, k;
    int frame_start = 0;
    double e1, e2, n1, n2;
    RASTER_MAP_TYPE data_type;
    struct Cell_head window;
    struct {
        struct Option *opt1, *route, *name, *output, *dist, *ht, *frames,
            *start;
        struct Flag *f, *c, *k, *o, *e;
    } parm;
    struct GModule *module;

    G_gisinit(argv[0]);

    /* Set description */
    module = G_define_module();
    G_add_keyword(_("miscellaneous"));
    G_add_keyword(_("graphics"));
    G_add_keyword(_("raster"));
    G_add_keyword(_("raster3d"));
    G_add_keyword(_("vector"));
    G_add_keyword(_("visualization"));
    module->description = _("Creates fly-through script to run in NVIZ.");

    parm.opt1 = G_define_standard_option(G_OPT_R_INPUT);

    parm.output = G_define_standard_option(G_OPT_F_OUTPUT);
    parm.output->description = _("Name of output script");

    parm.name = G_define_option();
    parm.name->key = "name";
    parm.name->type = TYPE_STRING;
    parm.name->required = NO;
    parm.name->description = _("Prefix of output images (default = NVIZ)");

    parm.route = G_define_option();
    parm.route->key = "route";
    parm.route->type = TYPE_STRING;
    parm.route->required = NO;
    parm.route->multiple = YES;
    parm.route->key_desc = "east,north";
    parm.route->description = _("Route coordinates (east,north)");

    parm.dist = G_define_option();
    parm.dist->key = "dist";
    parm.dist->type = TYPE_DOUBLE;
    parm.dist->required = YES;
    parm.dist->description = _("Camera layback distance (in map units)");

    parm.ht = G_define_option();
    parm.ht->key = "ht";
    parm.ht->type = TYPE_DOUBLE;
    parm.ht->required = YES;
    parm.ht->description = _("Camera height above terrain");

    parm.frames = G_define_option();
    parm.frames->key = "frames";
    parm.frames->type = TYPE_INTEGER;
    parm.frames->required = YES;
    parm.frames->description = _("Number of frames");

    parm.start = G_define_option();
    parm.start->key = "start";
    parm.start->type = TYPE_INTEGER;
    parm.start->required = NO;
    parm.start->description = _("Start frame number (default=0)");

    parm.f = G_define_flag();
    parm.f->key = 'f';
    parm.f->description = _("Full render -- Save images");

    parm.c = G_define_flag();
    parm.c->key = 'c';
    parm.c->description = _("Fly at constant elevation (ht)");

    parm.k = G_define_flag();
    parm.k->key = 'k';
    parm.k->description =
        _("Include command in the script to output a KeyFrame file");

    parm.o = G_define_flag();
    parm.o->key = 'o';
    parm.o->description = _("Render images off-screen");

    parm.e = G_define_flag();
    parm.e->key = 'e';
    parm.e->description = _("Enable vector and sites drawing");

    if (G_parser(argc, argv))
        exit(EXIT_FAILURE);

    /* check arguments */
    if (!parm.route->answer)
        G_fatal_error(_("Either -i flag and/or route parameter must be used"));

    /* get GRASS parameters */
    G_get_window(&window);
    /* projection = G_projection(); */
    D_do_conversions(&window, 0, 1, 0, 1);

    /* setup screen coords */
    /* screen_x = ((int)D_get_d_west() + (int)D_get_d_east()) / 2;
       screen_y = ((int)D_get_d_north() + (int)D_get_d_south()) / 2; */

    /* get camera parameters */
    DIST = atof(parm.dist->answer);
    HT = atof(parm.ht->answer);
    no_frames = atoi(parm.frames->answer);

    if (parm.start->answer)
        frame_start = atoi(parm.start->answer);

    if (parm.c->answer)
        height_flag = 1;
    if (parm.k->answer)
        key_frames = 1;
    if (parm.o->answer && !parm.f->answer)
        G_fatal_error(_("Off-screen only available with full render mode"));
    if (parm.o->answer)
        off_screen = 1;

    /* Initialize coords */
    e1 = e2 = n1 = n2 = -9999.;

    G_begin_distance_calculations();

    /* Open Input File for reading */
    name = parm.opt1->answer;

    /* Open Raster File */
    fd = Rast_open_old(name, "");

    /* Set Image name */
    if (parm.name->answer)
        sprintf(img_name, "%s", parm.name->answer);
    else
        sprintf(img_name, "NVIZ");

    /* Open ASCII file for output */
    /* append ".nvscr" to filename if it doesn't already have it */
    strncpy(outfile, parm.output->answer, GNAME_MAX - 7);
    outfile[GNAME_MAX - 7] =
        '\0'; /* strncpy() doesn't null terminate the string */
    if (strcmp(&outfile[strlen(outfile) - 6], ".nvscr") != 0)
        strcat(outfile, ".nvscr");

    if (NULL == (fp = fopen(outfile, "w")))
        G_fatal_error(_("Unable to open file <%s>"), outfile);

    /* Get Raster Type */
    data_type = Rast_get_map_type(fd);
    /* Done with file */

    /* Output initial startup stuff */
    sprintf(buf1, "## REGION: n=%f s=%f e=%f w=%f\n## Input=%s Dist=%f Ht=%f\n",
            window.north, window.south, window.east, window.west, outfile, DIST,
            HT);

    sprintf(buf2, "\nset FRAMES %d\n", no_frames);
    strcat(buf1, buf2);
    fprintf(fp, "%s", buf1);

    sprintf(buf1, "SendScriptLine \"Nclear_keys\"");
    sprintf(buf2, "\nSendScriptLine \"Nupdate_frames\"");
    strcat(buf1, buf2);
    fprintf(fp, "%s", buf1);

    sprintf(buf1, "\nSendScriptLine \"Nset_numsteps $FRAMES\"");
    sprintf(buf2, "\nSendScriptLine \"Nupdate_frames\"\n");
    strcat(buf1, buf2);
    fprintf(fp, "%s", buf1);

    /* Use Linear mode for smooth frame transition */
    sprintf(buf1, "\nSendScriptLine \"Nset_interp_mode linear\"");
    sprintf(buf2, "\nSendScriptLine \"Nupdate_frames\"\n\n");
    strcat(buf1, buf2);
    fprintf(fp, "%s", buf1);

    /* enable vector and sites drawing */
    if (parm.e->answer) {
        sprintf(buf1, "\nSendScriptLine \"Nshow_vect on\"");
        sprintf(buf2, "\nSendScriptLine \"Nshow_sites on\"\n\n");
        strcat(buf1, buf2);
        fprintf(fp, "%s", buf1);
    }

    /* Coords from Command Line */
    k = 0;
    for (i = 0; parm.route->answers[i]; i += 2) {
        /* Test for number coordinate pairs */
        k = i;
    }

    if (k < 6) {
        /* Only one coordinate pair supplied */
        G_fatal_error(_("You must provide at least four points %d"), k);
    }
    else {
        for (i = 0; i <= k - 2; i += 2) {
            sscanf(parm.route->answers[i], "%lf", &e1);
            sscanf(parm.route->answers[i + 1], "%lf", &n1);
            sscanf(parm.route->answers[i + 2], "%lf", &e2);
            sscanf(parm.route->answers[i + 3], "%lf", &n2);
            /* Get profile info */
            do_profile(e1, e2, n1, n2, name, fd, data_type);

            /* Get last coord */
            if (i == k - 2)
                do_profile(e2, e2, n2, n2, name, fd, data_type);
        }
    }
    /* done with coordinates */

    /* Output final part of script */
    /* generate key-frame script */
    if (key_frames) {
        strcpy(buf, outfile);
        buf[strlen(outfile) - 6] = '\0'; /* skip extension */
        strcat(buf, ".kanim");
        fprintf(fp, "\n## The following saves the animation to a format\n");
        fprintf(fp, "## suitable for editing with the kanimator panel\n");
        fprintf(fp, "SendScriptLine \"Nprint_keys %s\"\n", buf);
        fprintf(fp, "puts \"Saving Key Frame file %s\"\n", buf);
    }

    /* output off-screen option */
    if (off_screen) {
        fprintf(fp, "\n## Off screen rendering enabled \n");
        fprintf(fp, "## Ensure main window is minimized before running\n");
        fprintf(fp, "SendScriptLine \"Noff_screen 1\"\n");
    }

    fprintf(fp, "\n\nset num %d", frame_start);
    fprintf(fp, "\n\nfor {set frame 1} {$frame <= $FRAMES} {incr frame} {");

    if (parm.f->answer) {
        /* Full render and save */
        fprintf(fp, "\nset name %s", img_name);
        fprintf(fp, "\nset num2 [format \"%%04d\" $num]");
        fprintf(fp, "\nappend name $num2 \".ppm\"");
        fprintf(fp, "\nSendScriptLine \"Ndo_framestep $frame 1\"");
        fprintf(fp, "\nSendScriptLine \"Nwrite_ppm $name \"");
        fprintf(fp, "\nincr num");
    }
    else {
        /* Quick draw wire */
        /* output full variables commented so can be easily changed */
        fprintf(fp, "\nset name %s", img_name);
        fprintf(fp, "\nset num2 [format \"%%04d\" $num]");
        fprintf(fp, "\nappend name $num2 \".ppm\"");
        fprintf(fp,
                "\n## To render in full set to 1 and uncomment Nwrite_ppm \"");
        fprintf(fp, "\nSendScriptLine \"Ndo_framestep $frame 0\"");
        fprintf(fp, "\n#SendScriptLine \"Nwrite_ppm $name \"");
        fprintf(fp, "\nincr num");
    }

    fprintf(fp, "\n}\n");
    if (off_screen)
        fprintf(fp, "SendScriptLine \"Noff_screen 0\"\n");

    fprintf(fp, "SendScriptLine \"set ScriptPlaying 0\"\n");
    fprintf(fp, "puts \"DONE!\"\n");

    Rast_close(fd);
    fclose(fp);

    G_done_msg(_("Created NVIZ script <%s>."), outfile);

    exit(EXIT_SUCCESS);

} /* Done with main */

/* ************************************
 * Calculate camera and eye coordinates
 **************************************/
int do_profile(double e1, double e2, double n1, double n2,
               const char *name UNUSED, int fd, int data_type)
{
    float rows, cols, LEN;
    double Y, X, AZI;

    cols = e1 - e2;
    rows = n1 - n2;

    LEN = G_distance(e1, n1, e2, n2);

    /* Calculate Azimuth of Line */
    if (rows == 0 && cols == 0) {
        /* Special case for no movement */
        /* do nothing */
        return 0;
    }

    /* dist is not initialized ! */
    if (rows >= 0 && cols < 0) {
        /* SE Quad or due east */
        AZI = fabs(atan((rows / cols)));
        Y = (double)DIST * sin(AZI);
        X = (double)DIST * cos(AZI);

        if (e != 0.0 && (e != e1 || n != n1)) {
            dist -= G_distance(e, n, e1, n1);
        }
        read_rast(e2 - X, n2 + Y, LEN, fd, 1, data_type);
        read_rast(e2, n2, LEN, fd, 0, data_type);
    }

    if (rows < 0 && cols <= 0) {
        /* NE Quad  or due north */
        AZI = fabs(atan((cols / rows)));
        X = (double)DIST * sin(AZI);
        Y = (double)DIST * cos(AZI);

        if (e != 0.0 && (e != e1 || n != n1)) {
            dist -= G_distance(e, n, e1, n1);
        }
        read_rast(e2 - X, n2 - Y, LEN, fd, 1, data_type);
        read_rast(e2, n2, LEN, fd, 0, data_type);
    }

    if (rows > 0 && cols >= 0) {
        /* SW Quad or due south */
        AZI = fabs(atan((rows / cols)));
        X = (double)DIST * cos(AZI);
        Y = (double)DIST * sin(AZI);

        if (e != 0.0 && (e != e1 || n != n1)) {
            dist -= G_distance(e, n, e1, n1);
        }
        read_rast(e2 + X, n2 + Y, LEN, fd, 1, data_type);
        read_rast(e2, n2, LEN, fd, 0, data_type);
    }

    if (rows <= 0 && cols > 0) {
        /* NW Quad  or due west */
        AZI = fabs(atan((rows / cols)));
        X = (double)DIST * cos(AZI);
        Y = (double)DIST * sin(AZI);

        if (e != 0.0 && (e != e1 || n != n1)) {
            dist -= G_distance(e, n, e1, n1);
        }
        read_rast(e2 + X, n2 - Y, LEN, fd, 1, data_type);
        read_rast(e2, n2, LEN, fd, 0, data_type);
    }

    /* dist is not used ! */

    return 0;
} /* done with do_profile */

/*****************************
 * read_rast
 * function to get raster value at set location
 * and output nviz script
 *****************************/
int read_rast(double east, double north, double rrdist, int fd, int out_type,
              RASTER_MAP_TYPE data_type)
{
    int row, col, nrows, ncols;
    struct Cell_head window;
    CELL *cell;
    char buf[1024] = "";
    char buf2[1024];
    FCELL *fcell;
    DCELL *dcell;
    double camera_height;

    G_get_window(&window);
    nrows = window.rows;
    ncols = window.cols;

    row = (int)(0.5 + D_u_to_a_row(north));
    col = (int)(0.5 + D_u_to_a_col(east));

    if (row < 0 || row > nrows || col < 0 || col > ncols) {
        G_debug(3, "Fail: row=%d  nrows=%d   col=%d  ncols=%d", row, nrows, col,
                ncols);
        G_warning(
            _("Skipping this point, selected point is outside region. "
              "Perhaps the camera setback distance puts it beyond the edge?"));
        frame++;
        return 1;
    }

    if (data_type == CELL_TYPE) {
        cell = Rast_allocate_c_buf();
        Rast_get_c_row(fd, cell, row);
        if (Rast_is_c_null_value(&cell[col]))
            camera_height = (double)9999.;
        else
            camera_height = (double)cell[col];
        G_free(cell);
    }

    if (data_type == FCELL_TYPE) {
        fcell = Rast_allocate_f_buf();
        Rast_get_f_row(fd, fcell, row);
        if (Rast_is_f_null_value(&fcell[col]))
            camera_height = (double)9999.;
        else
            camera_height = (double)fcell[col];
        G_free(fcell);
    }

    if (data_type == DCELL_TYPE) {
        dcell = Rast_allocate_d_buf();
        Rast_get_d_row(fd, dcell, row);
        if (Rast_is_d_null_value(&dcell[col]))
            camera_height = (double)9999.;
        else
            camera_height = (double)dcell[col];
        G_free(dcell);
    }

    /* Output script commands */

    /*************************/

    /* Set camera Height value */
    if (camera_height == 9999.)
        camera_height = OLD_DEPTH;

    if (height_flag && out_type)
        camera_height = HT;
    else if (!height_flag && out_type)
        camera_height = camera_height + HT;

    if (out_type) {
        /* Set Camera Position */
        sprintf(buf2, "\nSendScriptLine \"Nmove_to_real %f %f %f\"", east,
                north, camera_height);
        key_time += (rrdist + fabs(camera_height - OLD_DEPTH)) / 10000.;
    }
    else {

        /* Set Center of View */
        sprintf(buf2, "\nSendScriptLine \"Nset_focus %f %f %f\"",
                east - window.west - (window.ew_res / 2),
                north - window.south - (window.ns_res / 2), camera_height);

        /* Use frame number for now -- TODO figure even increment
         * based on no. of frames and distance */
        sprintf(buf, "\nSendScriptLine \"Nadd_key %f KF_ALL_MASK 1 0.0\"\n",
                key_time);
        strcat(buf2, buf);
        cnt++;
    }

    /* Out to file */
    fprintf(fp, "%s", buf2);
    OLD_DEPTH = camera_height;

    frame++;
    return 0;
}
