/*
 * DANG_BEGIN_MODULE
 *
 * DANG_END_MODULE
 *
 * DANG_BEGIN_CHANGELOG
 *
 * $Date: $
 * $Source: $
 * $Revision: $
 * $State: $
 *
 * $Log: $
 *
 * DANG_END_CHANGELOG
 */

#include "dosemu.h"
#include "base/memory.h"
#include "base/detach.h"
#include "base/termio.h"
#include "base/misc.h"
#include "env/video.h"
#include "env/vc.h"
#include "console.h"

#include <fcntl.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/kd.h>
#include <sys/vt.h>
#include <sys/mman.h>

static caddr_t console_mem    = NULL;
static Boolean console_enable = TRUE;

#define GRAPH_PROT     (PROT_READ | PROT_WRITE)

static void update_cursor(void)
{
  /* This routine updates the state of the cursor, including its cursor
   * position and visibility.  The linux console recognizes the ANSI code 
   * "\033[y;xH" where y is the cursor row, and x is the cursor position.  
   * For example "\033[15;8H" printed to stdout will move the cursor to 
   * row 15, column 8.   The "\033[?25l" string hides the cursor and the 
   * "\033[?25h" string shows the cursor.
   */

  /* Static integers to preserve state of variables from last call. */
  static int oldx = -1;
  static int oldy = -1;
  static int oldblink = 0;

  int blinkflag = cursor_blink;

  /* The cursor is off-screen, so disable its blinking */
  if ((cursor_col > co) || (cursor_row > li))
    cursor_blink = 0;
  
  if (blinkflag) {
    /* Enable blinking if it has not already */
    if (!oldblink)
      fprintf(stdout, "\033[?25h");
    /* Update cursor position if it has moved since last time in this func */
    if ((cursor_col != oldx) || (cursor_row != oldy)) 
      fprintf(stdout, "\033[%d;%dH", cursor_row+1, cursor_col+1);
  }
  else {
    /* Disable blinking if it hasnt already */ 
    if (oldblink)
      fprintf(stdout, "\033[?25l");
  }
  
  /* Save current state of cursor for next call to this function. */
  oldx = cursor_col;
  oldy = cursor_row;
  oldblink = cursor_blink;
}

/*
 * If we own the console, we map the DOSEMU video space directly to the
 * memory on the video card.
 * If we don't own the console and the video mode doesn't seem to have
 * used more than the size of the DOSEMU video space, we map our store
 * buffer into the DOSEMU video space.
 */
void console_mmap(void)
{
  v_printf("CONSOLE: mmapping to video ram (%p:%ldK)\n", (caddr_t)graph_base,
	   graph_size/1024);
  mmap((caddr_t)graph_base, graph_size, GRAPH_PROT, MAP_SHARED | MAP_FIXED,
       mem_fd, (off_t)graph_base);
  memcpy((caddr_t)graph_base, console_mem, graph_size);
  v_printf("CONSOLE: mapped & memory copied\n");
}

void console_munmap(void)
{
  v_printf("CONSOLE: console_munmap, mapping to memory buffer\n");
  memcpy(console_mem, (caddr_t)graph_base, graph_size);
  mmap((caddr_t)graph_base, graph_size, GRAPH_PROT,
       MAP_SHARED | MAP_FIXED, selfmem_fd, (off_t)console_mem);
  v_printf("CONSOLE: memory copied & unmapped\n");
}

static void console_detach(void)
{
  console_enable = FALSE;
  console_munmap();
}

static void console_attach(void)
{
  console_enable = TRUE;
  console_mmap();
}

#include "base/port.h"
int console_init(void)
{
  vc_handler_t vc_handler;

  v_printf("CONSOLE: console_init called\n");

  priv_on();
  iopl(3);
  v_printf("CONSOLE: read(0x3d8) = 0x%04x\n", port_real_inb(0x3d8));
  v_printf("CONSOLE: read(0x3d8) = 0x%04x\n", port_in(0x3d8));
  v_printf("CONSOLE: read(0x3b8) = 0x%04x\n", port_real_inb(0x3b8));
  v_printf("CONSOLE: read(0x3b8) = 0x%04x\n", port_in(0x3b8));
  iopl(0);
  priv_off();
  port_safe_outb(0x3d8, port_safe_inb(0x3d8) & ~0x20);

  if (config.vga)
    ioctl(kbd_fd, KDSETMODE, KD_GRAPHICS);

  /* Clear the Linux console screen. The console recognizes these codes: 
   * \033[?25h = show cursor.
   * \033[0m = reset color.  
   * \033[H = Move cursor to upper-left corner of screen.  
   * \033[2J = Clear screen.  
   */
  fprintf(stdout, "\033[?25h\033[0m\033[H\033[2J");

  clear_screen(0, 7);

  if ((console_mem = malloc(graph_size)) == NULL) {
    error("Unable to allocate switch-away video buffer\n");
    leavedos(1);
  }
  console_mmap();

  if (!config.vga) {
    vc_handler.detach = console_detach;
    vc_handler.attach = console_attach;
    vc_register_handler(vc_handler);

    fake_video_port_init();

    vc_init();
  }
  return 1;
}

void console_term(void)
{
  v_printf("CONSOLE: console_term called\n");

  if (console_enable) {
    console_munmap();

    if (config.vga) 
      ioctl(kbd_fd, KIOCSOUND, 0);	/* turn off any sound */
    else
      fprintf(stdout,"\033[?25h");        /* Turn on the cursor */

    v_printf("CONSOLE: Release mouse control\n");
    ioctl(kbd_fd, KDSETMODE, KD_TEXT);
  }
}

struct video_system Video_console = {
   1,                    /* is_mapped */
   console_init,         /* console_init */
   console_term,         /* console_terminate */
   NULL,                 /* console_setmode */
   NULL,                 /* update_screen */
   update_cursor
};
