/*
   Terminal hooks for Windows NT(tm) port of GNU Emacs.
   Copyright (C) 1992 Free Software Foundation, Inc.

   This file is part of GNU Emacs.

   GNU Emacs 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 1, or (at your option) any later
   version.

   GNU Emacs 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 GNU Emacs; see the file COPYING.  If not, write to the Free Software
   Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.

   Tim Fleehart (apollo@online.com)		17-Jan-92
   Geoff Voelker (voelker@cs.washington.edu)	9-12-93   (updated to vers. 19)
*/


#include <stdlib.h>
#include <stdio.h>

#include "config.h"

#include <windows.h>

#include "lisp.h"
#include "frame.h"
#include "disptab.h"
#include "termhooks.h"

#include "ntterm_p.h"
#include "ntinevt_p.h"
#include "alloca_p.h"

extern FRAME_PTR updating_frame;

extern int meta_key;

static void MoveCursor(int row, int col);
static void ClearToEnd(void);
static void ClearFrame(void);
static void ClearEndOfLine(int);
static void InsDelLines(int vpos, int n);
static void ChangeLineHighlight(int, int, int);
static void ReassertLineHighlight(int, int);
static void InsertGlyphs(GLYPH *start, int len);
static void WriteGlyphs(GLYPH *string, int len);
static void DeleteGlyphs(int n);
static void RingBell(void);
static void ResetTerminalModes(void);
static void SetTerminalModes(void);
static void SetTerminalWindow(int size);
static void UpdateBegin(FRAME_PTR f);
static void UpdateEnd(FRAME_PTR f);
static void ResetKbd(void);
static void UnsetKbd(void);
static int HLmode(int new_highlight);

/*
   Init hook called in init_keyboard
   */
void (*keyboard_init_hook)(void) = ResetKbd;
    
COORD	Cursor;
HANDLE	PrevScreen, CurScreen;
UCHAR	CAttr, CAttrNormal, CAttrReverse;
HANDLE  Keyboard;

/*
 * CtrlCHandler for Windows NT (tm)
 *
 * Setting this as the ctrl handler prevents emacs from being killed when
 * someone hits ^C in a 'suspended' session (child shell).
 */

BOOL WINAPI
CtrlCHandler(DWORD type)
{
	if (type == CTRL_C_EVENT) {
		return TRUE;
	} else {
		return FALSE;
	}
}

/*
   If we're updating a frame, use it as the current frame
   Otherwise, use the selected frame
   */
#define PICK_FRAME() (updating_frame ? updating_frame : selected_frame)

/*
 * move the cursor to (row, col)
 */
void
MoveCursor(int row, int col)
{
    Cursor.X = col;
    Cursor.Y = row;

    if (updating_frame == NULL)
    {
        SetConsoleCursorPosition(CurScreen, Cursor);
    }
}

/*
 * clear from cursor to end of screen
 */
void
ClearToEnd(void)
{
    FRAME_PTR f = PICK_FRAME();

    ClearEndOfLine(FRAME_WIDTH(f) - 1);

    InsDelLines(Cursor.Y, FRAME_HEIGHT(f) - Cursor.Y - 1);
}

/*
 * clear the frame
 */
void
ClearFrame(void)
{
    SMALL_RECT	Scroll;
    COORD	Dest;
    CHAR_INFO	Fill;
    FRAME_PTR   f = PICK_FRAME();

    HLmode(0);

    Scroll.Top = 0;
    Scroll.Bottom = FRAME_HEIGHT(f) - 1;
    Scroll.Left = 0;
    Scroll.Right = FRAME_WIDTH(f) - 1;

    Dest.Y = FRAME_HEIGHT(f);
    Dest.X = 0;

    Fill.Char.AsciiChar = 0x20;
    Fill.Attributes = CAttr;

    ScrollConsoleScreenBuffer(CurScreen, &Scroll, NULL, Dest, &Fill);

    MoveCursor(0, 0);
}


/*
 * clear from Cursor to end (what's "standout marker"?)
 */
void
ClearEndOfLine(int end)
{
    static  GLYPH   base[256];
    static  BOOL    beenhere = FALSE;

    if (!beenhere)
    {
        int	i;

        for (i = 0; i < 256; i++)
        {
            base[i] = SPACEGLYPH;		/* empty space	*/
        }

        beenhere = TRUE;
    }

    WriteGlyphs(base, end - Cursor.X);	/* fencepost ?	*/
}

/*
 * insert n lines at vpos. if n is negative delete -n lines
 */
void
InsDelLines(int vpos, int n)
{
    int		i, nb, save_highlight;
    SMALL_RECT	Scroll;
    COORD	Dest;
    CHAR_INFO	Fill;
    FRAME_PTR f = PICK_FRAME();

    if (n < 0)
    {
        Scroll.Top = vpos - n;
        Scroll.Bottom = FRAME_HEIGHT(f);
        Dest.Y = vpos;
    }
    else
    {
        Scroll.Top = vpos;
        Scroll.Bottom = FRAME_HEIGHT(f) - n;
        Dest.Y = vpos + n;
    }
    Scroll.Left = 0;
    Scroll.Right = FRAME_WIDTH(f);

    Dest.X = 0;

    save_highlight = HLmode(0);

    Fill.Char.AsciiChar = 0x20;
    Fill.Attributes = CAttr;

    ScrollConsoleScreenBuffer(CurScreen, &Scroll, NULL, Dest, &Fill);

    /*
     * here we have to deal with a win32 console flake: If the scroll
     * region looks like abc and we scroll c to a and fill with d we get
     * cbd... if we scroll block c one line at a time to a, we get cdd...
     * Jove expects cdd consistently... So we have to deal with that
     * here... (this also occurs scrolling the same way in the other
     * direction.
     */

    if (n > 0)
    {
        if (Scroll.Bottom < Dest.Y)
        {
            for (i = Scroll.Bottom; i < Dest.Y; i++)
            {
                MoveCursor(i, 0);
                ClearEndOfLine(FRAME_WIDTH(f));
            }
        }
    }
    else
    {
        nb = Dest.Y + (Scroll.Bottom - Scroll.Top) + 1;

        if (nb < Scroll.Top)
        { 
            for (i = nb; i < Scroll.Top; i++)
            {
                MoveCursor(i, 0);
                ClearEndOfLine(FRAME_WIDTH(f));
            }
        }
    }

    Cursor.X = 0;
    Cursor.Y = vpos;

    HLmode(save_highlight);
}


/*
 * HLmode -- Changes attribute to use when drawing characters to control
 * highlighting
 */
static int
HLmode(int new_highlight)
{
    static int Highlight = 0;
    int old_highlight;

    old_highlight = Highlight;
    Highlight = (new_highlight != 0);	/* map to boolean */
    if (Highlight)
    {
        CAttr = CAttrReverse;
    }
    else
    {
        CAttr = CAttrNormal;
    }
    return old_highlight;
}

/*
 * Call this when about to modify line at position VPOS and change whether it
 * is highlighted.
 */
void
ChangeLineHighlight(int new_highlight, int vpos, int first_unused_hpos)
{
    HLmode(new_highlight);
    MoveCursor(vpos, 0);
    ClearEndOfLine(first_unused_hpos);
}

/*
 * External interface to control of standout mode. Call this when about to
 * modify line at position VPOS and not change whether it is highlighted.
 */
void
ReassertLineHighlight(int highlight, int vpos)
{
    HLmode(highlight);
    vpos;			/* pedantic compiler silencer */
}

#undef	LEFT
#undef	RIGHT
#define	LEFT	1
#define	RIGHT	0

void
ScrollLine(int dist, int direction)
{
    /*
     * The idea here is to implement a horizontal scroll in one line to
     * implement delete and half of insert.
     */
    SMALL_RECT	Scroll;
    COORD	Dest;
    CHAR_INFO	Fill;
    FRAME_PTR f = PICK_FRAME();

    Scroll.Top = Cursor.Y;
    Scroll.Bottom = Cursor.Y;

    if (direction == LEFT)
    {
        Scroll.Left = Cursor.X+dist;
        Scroll.Right = FRAME_WIDTH(f)-1;
    }
    else
    {
        Scroll.Left = Cursor.X;
        Scroll.Right = FRAME_WIDTH(f)-dist-1;
    }

    Dest.X = Cursor.X;
    Dest.Y = Cursor.Y;

    Fill.Char.AsciiChar = 0x20;
    Fill.Attributes = CAttr;
    
    ScrollConsoleScreenBuffer(CurScreen, &Scroll, NULL, Dest, &Fill);
}


/*
 * if start is zero insert blanks instead of a string at start ?
 */
void
InsertGlyphs(register GLYPH *start, register int len)
{
    ScrollLine(len, RIGHT);

    /*
     * move len chars to the right starting at Cursor, fill with blanks
     */
    if (start)
    {
	/*
	 * print the first len characters of start, Cursor.X adjusted
	 * by WriteGlyphs.
	 */
	
	WriteGlyphs(start, len);
    }
    else
    {
        ClearEndOfLine(Cursor.X + len);
    }
}

void
WriteGlyphs(register GLYPH *string, register int len)
{
    register char *ptr;
    register unsigned int glyphLength = GLYPH_TABLE_LENGTH;
    Lisp_Object *glyphBase = GLYPH_TABLE_BASE;
    int i;
    GLYPH glyph;
    FRAME_PTR f = PICK_FRAME();
    WORD *attrs;
    char *chars;

    attrs = alloca(len*sizeof(*attrs));
    chars = alloca(len*sizeof(*chars));
    if (attrs == NULL || chars == NULL)
    {
        printf("alloca failed in WriteGlyphs\n");
        return;
    }

    /*
     * We have to deal with the glyph indirection...go over the glyph
     * buffer and extract the characters.
     */
    ptr = chars;
    while (--len >= 0)
    {
	glyph = *string++;

	if (glyph > glyphLength)
        {
	    *ptr++ = glyph & 0xFF;
	    continue;
	}
	GLYPH_FOLLOW_ALIASES(glyphBase, glyphLength, glyph);
	if (GLYPH_FACE(glyph) != 0)
	    printf("Glyph face is %d\n", GLYPH_FACE(glyph));
	if (GLYPH_SIMPLE_P(glyphBase, glyphLength, glyph))
        {
	    *ptr++ = glyph & 0xFF;
	    continue;
	}
	for (i = 0; i < GLYPH_LENGTH(glyphBase, glyph); i++)
        {
	    *ptr++ = (GLYPH_STRING(glyphBase, glyph))[i];
	}
    }

    /* Number of characters we have in the buffer */
    len = ptr-chars;
    
    /*
     * Fill in the attributes for these characters.
     */
    memset(attrs, CAttr, len*sizeof(*attrs));

    /*
     * Write the attributes.
     */
    if (!WriteConsoleOutputAttribute(CurScreen, attrs, len, Cursor, &i))
    {
	printf("Failed writing console attributes.\n");
	fflush(stdout);
    }
    /*
     * Write the characters.
     */
    if (!WriteConsoleOutputCharacter(CurScreen, chars, len, Cursor, &i))
    {
	printf("Failed writing console characters.\n");
	fflush(stdout);
    }

    Cursor.X += len;
    MoveCursor(Cursor.Y, Cursor.X);
}

void
DeleteGlyphs(int n)
{
    /*
     * delete chars means scroll chars from Cursor.X + n to Cursor.X,
     * anything beyond the edge of the screen should come out empty...
     */
    ScrollLine(n, LEFT);
}

void
RingBell(void)
{
    Beep(666, 100);		/* wha'da-ya-want-fer-nothin'?	*/
}

/* Reset to the original console mode but don't get rid of our console
   For suspending emacs */
void
RestoreConsole(void)
{
    UnsetKbd();
    SetConsoleActiveScreenBuffer(PrevScreen);
}

/* Put our console back up, for ending a suspended session */
void
TakeConsole(void)
{
    ResetKbd();
    SetConsoleActiveScreenBuffer(CurScreen);
}
   
void
ResetTerminalModes(void)
{
    UnsetKbd();

    SetConsoleActiveScreenBuffer(PrevScreen);

    CloseHandle(CurScreen);
    
    CurScreen = NULL;
}

void
SetTerminalModes(void)
{
    CONSOLE_CURSOR_INFO cci;

    if (CurScreen == NULL)
    {
	ResetKbd();

	CurScreen = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE,
                                              0, NULL,
                                              CONSOLE_TEXTMODE_BUFFER,
                                              NULL);

	if (CurScreen == INVALID_HANDLE_VALUE)
        {
            printf("CreateConsoleScreenBuffer failed in ResetTerm\n");
            printf("LastError = 0x%lx\n", GetLastError());

            printf("CreateConsoleScreenBuffer failed in ResetTerm\n");
            printf("LastError = 0x%lx\n", GetLastError());
            
            fflush(stdout);

            exit(0);
	}

	SetConsoleActiveScreenBuffer(CurScreen);

	/* make cursor big and visible */
        cci.dwSize = 100;
        cci.bVisible = TRUE;
        (void) SetConsoleCursorInfo(CurScreen, &cci);
    }
}

/*
 * hmmm... perhaps these let us bracket screen changes so that we can flush
 * clumps rather than one-character-at-a-time...
 *
 * we'll start with not moving the cursor while an update is in progress
 */
void
UpdateBegin(FRAME_PTR f)
{
}

void
UpdateEnd(FRAME_PTR f)
{
    SetConsoleCursorPosition(CurScreen, Cursor);
}

void
SetTerminalWindow(int size)
{
}

void
UnsetKbd(void)
{
    SetConsoleMode(Keyboard, ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT |
                   ENABLE_ECHO_INPUT | ENABLE_MOUSE_INPUT);
}

void
ResetKbd(void)
{
    Keyboard = GetStdHandle(STD_INPUT_HANDLE);

    SetConsoleMode(Keyboard, ENABLE_MOUSE_INPUT | ENABLE_WINDOW_INPUT);
}

void
initialize_win_nt_display(void)
{
    CONSOLE_SCREEN_BUFFER_INFO	Info;

    cursor_to_hook		= MoveCursor;
    raw_cursor_to_hook		= MoveCursor;
    clear_to_end_hook		= ClearToEnd;
    clear_frame_hook		= ClearFrame;
    clear_end_of_line_hook	= ClearEndOfLine;
    ins_del_lines_hook		= InsDelLines;
    change_line_highlight_hook	= ChangeLineHighlight;
    reassert_line_highlight_hook= ReassertLineHighlight;
    insert_glyphs_hook		= InsertGlyphs;
    write_glyphs_hook		= WriteGlyphs;
    delete_glyphs_hook		= DeleteGlyphs;
    ring_bell_hook		= RingBell;
    reset_terminal_modes_hook	= ResetTerminalModes;
    set_terminal_modes_hook	= SetTerminalModes;
    set_terminal_window_hook	= SetTerminalWindow;
    update_begin_hook		= UpdateBegin;
    update_end_hook		= UpdateEnd;

    read_socket_hook = Win32ReadSocket;
    mouse_position_hook = Win32MousePosition;
    
    PrevScreen = GetStdHandle(STD_OUTPUT_HANDLE);

    SetTerminalModes();

    GetConsoleScreenBufferInfo(CurScreen, &Info);

    meta_key = 1;
    CAttr = Info.wAttributes & 0xFF;
    CAttrNormal = CAttr;
    CAttrReverse = ((CAttr & 0xf) << 4) + ((CAttr & 0xf0) >> 4);

    FRAME_HEIGHT(selected_frame) = Info.dwSize.Y;	/* lines per page */
    FRAME_WIDTH(selected_frame) = Info.dwSize.X;  /* characters per line */

    MoveCursor(0, 0);

    ClearFrame();
}

DEFUN_P(Fset_screen_color, (Lisp_Object foreground, Lisp_Object background));
DEFUN ("set-screen-color", Fset_screen_color, Sset_screen_color, 2, 2, 0,
       "Set screen colors.")
    (foreground, background)
    Lisp_Object foreground;
    Lisp_Object background;
{
    CAttrNormal = XFASTINT(foreground) + (XFASTINT(background) << 4);
    CAttrReverse = XFASTINT(background) + (XFASTINT(foreground) << 4);

    Frecenter (Qnil);
    return Qt;
}

DEFUN_P(Fset_cursor_size, (Lisp_Object size));
DEFUN ("set-cursor-size", Fset_cursor_size, Sset_cursor_size, 1, 1, 0,
       "Set cursor size.")
    (size)
    Lisp_Object size;
{
    CONSOLE_CURSOR_INFO cci;
    cci.dwSize = XFASTINT(size);
    cci.bVisible = TRUE;
    (void) SetConsoleCursorInfo(CurScreen, &cci);

    return Qt;
}

_VOID_
syms_of_ntterm ()
{
    defsubr(&Sset_screen_color);
    defsubr(&Sset_cursor_size);
}
