/**************************************************************************
 * Etch A Sketch 1.1.  Computer control of the drawing toy.               *
 * Copyright (C) 2004 Neil Fraser                                         *
 * http://neil.fraser.name/hardware/etch/                                 *
 *                                                                        *
 * 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.                          *
 *                                                                        *
 * 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 (www.gnu.org) for more details.             *
 *                                                                        *
 * Thanks to David Brackeen for mouse code.                               *
 * http://www.brackeen.com/home/vga/mouse.html                            *
 *                                                                        *
 * Thanks to Peter Anderson for graphics code.                            *
 * http://www.phanderson.com/graphics/                                    *
 *                                                                        *
 * Compile with:  tcc etch.c graphics.lib                                 *
 * Executed with: etch [d]  (where d is the delay factor)                 *
 **************************************************************************/

#include <stdio.h>
#include <graphics.h>
#include <conio.h>
#include <dos.h>

#define ETCH_WIDTH        1000      /* width in steps of etch a sketch */
#define ETCH_HEIGHT       700       /* height in steps of etch a sketch */
#define VIEW_WIDTH        390       /* width in pixels of view portal */
#define VIEW_HEIGHT       198       /* height in pixels of view portal */
#define SCREEN_WIDTH      640       /* width in pixels of screen */
#define SCREEN_HEIGHT     200       /* height in pixels of screen */

#define THRESHOLD 1 /* How many mouse ticks needed for one stepper step */
#define LAG 2        /* How many steps needed to compensate for the lag */

#define MOUSE_INT           0x33
#define MOUSE_RESET         0x00
#define MOUSE_GETPRESS      0x05
#define MOUSE_GETRELEASE    0x06
#define MOUSE_GETMOTION     0x0B
#define LEFT_BUTTON         0x00
#define RIGHT_BUTTON        0x01
#define MIDDLE_BUTTON       0x02

#define PORT                0x3BC   /* choose from:  0x3BC | 0x378 | 0x278 */

typedef unsigned char  byte;
typedef unsigned short word;
typedef unsigned long  dword;
typedef short sword;                  /* signed word */


/**************************************************************************
 *  get_mouse_motion                                                      *
 *    Returns the distance the mouse has moved since it was lasted        *
 *    checked.                                                            *
 **************************************************************************/
void get_mouse_motion(sword *dx, sword *dy) {
    union REGS regs;

    regs.x.ax = MOUSE_GETMOTION;
    int86(MOUSE_INT, &regs, &regs);
    *dx=regs.x.cx;
    *dy=regs.x.dx;
}


/**************************************************************************
 *  init_mouse                                                            *
 *    Resets the mouse.  Returns 0 if mouse not found.                    *
 **************************************************************************/
sword init_mouse() {
    sword dx,dy;
    union REGS regs;

    regs.x.ax = MOUSE_RESET;
    int86(MOUSE_INT, &regs, &regs);
    get_mouse_motion(&dx,&dy);
    return regs.x.ax;
}


/**************************************************************************
 *  get_mouse_press                                                       *
 *    Returns 1 if a button has been pressed since it was last checked.   *
 **************************************************************************/
sword get_mouse_press(sword button) {
    union REGS regs;

    regs.x.ax = MOUSE_GETPRESS;
    regs.x.bx = button;
    int86(MOUSE_INT, &regs, &regs);
    return regs.x.bx;
}


/**************************************************************************
 *  get_mouse_release                                                     *
 *    Returns 1 if a button has been released since it was last checked.  *
 **************************************************************************/
sword get_mouse_release(sword button) {
    union REGS regs;

    regs.x.ax = MOUSE_GETRELEASE;
    regs.x.bx = button;
    int86(MOUSE_INT, &regs, &regs);
    return regs.x.bx;
}


/**************************************************************************
 *  clearscreen                                                           *
 *    Clear the screen and redraw the border                              *
 **************************************************************************/
void clearscreen() {
    cleardevice();
    setcolor(WHITE);
    rectangle((SCREEN_WIDTH-VIEW_WIDTH)/2-1, 0, (SCREEN_WIDTH-VIEW_WIDTH)/2+VIEW_WIDTH, VIEW_HEIGHT+1);
}


/**************************************************************************
 *  Main                                                                  *
 **************************************************************************/
int main(int argc, char *argv[]) {
    sword dx = 0, dy = 0;
    sword olddx = 0, olddy = 0;
    int bufferx = 0, buffery = 0;
    word press, release;
    long etchx = ETCH_WIDTH/2, etchy = ETCH_HEIGHT/2;
    int lagx = 0, lagy = 0;

    int graphdriver = DETECT; /* Expected mode will be 640x200 CGA */
    int graphmode;

    int statusbyte;   /* Latest byte received from the parallel port */
    int commandbyte;  /* Byte to be sent out the parallel port */
    int keypress = 0; /* Latest character from keyboard */
    int flipmode = 0; /* Are we tilting down (-1) up (1) or not moving (0) */
    int horizontalquad = 0; /* Current step position for motor (0-3) */
    int verticalquad = 0;   /* Current step position for motor (0-3) */

    int now;
    int lasttilttime = 0;
    int lasthorizontaltime = 0;
    int lastverticaltime = 0;
    int beep = 0;

    int delay = 0;
    int d = 0;
    if (argc == 2) {
        delay = atoi(argv[1]);
        printf("Delay = %d.\n", delay);
    }

    /* Initialize the mouse */
    if (!init_mouse()) {
        printf("Mouse not found.\n");
        exit(1);
    }

    /* Initialize graphics system */
    initgraph(&graphdriver, &graphmode, "");
    clearscreen();

    while (keypress != 27) {                 /* start main loop */
        now = time();

        /* Read the keyboard and mouse buttons */
        if (kbhit() != 0)
            keypress = getch();
        else
            keypress = 0;

        if (get_mouse_press(LEFT_BUTTON) || keypress == '+') {
          flipmode = (flipmode == 0) ? 1 : 0;
          lasttilttime = now;
        }
        if (get_mouse_press(RIGHT_BUTTON) || keypress == '-') {
          flipmode = (flipmode == 0) ? -1 : 0;
          lasttilttime = now;
        }
        if (get_mouse_release(LEFT_BUTTON)) {
          flipmode = 0;
          lasttilttime = now;
        }
        if (get_mouse_release(RIGHT_BUTTON)) {
          flipmode = 0;
          lasttilttime = now;
        }
        if (keypress == ' ') {
          clearscreen();
        }

        commandbyte = 0;
        if (flipmode != 0) {
          if (now > lasttilttime + 5) {
            flipmode = 0; /* Motor stalled? */
            beep = 1;
          }
          statusbyte = inportb (PORT+1);
          if ((flipmode == 1) && !(statusbyte & 16) && (statusbyte & 32)) {
            flipmode = 0; /* Upper limit */
          }
          if ((flipmode == -1) && (statusbyte & 16) && (statusbyte & 32)) {
            flipmode = 0; /* Lower limit */
            clearscreen(); /* Clear CRT */
          }
        }
        if (flipmode == 1) {
            commandbyte += 128;
        } else if (flipmode == -1) {
            commandbyte += 64;
        }


        /* printf("X = %d \t Y = %d\n", mouse.x, mouse.y); */
        get_mouse_motion(&dx,&dy);
        /* Add a tiny bit of momentum */
        dx = (dx + olddx) / 2; dy = (dy + olddy) / 2;
        /* Draw a velocity vector
        setcolor(BLACK);
        moveto(100,100);
        lineto(100+olddx,100+olddy);
        setcolor(WHITE);
        moveto(100,100);
        lineto(100+dx,100+dy);
        */
        olddx = dx; olddy = dy;

        bufferx += dx; buffery += dy;

        if (bufferx>THRESHOLD) {
            horizontalquad++;
            lasthorizontaltime = now;
            if (lagx == LAG) {
              etchx++;
              if (etchx>ETCH_WIDTH-1) etchx=ETCH_WIDTH-1;
              bufferx =- THRESHOLD;
            } else {
              lagx++;
            }
        } else if (bufferx<-THRESHOLD) {
            horizontalquad--;
            lasthorizontaltime = now;
            if (lagx == -LAG) {
              etchx--;
              if (etchx<0) etchx=0;
              bufferx =+ THRESHOLD;
            } else {
              lagx--;
            }
        }
        if (buffery>THRESHOLD) {
            verticalquad++;
            lastverticaltime = now;
            if (lagy == LAG) {
              etchy++;
              if (etchy>ETCH_HEIGHT-1) etchy=ETCH_HEIGHT-1;
              buffery =- THRESHOLD;
            } else {
              lagy++;
            }
        } else if (buffery<-THRESHOLD) {
            verticalquad--;
            lastverticaltime = now;
            if (lagy == -LAG) {
              etchy--;
              if (etchy<0) etchy=0;
              buffery =+ THRESHOLD;
            } else {
              lagy--;
            }
        }

        putpixel((SCREEN_WIDTH-VIEW_WIDTH)/2 + VIEW_WIDTH*etchx/ETCH_WIDTH, 1 + VIEW_HEIGHT*etchy/ETCH_HEIGHT, WHITE);

        if (now > lasthorizontaltime + 1) {
            /* It's been over a second without movement, idle the motor */
            commandbyte += (0+0+4);
        } else {
            switch (horizontalquad) {
                case 4:
                    horizontalquad = 0;
                    /* Fall through */
                case 0:
                    commandbyte += 0+0+0;
                    break;
                case 1:
                    commandbyte += 0+2+0;
                    break;
                case 2:
                    commandbyte += 1+2+0;
                    break;
                case -1:
                    horizontalquad = 3;
                    /* Fall through */
                case 3:
                    commandbyte += 1+0+0;
            }
        }
        if (now > lastverticaltime + 1) {
            /* It's been over a second without movement, idle the motor */
            commandbyte += (0+0+32);
        } else {
            switch (verticalquad) {
                case 4:
                    verticalquad = 0;
                    /* Fall through */
                case 0:
                    commandbyte += 0+0+0;
                    break;
                case 1:
                    commandbyte += 8+0+0;
                    break;
                case 2:
                    commandbyte += 8+16+0;
                    break;
                case -1:
                    verticalquad=3;
                    /* Fall through */
                case 3:
                    commandbyte += 0+16+0;
            }
        }

        outportb (PORT, commandbyte);

        if (beep) {
            sound(750);   /* Stall warning */
            sleep(1);
            nosound();
            beep = 0;
        }

        for (d=0; d<delay; d++) {
            /* Busy loop */
            now = time();
        }

    }                                   /* end while loop */

    cleardevice();
    commandbyte = 0x24; /* Switch off the tilt, idle the stepper motors */
    outportb (PORT, commandbyte);
    return;
}
