/*
 * Grace - GRaphing, Advanced Computation and Exploration of data
 * 
 * Home page: http://plasma-gate.weizmann.ac.il/Grace/
 * 
 * Copyright (c) 1991-1995 Paul J Turner, Portland, OR
 * Copyright (c) 1996-2000 Grace Development Team
 * 
 * Maintained by Evgeny Stambulchik
 * 
 * 
 *                           All Rights Reserved
 * 
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 2 of the License, or
 *    (at your option) any later version.
 * 
 *    This program 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 General Public License for more details.
 * 
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#define _NEED_CUPS_

#include <config.h>

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

#include "defines.h"
#include "globals.h"
#include "graphutils.h"
#include "utils.h"
#include "device.h"
#include "files.h"
#include "plotone.h"

FILE *prstream;
char print_file[GR_MAXPATHLEN] = "";

static unsigned int ndevices = 0;
static int curdevice = 0;
static Device_entry *device_table = NULL;

int use_cups;
int cups_idest;

int is_valid_page_geometry(Page_geometry pg)
{
    if (pg.width  > 0 &&
	pg.height > 0 &&
        pg.dpi > 0.0) {
	return TRUE;
    } else {
        return FALSE;
    }
}

int set_page_geometry(Page_geometry pg)
{
    if (is_valid_page_geometry(pg) == TRUE) {
        device_table[curdevice].pg = pg;
	return RETURN_SUCCESS;
    } else {
        return RETURN_FAILURE;
    }
}

/**
 * return page dimensions in pixels 
 */
Page_geometry get_page_geometry(void)
{
    return (device_table[curdevice].pg);
}

/**
 *  "wpp" and "hpp" are assumed in 72 dpi
 */
int set_page_dimensions(int wpp, int hpp, int rescale)
{
    int i;
    
    if (wpp <= 0 || hpp <= 0) {
        return RETURN_FAILURE;
    } else {
	if (rescale) {
            int wpp_old, hpp_old;
            
            get_device_page_dimensions(curdevice, &wpp_old, &hpp_old);
            if (hpp*wpp_old - wpp*hpp_old != 0) {
                /* aspect ratio changed */
                double ext_x, ext_y;
                double old_aspectr, new_aspectr;
                
                old_aspectr = (double) wpp_old/hpp_old;
                new_aspectr = (double) wpp/hpp;
                if (old_aspectr >= 1.0 && new_aspectr >= 1.0) {
                    ext_x = new_aspectr/old_aspectr;
                    ext_y = 1.0;
                } else if (old_aspectr <= 1.0 && new_aspectr <= 1.0) {
                    ext_x = 1.0;
                    ext_y = old_aspectr/new_aspectr;
                } else if (old_aspectr >= 1.0 && new_aspectr <= 1.0) {
                    ext_x = 1.0/old_aspectr;
                    ext_y = 1.0/new_aspectr;
                } else {
                    ext_x = new_aspectr;
                    ext_y = old_aspectr;
                }

                rescale_viewport(ext_x, ext_y);
            } 
        }
        for (i = 0; i < ndevices; i++) {
            Page_geometry *pg = &device_table[i].pg;
            pg->width  = (unsigned long) rint((double) wpp*(pg->dpi/72));
            pg->height = (unsigned long) rint((double) hpp*(pg->dpi/72));
        }
        return RETURN_SUCCESS;
    }
}

int get_device_page_dimensions(int dindex, int *wpp, int *hpp)
{
    if (dindex >= ndevices || dindex < 0) {
        return RETURN_FAILURE;
    } else {
        Page_geometry *pg = &device_table[dindex].pg;
        *wpp = (int) rint((double) pg->width*72/pg->dpi);
        *hpp = (int) rint((double) pg->height*72/pg->dpi);
        return RETURN_SUCCESS;
    }
}

int register_device(Device_entry device)
{
    int dindex;
    
    ndevices++;
    dindex = ndevices - 1;
    device_table = xrealloc(device_table, ndevices*sizeof(Device_entry));

    device_table[dindex] = device;
    device_table[dindex].name = copy_string(NULL, device.name);
    device_table[dindex].fext = copy_string(NULL, device.fext);
    
    return dindex;
}

int select_device(int dindex)
{
    if (dindex >= ndevices || dindex < 0) {
        return RETURN_FAILURE;
    } else {
        curdevice = dindex;
	return RETURN_SUCCESS;
    }
}

/*
 * set the current print device
 */
int set_printer(int device)
{
    if (device >= ndevices || device < 0 ||
        device_table[device].type == DEVICE_TERM) {
        return RETURN_FAILURE;
    } else {
        hdevice = device;
        if (device_table[device].type != DEVICE_PRINT) {
            set_ptofile(TRUE);
        }
	return RETURN_SUCCESS;
    }
}

int set_printer_by_name(char *dname)
{
    int device;
    
    device = get_device_by_name(dname);
    
    return set_printer(device);
}

int get_device_by_name(char *dname)
{
    int i;
    
    i = 0;
    while (i < ndevices) {
        if (strncmp(device_table[i].name, dname, strlen(dname)) == 0) {
            break;
        } else {
            i++;
        }
    }
    if (i >= ndevices) {
        return -1;
    } else {
	return i;
    }
}

int initgraphics(void)
{
    return ((*device_table[curdevice].init)());
}

Device_entry get_device_props(int device)
{
    return (device_table[device]);
}

Device_entry get_curdevice_props()
{
    return (device_table[curdevice]);
}

char *get_device_name(int device)
{
    return (device_table[device].name);
}

void *get_curdevice_data(void)
{
    return (device_table[curdevice].data);
}

void set_curdevice_data(void *data)
{
    device_table[curdevice].data = data;
}

int set_device_props(int deviceid, Device_entry device)
{
    if (deviceid >= ndevices || deviceid < 0 ||
        is_valid_page_geometry(device.pg) != TRUE) {
        return RETURN_FAILURE;
    }
    
    device_table[deviceid].type = device.type;
/*
 *     device_table[deviceid].init = device.init;
 *     device_table[deviceid].parser = device.parser;
 *     device_table[deviceid].setup = device.setup;
 */
    device_table[deviceid].devfonts = device.devfonts;
    device_table[deviceid].fontaa = device.fontaa;
    device_table[deviceid].pg = device.pg;
    device_table[deviceid].data = device.data;

    return RETURN_SUCCESS;
}

void set_curdevice_props(Device_entry device)
{
    set_device_props(curdevice, device);
}

int parse_device_options(int dindex, char *options)
{
    char *p, *oldp, opstring[64];
    int n;
        
    if (dindex >= ndevices || dindex < 0 || 
            device_table[dindex].parser == NULL) {
        return RETURN_FAILURE;
    } else {
        oldp = options;
        while ((p = strchr(oldp, ',')) != NULL) {
	    n = MIN2((p - oldp), 64 - 1);
            strncpy(opstring, oldp, n);
            opstring[n] = '\0';
            if (device_table[dindex].parser(opstring) != RETURN_SUCCESS) {
                return RETURN_FAILURE;
            }
            oldp = p + 1;
        }
        return device_table[dindex].parser(oldp);
    }
}

int number_of_devices(void)
{
    return (ndevices);
}

void get_page_viewport(double *vx, double *vy)
{
    *vx = device_table[curdevice].pg.width/device_table[curdevice].pg.dpi;
    *vy = device_table[curdevice].pg.height/device_table[curdevice].pg.dpi;
    if (*vx < *vy) {
        *vy /= *vx;
        *vx = 1.0;
    } else {
        *vx /= *vy;
        *vy = 1.0;
    }
}

int terminal_device(void)
{
    if (device_table[curdevice].type == DEVICE_TERM) {
        return TRUE;
    } else {
        return FALSE;
    }
}


/*
 * flag to indicate destination of hardcopy output,
 * ptofile = 0 means print to printer, otherwise print to file
 */

static int ptofile = FALSE;
                           
void set_ptofile(int flag)
{
    ptofile = flag;
}

int get_ptofile(void)
{
    return ptofile;
}

/****************** EXTENDED PAGE FORMATS CHOICE ******************/

static char *nomAB[NB_OF_PAGE_STD_FMT] = {"Custom" ,"Letter"
  ,"A0" ,"A1" ,"A2" ,"A3" ,"A4" ,"A5" ,"A6" ,"A7" ,"A8" ,"A9"
  ,"B0" ,"B1" ,"B2" ,"B3" ,"B4" ,"B5" ,"B6" ,"B7" ,"B8" ,"B9"};
/* Standard dimensions in mm   0    1     2    3    4    5    6    7    8   9  10 */
static int PageStdFmt_A[] = {1189 ,841  ,594 ,420 ,297 ,210 ,148 ,105 ,74 ,52 ,37};
static int PageStdFmt_B[] = {1414 ,1000 ,707 ,500 ,353 ,250 ,176 ,125 ,88 ,62 ,44};

/**
 *  set_page_dimensions_std i.e. "Letter", "A0" to "A9" or "B0" to "B9"
 *  pagefmt     : "Letter", "A4", "B5",... 
 *  orientation : PAGE_ORIENT_PORTRAIT or PAGE_ORIENT_LANDSCAPE
 * To be used in command interpreter
 */
int set_page_dimensions_std (char *pagefmt ,int orientation ,int rescale)
{
  int *std_fmt ,dim ,ix_mm ,iy_mm;
  double xpp ,ypp;
  dim = atoi (&pagefmt[1]);
  if (pagefmt[0] == 'A') {
    std_fmt = PageStdFmt_A;
  } else if (pagefmt[0] == 'B') {
    std_fmt = PageStdFmt_B;
  } else if (pagefmt[0] == 'L') {  /* assume L==Letter */
    if (orientation == PAGE_ORIENT_PORTRAIT) {
      return set_page_dimensions (792 ,612 ,rescale);
    } else {
      return set_page_dimensions (612 ,792 ,rescale);
    }    
  } else {
    return RETURN_FAILURE;
  }
  if (dim < 0 || dim > 9) return RETURN_FAILURE;
  if (orientation == PAGE_ORIENT_PORTRAIT) {
    ix_mm = std_fmt[dim+1];
    iy_mm = std_fmt[dim];
  } else {
    ix_mm = std_fmt[dim];
    iy_mm = std_fmt[dim+1];
  }
  xpp = (double)(ix_mm * 72) / 25.4;
  ypp = (double)(iy_mm * 72) / 25.40;
  return set_page_dimensions ((int)xpp ,(int)ypp ,rescale);
}

/**
 * Given "index" and "orientation",  compute "wpp72" and "hpp72" in 72 dpi.
 */
int calc_page_dimensions_std (int index ,int orientation ,int *wpp72 ,int *hpp72)
{
  int  *l ,i ,ix_mm ,iy_mm;
  if (index <= PAGE_FORMAT_CUSTOM || index > NB_OF_PAGE_STD_FMT) return RETURN_FAILURE;
  /* set to portrait by default and swap after if needed */
  if (index == PAGE_FORMAT_USLETTER) { 
    *wpp72 = 612;
    *hpp72 = 792;
  } else  {
    if (index < 12) {
      l = PageStdFmt_A;
      i = index - 1;
    } else {
      l = PageStdFmt_B;
      i = index - 11;
    }
    ix_mm = l[i];
    iy_mm = l[i-1];
    *wpp72 = (int) rint((double)ix_mm * 72.0 / 25.4);
    *hpp72 = (int) rint((double)iy_mm * 72.0 / 25.4);
  }
  if (orientation == PAGE_ORIENT_LANDSCAPE) iswap (wpp72 ,hpp72);
  return RETURN_SUCCESS;
}

/**
 * Given "px" and "py" in  "page_units_index" 
 *        compute "wpp" and "hpp"   in "dpi" units.
 */
int page_units_to_pix (int page_units_index ,double dpi ,double px ,double py ,int *wpp ,int *hpp)
{
  switch (page_units_index) {
  case PAGE_UNITS_PIXELS:
    *wpp = (int) px;
    *hpp = (int) py;
    break;
  case PAGE_UNITS_INCHES:
    *wpp = (int) rint (px * dpi);
    *hpp = (int) rint (py * dpi);
    break;
  case PAGE_UNITS_CM:              
    *wpp = (int) rint (px * dpi / CM_PER_INCH);
    *hpp = (int) rint (py * dpi / CM_PER_INCH);
    break;
  default:
    errmsg("Internal error: illegal index in page_units_to_pp");
    return RETURN_FAILURE;
  }
  return RETURN_SUCCESS;
}

/**
 *  Given the index and Page_geometry, compute the size in user units
 */
int pix_to_page_units (int page_units_index ,Page_geometry pg ,double *px ,double *py)
{
  switch (page_units_index) {
  case PAGE_UNITS_PIXELS:
    *px = (double)pg.width;
    *py = (double)pg.height;
    break;
  case PAGE_UNITS_INCHES:
    *px = (double)pg.width  / (double)pg.dpi;
    *py = (double)pg.height / (double)pg.dpi;
    break;
  case PAGE_UNITS_CM:    
    *px = CM_PER_INCH * (double)pg.width  / (double)pg.dpi;
    *py = CM_PER_INCH * (double)pg.height / (double)pg.dpi;
    break;
  default:
    errmsg("Internal error: illegal index in pp_to_page_units");
    return RETURN_FAILURE;
  }
  return RETURN_SUCCESS;
}

/**
 * Detect if "pg" define a standard value (A4, Letter,..;)
 *        with a tolerance of one pixel
 *  returns the index in nomAB
 */
int check_page_dimensions_std (Page_geometry pg)
{
  int  *l ,width_pp, height_pp ,i ,ix_mm ,iy_mm ,j ,k;
  double x_mm ,y_mm;
  width_pp  = (int) rint((double) 72*pg.width/pg.dpi);   /* in 72 dpi */
  height_pp = (int) rint((double) 72*pg.height/pg.dpi);  /* in 72 dpi */
  if ((width_pp == 612 && height_pp == 792) ||
      (height_pp == 612 && width_pp == 792)) {
    return PAGE_FORMAT_USLETTER;
  }
  x_mm = width_pp  * 25.4 / 72.0;  ix_mm = (int)x_mm;
  y_mm = height_pp * 25.4 / 72.0;  iy_mm = (int)y_mm;
  /* To avoid rounding problems, we accept one pixel difference */
  for (j = 0; j < 2; j++) {
    l = (j == 0) ? PageStdFmt_A : PageStdFmt_B;
    k = -1;
    for (i = 0; i < 11; i++) {
      if (abs(ix_mm - l[i]) < 2) { 
	if (i > 0 && abs(iy_mm - l[i-1]) < 2) {          /* portrait */
	  k = i - 1;
	} else if (i < 10 && abs(iy_mm - l[i+1]) < 2) {  /*landscape  */
	  k = i;
	}
	if (k >= 0) return k + 10*j + 2;                 /* assume 2 items before PageStdFmt_A[] */
      }
    }
  }
  return PAGE_FORMAT_CUSTOM;
}

/**
 * Used to build the page Format ComboBox
 *  i refer to nomAB index
 */
char *page_dimensions_get_name (int i)
{
  return nomAB[i];
}

/*
 * If writing to a file, check to see if it exists
 *    transfered from plotone.c
 */
void do_hardcopy (void)
{
    char tbuf[128], *s;
    char fname[GR_MAXPATHLEN];
    view v;
    double vx, vy;
    int truncated_out;

    bg_white();  /* P. Vincent */

    if (get_ptofile()) {
        if (print_file[0] == '\0') {
            Device_entry dev = get_device_props(hdevice);
            sprintf(print_file, "%s.%s", get_docbname(), dev.fext);
        }
        strncpy (fname ,print_file ,GR_MAXPATHLEN-1);
	prstream = grace_openw(fname);
    } else {
        s = get_print_cmd();
        if (s == NULL || s[0] == '\0') {
            errmsg("No print command defined, output aborted");
            return;
        }
        strcpy (fname, "gracegtk_tmpXXXXXX");
	prstream = gg_tmpfile (fname);
    }
    if (prstream == NULL) return;

    select_device(hdevice);

    drawgraph();

    grace_close(prstream);

    v = get_bbox(BBOX_TYPE_GLOB);
    get_page_viewport(&vx, &vy);
    if (v.xv1 < 0.0 || v.xv2 > vx || v.yv1 < 0.0 || v.yv2 > vy) {
        truncated_out = TRUE;
    } else {
        truncated_out = FALSE;
    }

    if (get_ptofile() == FALSE) {
      if (truncated_out == FALSE || yesno ("Printout is truncated. Continue?")) {
	if (use_cups) {
#ifdef HAVE_CUPS
	  device_cups_print (fname);
#endif /* HAVE_CUPS */
	} else {
	  sprintf(tbuf, "%s %s", get_print_cmd(), fname);
	  system_wrap (tbuf);
	}
      }
#ifndef PRINT_CMD_UNLINKS
      remove (fname);
#endif
    } else {
      if (truncated_out == TRUE) {
	errmsg("Output is truncated - tune device dimensions");
      }
    }
    select_device(tdevice);
}


/****************** CUPS support ******************/

#ifdef HAVE_CUPS

static int         ndests;
static PrintDest   *print_dests;
static cups_dest_t *dests;

static void parse_group(PrintDest *pd, ppd_group_t *group)
{
  int           i ,j;           /* Looping vars */
  ppd_option_t  *option;        /* Current option */
  ppd_choice_t  *choice;        /* Current choice */
  ppd_group_t   *subgroup;      /* Current subgroup */
  PrintOptGroup *og;
  PrintOption   *po;

  pd->ogroups = xrealloc(pd->ogroups ,(pd->nogroups + 1)*sizeof(PrintOptGroup));
  if (!pd->ogroups) return;
    
  og = &pd->ogroups[pd->nogroups];
  pd->nogroups++;
    
  og->name = copy_string (NULL ,group->name);
  og->text = copy_string (NULL ,group->text);
    
  og->opts = xcalloc(group->num_options ,sizeof(PrintOption));
  if (!og->opts) return;
  og->nopts = 0;

  for (i = 0 ,option = group->options ,po = og->opts; i < group->num_options; i++ ,option++) {
    po->name 	 = copy_string (NULL ,option->keyword);
    po->text 	 = copy_string (NULL ,option->text);
    po->defaut   = -1;
    po->selected = -1;
    po->choices  = malloc (option->num_choices * sizeof (char*));
    po->nchoices = option->num_choices;
    for (j = 0 ,choice = option->choices; j < option->num_choices; j++ ,choice++) {
      po->choices[j] = copy_string (NULL ,choice->text);
      if (choice->marked) po->defaut = j;
    }
    if (po->defaut != -1) {
      og->nopts++;
      po++;
      pd->nopts++;
    } else {
      xfree(po->name);
      xfree(po->text);
    }
  }

  for (i = 0 ,subgroup = group->subgroups; i < group->num_subgroups; i++ ,subgroup++) {
    parse_group (pd ,subgroup);
  }

}

int device_cups_init (void)
{
  int i ,j;
  ndests = cupsGetDests (&dests);         /* Nb of destinations */
  if (ndests == 0) {
    /* no CUPS printers defined or CUPS not running */
    return RETURN_FAILURE;
  } else {
    print_dests = xcalloc (ndests ,sizeof(PrintDest));
    if (print_dests == NULL) {
      return RETURN_FAILURE;
    }
  }
  for (i = 0; i < ndests; i++) {
    cups_dest_t   *dest = &dests[i];
    PrintDest     *pd   = &print_dests[i];
    char 	  *printer;
    const char    *filename;        	  /* PPD filename */
    ppd_file_t    *ppd;             	  /* PPD data */
    ppd_group_t   *group;           	  /* Current group */
    if ((filename = cupsGetPPD (dests[i].name)) == NULL) continue;
    if ((ppd = ppdOpenFile(filename)) == NULL) {
      remove (filename);
      continue;
    }
    ppdMarkDefaults (ppd);
    cupsMarkOptions (ppd ,dests[i].num_options ,dests[i].options);
    for (j = 0 ,group = ppd->groups; j < ppd->num_groups; j++ ,group++) {
      //      parse_group (&dests[i] ,group);
    }
    printer = copy_string(NULL ,dest->name);
    if (dest->instance) {
      printer = concat_strings(printer ,"/");
      printer = concat_strings(printer ,dest->instance);
    }
        
    pd->name    = copy_string(NULL ,dest->name);
    pd->inst    = copy_string(NULL ,dest->instance);
    pd->printer = printer;        
    if ((filename = cupsGetPPD  (dest->name)) == NULL) continue;
    if ((ppd 	  = ppdOpenFile (filename))   == NULL) {
      remove(filename);
      continue;
    }
    ppdMarkDefaults (ppd);
    cupsMarkOptions (ppd ,dest->num_options ,dest->options);

    for (j = 0 ,group = ppd->groups; j < ppd->num_groups; j++ ,group++) {
      parse_group (pd ,group);
    }

    ppdClose(ppd);
    remove(filename);
  }
  return RETURN_SUCCESS;
}


int device_cups_print (const char *fname)
{
  PrintDest *pd = &print_dests[cups_idest];
  int i ,j;
  int jobid;
  int num_options = 0;
  cups_option_t *options = NULL;
  for (i = 0; i < pd->nogroups; i++) {
    PrintOptGroup *og = &pd->ogroups[i];
    for (j = 0; j < og->nopts; j++) {
      PrintOption *po = &og->opts[j];
      if (po->selected >= 0 && po->selected != po->defaut) {
	num_options = cupsAddOption (po->name ,po->choices[po->selected] ,num_options ,&options);
      }
    }
  }
  jobid = cupsPrintFile (pd->name, fname, "GraceGTK", num_options, options);
  if (jobid == 0) {
    errmsg (ippErrorString (cupsLastError()));
    return RETURN_FAILURE;
  }
  return RETURN_SUCCESS;
}

/************** UTILITIES for CUPS **************/

PrintDest *device_cups_get_printdest (int i)
{
  return &print_dests[i];
}

/**
 * To be used by gg_combo_fun()
 */
int device_cups_ndests (void)
{
  return ndests;
}

char *device_cups_dest_name (int i)
{  
  return dests[i].name;
}

#endif /* HAVE_CUPS */
