/* $Id: panelib.c,v 1.16 2009/02/28 02:53:42 kiesling Exp $ -*-c-*-*/

/*
  This file is part of Ctalk.
  Copyright © 2005-2009  Robert Kiesling, ctalk@ctalklang.org.
  Permission is granted to copy this software provided that this copyright
  notice is included in all source code modules.

  This library 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 2 of the License, or (at your option) any later version.

  This library 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 General Public License
  along with this program; if not, write to the Free Software Foundation, 
  Inc., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <termios.h>
#include "object.h"
#include "message.h"
#include "ctalk.h"
#include "rtinfo.h"

extern RT_INFO *__call_stack[MAXARGS+1];  /* Declared in rtinfo.c. */
extern int __call_stack_ptr;

#define PANE_RCVR_OBJ (__call_stack[__call_stack_ptr + 1]->rcvr_obj)

/* cell_size should only need to be 1 * sizeof (int) for most 
   visual classes. 
   Note that we buffer an extra row and column in case the pane requires
   a shadow.
*/
int **__ctalkCreateWinBuffer (int x_size, int y_size, int cell_size) {
  static int **p, i, is_text_pane = FALSE;
  OBJECT *rcvr_object;

  if ((rcvr_object = PANE_RCVR_OBJ) == NULL)
    return (int **)NULL;
  if (!strcmp (rcvr_object -> __o_classname, "ANSITerminalPane"))
    is_text_pane = TRUE;

  p = (int **)__xalloc ((x_size + 2) * sizeof (int *));
  for (i = 0; i <= x_size + 1; i++) {
    p[i] = (int *)__xalloc((y_size + 2) * sizeof (int));
    if (is_text_pane) {
      int j, space_int = (int)' ';
      for (j = 0; j <= y_size + 1; j++) {
	p[i][j] = space_int;
      }
    }
  }
  return p;
}

static FILE *__pane_output_stream (OBJECT *pane_object) {
  OBJECT *stream_instance_var,
    *stream_outputstream_instance_var,
    *stream_outputstream_instance_var_value;
  static FILE *f;
  if ((stream_instance_var = 
       __ctalkGetInstanceVariable (pane_object, "paneStream", TRUE)) 
      == NULL) {
    return NULL;
  }
  if ((stream_outputstream_instance_var = 
       __ctalkGetInstanceVariable (stream_instance_var, 
				   "outputHandle", TRUE)) == NULL) {
    return NULL;
  }
  if ((stream_outputstream_instance_var_value = 
       __ctalkGetInstanceVariable (stream_outputstream_instance_var, 
				   "value", TRUE)) == NULL) {
    return NULL;
  }
  sscanf (stream_outputstream_instance_var_value -> __o_value, 
	  FMT_0XHEX(&f));
  return f;
}

static int __pane_shadow (OBJECT *pane_object) {
  OBJECT *shadow_instance_var,
    *shadow_instance_var_value;
  static int shadow;
  if ((shadow_instance_var = 
       __ctalkGetInstanceVariable (pane_object, "shadow", TRUE)) 
      == NULL) {
    return ERROR;
  }
  if ((shadow_instance_var_value = 
       __ctalkGetInstanceVariable (shadow_instance_var, "value", TRUE)) 
      == NULL) {
    return ERROR;
  }
  shadow = atoi (shadow_instance_var_value -> __o_value);
  return shadow;
}

static int __pane_border (OBJECT *pane_object) {
  OBJECT *border_instance_var,
    *border_instance_var_value;
  static int border;

  if ((border_instance_var = 
       __ctalkGetInstanceVariable (pane_object, "border", TRUE)) 
      == NULL) {
    return ERROR;
  }
  if ((border_instance_var_value = 
       __ctalkGetInstanceVariable (border_instance_var, "value", TRUE)) 
      == NULL) {
    return ERROR;
  }
  border = atoi (border_instance_var_value -> __o_value);
  return border;
}

static int __pane_mapped (OBJECT *pane_object) {
  OBJECT *mapped_instance_var,
    *mapped_instance_var_value;
  static int mapped;

  if ((mapped_instance_var = 
       __ctalkGetInstanceVariable (pane_object, "mapped", TRUE)) 
      == NULL) {
    return ERROR;
  }
  if ((mapped_instance_var_value = 
       __ctalkGetInstanceVariable (mapped_instance_var, "value", TRUE)) 
      == NULL) {
    return ERROR;
  }
  mapped = atoi (mapped_instance_var_value -> __o_value);
  return mapped;
}

static int __pane_x_org (OBJECT *pane_object) {
  OBJECT *origin_instance_var,
    *origin_x_instance_var, *origin_x_instance_var_value;
  static int x_org;

  if ((origin_instance_var = 
       __ctalkGetInstanceVariable (pane_object, "origin", TRUE)) 
      == NULL) {
    return ERROR;
  }
  if ((origin_x_instance_var = 
       __ctalkGetInstanceVariable (origin_instance_var, "x", TRUE)) 
      == NULL) {
    return ERROR;
  }
  if ((origin_x_instance_var_value = 
       __ctalkGetInstanceVariable (origin_x_instance_var, "value", TRUE)) 
      == NULL) {
    return ERROR;
  }
  x_org = atoi (origin_x_instance_var_value -> __o_value);
  return x_org;
}

static int __pane_y_org (OBJECT *pane_object) {
  OBJECT *origin_instance_var,
    *origin_y_instance_var, *origin_y_instance_var_value;
  static int y_org;

  if ((origin_instance_var = 
       __ctalkGetInstanceVariable (pane_object, "origin", TRUE)) 
      == NULL) {
    return ERROR;
  }
  if ((origin_y_instance_var = 
       __ctalkGetInstanceVariable (origin_instance_var, "y", TRUE)) 
      == NULL) {
    return ERROR;
  }
  if ((origin_y_instance_var_value = 
       __ctalkGetInstanceVariable (origin_y_instance_var, "value", TRUE)) 
      == NULL) {
    return ERROR;
  }
  y_org = atoi (origin_y_instance_var_value -> __o_value);
  return y_org;
}

static int __pane_x_size (OBJECT *pane_object) {
  OBJECT *size_instance_var,
    *size_x_instance_var, *size_x_instance_var_value;
  static int x_size;

  if ((size_instance_var = 
       __ctalkGetInstanceVariable (pane_object, "size", TRUE)) 
      == NULL) {
    return ERROR;
  }
  if ((size_x_instance_var = 
       __ctalkGetInstanceVariable (size_instance_var, "x", TRUE)) 
      == NULL) {
    return ERROR;
  }
  if ((size_x_instance_var_value = 
       __ctalkGetInstanceVariable (size_x_instance_var, "value", TRUE)) 
      == NULL) {
    return ERROR;
  }
  x_size = atoi (size_x_instance_var_value -> __o_value);
  return x_size;
}

static int __pane_y_size (OBJECT *pane_object) {
  OBJECT *size_instance_var,
    *size_y_instance_var, *size_y_instance_var_value;
  static int y_size;

  if ((size_instance_var = 
       __ctalkGetInstanceVariable (pane_object, "size", TRUE)) 
      == NULL) {
    return ERROR;
  }
  if ((size_y_instance_var = 
       __ctalkGetInstanceVariable (size_instance_var, "y", TRUE)) 
      == NULL) {
    return ERROR;
  }
  if ((size_y_instance_var_value = 
       __ctalkGetInstanceVariable (size_y_instance_var, "value", TRUE)) 
      == NULL) {
    return ERROR;
  }
  y_size = atoi(size_y_instance_var_value -> __o_value);
  return y_size;
}

static int **__pane_buf (OBJECT *pane_object) {
  OBJECT *pane_instance_var,
    *pane_instance_var_value;
  static int **buf;

  if ((pane_instance_var = 
       __ctalkGetInstanceVariable (pane_object, "paneBuffer", TRUE)) 
      == NULL) {
    return NULL;
  }
  if ((pane_instance_var_value = 
       __ctalkGetInstanceVariable (pane_instance_var, "value", TRUE)) 
      == NULL) {
    return NULL;
  }
  /* FMT_0XHEX but buf needs to be cast to int **. */
  sscanf (pane_instance_var_value -> __o_value, "0x%p", (int **)&buf);
  return buf;
}

static int graphics_bits;

static int __pane_graphics_bits (OBJECT *pane_object) {
  OBJECT *pane_attrs_var, *pane_attrs_value_var;
  if ((pane_attrs_var = 
       __ctalkGetInstanceVariable (pane_object, "graphicsBits", TRUE)) 
      == NULL) {
    return 0;
  }
  if ((pane_attrs_value_var = 
       __ctalkGetInstanceVariable (pane_attrs_var, "value", TRUE)) 
      == NULL) {
    return 0;
  }
  graphics_bits = atoi (pane_attrs_value_var -> __o_value);
  return graphics_bits;
}

#define BOLD      (1 << 0)
#define REVERSE   (1 << 1)
#define UNDERLINE (1 << 2)
#define BLINK     (1 << 3)

static char *__ansi_graphics_attr_str (int bits) {

  static char attr_str[MAXMSG];

  *attr_str = '\0';

  if (bits & BOLD)
    strcat (attr_str, ((strlen (attr_str) == 0) ? "1" : ";1"));
  if (bits & REVERSE)
    strcat (attr_str, ((strlen (attr_str) == 0) ? "7" : ";7"));
  if (bits & UNDERLINE)
    strcat (attr_str, ((strlen (attr_str) == 0) ? "4" : ";4"));
  if (bits & BLINK)
    strcat (attr_str, ((strlen (attr_str) == 0) ? "5" : ";5"));

  if (*attr_str == '\0')
    strcat (attr_str, "0");
  strcat (attr_str, "m");
  return attr_str;
}

/* ANSI/VT100/Xterm escape sequences. */
#define VT_REV    "\033[7m"     /* Reverse video.                    */
#define VT_NORM   "\033[0m"     /* Normal video.                     */
#define VT_CPOS   "\033[%d;%df" /* Set cursor position: row, column. */
#define VT_CLS    "\033[2J"     /* Clear the screen.                 */
#define VT_RESET  "\033[!p"     /* Soft reset.                       */
#define VT_SGR    "\033[%cm"    /* Set graphics.                     */
#define VT_SCS_GRAPHICS    "\033(0"    /* Set graphics characters.   */
#define VT_SCS_ASCII       "\033(B"    /* Set ASCII characters.      */

static void __ansi_graphics_charset (FILE *stream) {
  fputs (VT_SCS_GRAPHICS, stream);
  fprintf (stream, "\033[%s", __ansi_graphics_attr_str (graphics_bits));
}

static void __ansi_ascii_charset (FILE *stream) {
  fputs (VT_SCS_ASCII, stream);
  fprintf (stream, "\033[%s", __ansi_graphics_attr_str (graphics_bits));
}

int __ctalkANSITerminalPaneRefresh (void) {
  int **buf, x_org, y_org, x_size, y_size, buf_x, buf_y,
    shadow, border;
  struct termios old_termios_s, new_termios_s;
  char rowbuf[MAXMSG];
  FILE *stream;

  buf = __pane_buf (PANE_RCVR_OBJ);
  x_org = __pane_x_org (PANE_RCVR_OBJ);
  y_org = __pane_y_org (PANE_RCVR_OBJ);
  x_size = __pane_x_size (PANE_RCVR_OBJ);
  y_size = __pane_y_size (PANE_RCVR_OBJ);
  stream = __pane_output_stream (PANE_RCVR_OBJ);
  shadow = __pane_shadow (PANE_RCVR_OBJ);
  border = __pane_border (PANE_RCVR_OBJ);

  tcgetattr (fileno(stream), &old_termios_s);
  memcpy ((void *)&new_termios_s, (void *)&old_termios_s, 
	  sizeof (struct termios));
#if defined(__sparc__) && defined(__svr4__)
  new_termios_s.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
			     | INLCR | IGNCR | ICRNL | IXON);
  new_termios_s.c_oflag &= ~OPOST;
  new_termios_s.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
  new_termios_s.c_cflag &= ~(CSIZE | PARENB);
  new_termios_s.c_cflag |= CS8;
  new_termios_s.c_cc[VMIN] = 0;
  new_termios_s.c_cc[VTIME] = 0;
#else /* Linux */
  cfmakeraw (&new_termios_s);
#endif
  tcsetattr (fileno(stream), TCSADRAIN, &new_termios_s);

  __pane_graphics_bits (PANE_RCVR_OBJ);

  fprintf (stream, VT_CPOS, x_org, y_org);
  fprintf (stream, "\033[%s",  __ansi_graphics_attr_str (graphics_bits));
  fflush (stream);
  for (buf_y = 0; buf_y < y_size; buf_y++) {
    if (border) {
      if (buf_y == 0) {
	fprintf (stream, VT_CPOS, y_org, x_org);
	__ansi_graphics_charset (stream);
	fputc ('l', stream);
	for (buf_x = 0; buf_x < (x_size - 2); buf_x++)
	  fputc ('q', stream);
	fputc ('k', stream);
	__ansi_ascii_charset (stream);
	continue;
      }
      if (buf_y == (y_size - 1)) {
	fprintf (stream, VT_CPOS, y_org + buf_y, x_org);
	__ansi_graphics_charset (stream);
	fputc ('m', stream);
	for (buf_x = 0; buf_x < (x_size - 2); buf_x++)
	  fputc ('q', stream);
	fputc ('j', stream);
	__ansi_ascii_charset (stream);
	if (shadow) {
	  fprintf (stream, "\033[%s", 
		   __ansi_graphics_attr_str (graphics_bits));
	  fputs (VT_REV, stream);
	  fputs (" ", stream);
	  fprintf (stream, "\033[%s", 
		   __ansi_graphics_attr_str (graphics_bits));
	  fputs (VT_NORM, stream);
	}
	continue;
      }
      fprintf (stream, VT_CPOS, y_org + buf_y, x_org);
      __ansi_graphics_charset (stream);
      fputc ('x', stream);
      __ansi_ascii_charset (stream);
      for (buf_x = 0; buf_x < (x_size - 2); buf_x++)
	rowbuf[buf_x] = (char)buf[buf_x+1][buf_y];
      rowbuf[x_size-2] = '\0';
      fprintf (stream, VT_CPOS, y_org + buf_y, x_org + 1);
      fprintf (stream, "\033[%s", __ansi_graphics_attr_str (graphics_bits));
      fputs (rowbuf, stream);
      __ansi_graphics_charset (stream);
      fputc ('x', stream);
      __ansi_ascii_charset (stream);
    } else {
      for (buf_x = 0; buf_x < x_size; buf_x++)
	rowbuf[buf_x] = (char)buf[buf_x][buf_y];
      rowbuf[x_size] = '\0';
      fprintf (stream, VT_CPOS, y_org + buf_y, x_org);
      fprintf (stream, "\033[%s", __ansi_graphics_attr_str (graphics_bits));
      fputs (rowbuf, stream);
    }
    if (shadow) {
      if (buf_y > 0) {
	fprintf (stream, "\033[%s", __ansi_graphics_attr_str (graphics_bits));
	fputs (VT_REV, stream);
	fputs (" ", stream);
	fprintf (stream, "\033[%s", __ansi_graphics_attr_str (graphics_bits));
	fputs (VT_NORM, stream);
      }
    }
  }
  if (shadow) {
    int i;
    fprintf (stream, "\033[%s",  __ansi_graphics_attr_str (graphics_bits));
    fputs (VT_REV, stream);
    fprintf (stream, VT_CPOS, y_org + y_size, x_org + 1);
    for (i = 0; i < x_size; i++)
      fputs (" ", stream);
    fprintf (stream, "\033[%s", __ansi_graphics_attr_str (graphics_bits));
    fputs (VT_NORM, stream);
  }
  fflush (stream);
  tcsetattr (fileno(stream), TCSADRAIN, &old_termios_s);
  return SUCCESS;
}

int __ctalkANSITerminalPanePutChar (int x, int y, char c) {
  int **buf;
  buf = __pane_buf (PANE_RCVR_OBJ);
  /* Translate from pane coords to buffer coords. */
  buf[x-1][y-1] = (int)c;
  return SUCCESS;
}

int __ctalkANSITerminalPaneMapWindow (OBJECT *__child) {
  int x_org, y_org, x_size, y_size, p_x_org, p_y_org,
    p_x_clip, p_y_clip, x_limit, y_limit, shadow, 
    **childbuf, **parentbuf, i, j;
  if (!__pane_mapped (__child))
    return SUCCESS;
  parentbuf = __pane_buf(PANE_RCVR_OBJ);
  childbuf = __pane_buf(__child);
  x_org = __pane_x_org (__child);
  y_org = __pane_y_org (__child);
  x_size = __pane_x_size (__child);
  y_size = __pane_y_size (__child);
  shadow = __pane_shadow (__child);
  p_x_org = __pane_x_org(PANE_RCVR_OBJ);
  p_y_org = __pane_y_org(PANE_RCVR_OBJ);
  p_x_clip = __pane_x_size(PANE_RCVR_OBJ);
  p_y_clip = __pane_y_size(PANE_RCVR_OBJ);

  x_limit = x_org + x_size + (shadow ? 1 : 0);
  x_limit = ((x_limit > p_x_clip) ? p_x_clip : x_limit);
  x_limit -= x_org;
  y_limit = y_org + y_size + (shadow ? 1 : 0);
  y_limit = ((y_limit > p_y_clip) ? p_y_clip : y_limit);
  y_limit -= y_org ;

  for (i = 0; i < y_limit; i++) {
    for (j = 0; j < x_limit; j++) {
      childbuf[j][i] = parentbuf[j+x_org][i+y_org];
    }
  }
  return SUCCESS;
}

int __ctalkANSITerminalPaneUnMapWindow (OBJECT *__child) {
  int x_org, y_org, x_size, y_size, p_x_org, p_y_org,
    p_x_clip, p_y_clip, x_limit, y_limit, shadow, 
    **childbuf, **parentbuf, i, j;
  if (!__pane_mapped (__child))
    return SUCCESS;
  parentbuf = __pane_buf(PANE_RCVR_OBJ);
  childbuf = __pane_buf(__child);
  x_org = __pane_x_org (__child);
  y_org = __pane_y_org (__child);
  x_size = __pane_x_size (__child);
  y_size = __pane_y_size (__child);
  shadow = __pane_shadow (__child);
  p_x_org = __pane_x_org(PANE_RCVR_OBJ);
  p_y_org = __pane_y_org(PANE_RCVR_OBJ);
  p_x_clip = __pane_x_size(PANE_RCVR_OBJ);
  p_y_clip = __pane_y_size(PANE_RCVR_OBJ);

  x_limit = x_org + x_size + (shadow ? 1 : 0);
  x_limit = ((x_limit > p_x_clip) ? p_x_clip : x_limit);
  x_limit -= x_org;
  y_limit = y_org + y_size + (shadow ? 1 : 0);
  y_limit = ((y_limit > p_y_clip) ? p_y_clip : y_limit);
  y_limit -= y_org ;

  for (i = 0; i < y_limit; i++) {
    for (j = 0; j < x_limit; j++) {
      parentbuf[j+x_org][i+y_org] = childbuf[j][i];
    }
  }
  return SUCCESS;
}
