/*
 * Copyright (C) 2003  Emmanuel VARAGNAT <linux@guzu.net>
 *
 * 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.
 *
 */
 
 /*

Compilation:
 gcc -march=i686 -fomit-frame-pointer -Wall -O2 dga_fun.c -o dga_fun -I X11 -L /usr/X11/lib -lX11 -lXext -lXxf86dga -lm

Usage:
 dga_fun [0-6]

dga_fun must be run as root.

*/

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xos.h>
#include <X11/extensions/xf86dga.h>
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <math.h>

static Display *dpy;
static int screen;
static int cpt = 0;
unsigned long *screen_orig = NULL, *screen_buf = NULL;

static int width, height;
static int pixel_per_line;
static int bank_size;
static int ram;
static char *address;
static long *bpp32;

static unsigned int screen_size;

static int null_handler(Display *dpy) {
  return True;
}

static void ByeBye(void) {
  XF86DGADirectVideo(dpy, screen, 0);  

  XSetIOErrorHandler(null_handler);
  XCloseDisplay(dpy);

  if(screen_orig != NULL)
    free(screen_orig);

  if(screen_orig != NULL)
    free(screen_buf);
}

static void signal_handler(int signum) {
  ByeBye();
  exit(signum);
}

static int is_DGA_available() {
  int event_base, error_base;     /* not used here */
  int major, minor;
  
  /* check, if DGA is available on this machine */
  if(! XF86DGAQueryExtension(dpy, &event_base, &error_base)) {
    printf("DGA extension not found\n");
    return 0;
  }
  
  /* DGA was found */
  if(! XF86DGAQueryVersion(dpy, &major, &minor)) 
    printf("DGA available, but cannot determine version\n\r");
  else
    printf("DGA available, version %d.%d\n\r", major, minor);
  
  return 1; 
}


/********************************************************************************
 ********************************************************************************
 ********************************************************************************
 ********************************************************************************
 ********************************************************************************/

static void CopyToBuff(unsigned long offset,
		       long i, long j,
		       int delta,
		       unsigned long mask,
		       unsigned long inv_mask)
{
  unsigned long x, y;
  unsigned long pixel_orig, pos;

  if(delta < 0 && ( i + delta < 0 || j + delta < 0 ))
      return;
  else  // delta positif
    if( (width - delta) <= i || (height - delta) <= j )
      return;
  
  x = i + delta;
  y = j + delta;

  pixel_orig = screen_orig[offset + i] & mask;
  pos = y * pixel_per_line + x;
  screen_buf[pos] = ( screen_buf[pos] & inv_mask ) | pixel_orig;
}


#define OFFSET_MAX 100
  
static void color_offset() {
  register long offset, i, j;
  static unsigned long pixel_orig, pos;
  static int delta_red, delta_green, delta_blue;
  
  memset(screen_buf, 0, screen_size);
  
  delta_red = (random() % OFFSET_MAX) - OFFSET_MAX/2;
  delta_green = 0;
  delta_blue = (random() % OFFSET_MAX) - OFFSET_MAX/2;

  for(j = 0; j < height; j++) {
    offset = j * pixel_per_line;

    for (i = 0; i < width; i++) {
      /* red */
      CopyToBuff(offset, i, j, delta_red, 0xFF0000L, 0x00FFFFL);

      /* green */
      pixel_orig = screen_orig[offset + i] & 0x00FF00L;
      pos = j * pixel_per_line + i;
      screen_buf[pos] = ( screen_buf[pos] & ( ~ 0xFF00FFL ) ) | pixel_orig;

      /* blue */
      CopyToBuff(offset, i, j, delta_blue, 0x0000FFL, 0xFFFF00L);
    }
  }
  
  memcpy(bpp32, screen_buf, screen_size);
}


/********************************************************************************
 ********************************************************************************
 ********************************************************************************
 ********************************************************************************
 ********************************************************************************/

#define LARGEUR_BANDE 20.0
#define VITESSE 0.2

static int sin_table_size;
static unsigned int sin_table[1024];

static void init_sin_table() {
  double x, max;
  
  sin_table_size = 0;
  max = 2.0 * 3.14 * LARGEUR_BANDE;
  for(x = 0; x < max && sin_table_size < 1024; x += VITESSE) {
    sin_table[sin_table_size++] = (unsigned char) (60.0 * sin(x / LARGEUR_BANDE) + 195.0);
  }
}

static void balayage() {
  static long offset, i, j, k;
  static unsigned int brightness;
  static unsigned long val_red, val_green, val_blue;

  for(j = 0; j < height; j++) {
    offset = j * pixel_per_line;

    brightness = sin_table[(j+k) % sin_table_size];

    for (i = 0; i < width; i++) {
      /* rouge */
      val_red = (screen_orig[offset+i] & 0xFF0000L) >> 16;
      val_red = (val_red * brightness) >> 8;
      //      val_red |= ~ brightness;
      //      val_red = ((unsigned long) ((double)val_red / 256.0 * (double)brightness));

      /* vert */
      val_green = (screen_orig[offset+i] & 0x00FF00L) >> 8;
      val_green = (val_green * brightness) >> 8;

      /* bleu */
      val_blue = (screen_orig[offset+i] & 0x0000FFL);
      val_blue = (val_blue * brightness) >> 8;

      bpp32[offset+i] = val_red << 16 | val_green << 8 | val_blue;
    }
  }
  k += 20;
}

/********************************************************************************
 ********************************************************************************
 ********************************************************************************
 ********************************************************************************
 ********************************************************************************/

#define GAMMA 0.1

static int max_coord;
static int coord_x[1024];
static int coord_y[1024];

static void init_coord_table(float rayon, float p) {
  double x, max;
  
  max_coord = 0;
  max = 6.28;
  for(x = 0; x < max && max_coord < 1024; x += GAMMA) {
    coord_x[max_coord]   = (int) (rayon * cos(x/p));
    coord_y[max_coord++] = (int) (rayon * sin(x/p));
  }
}


inline void CopyToBuff2(long i, long j,
			unsigned long offset,
			int delta_x, int delta_y,
			unsigned long mask, unsigned int inv_mask)
{
  unsigned long x, y;
  unsigned long pos;

  if(delta_x < 0) {
    if( (x = i + delta_x) < 0)
      return;
  }
  else  // delta positif
    if( (width - delta_x) <= i)
      return;
  
  if(delta_y < 0) {
    if(j + delta_y < 0)
      return;
  }
  else  // delta positif
    if( (height - delta_y) <= j)
      return;
  
  x = i + delta_x;
  y = j + delta_y;

  //  pos = ((y << 10) + (y << 8)) + x;
  pos = (y * pixel_per_line) + x;
  screen_buf[pos] = ( screen_buf[pos] & inv_mask ) | (screen_orig[offset + i] & mask);
}

static void circle(unsigned int *red, unsigned int *green, unsigned int *blue) {
  register long offset, i, j;
  
  memset(screen_buf, 0, screen_size);
  
  for(j = 0; j < height; j++) {
    offset = j * pixel_per_line;
    for (i = 0; i < width; i++) {
      CopyToBuff2(i, j, offset, coord_x[*red],   coord_y[*red],   0xFF0000L, 0x00FFFFL);
      CopyToBuff2(i, j, offset, coord_x[*green], coord_y[*green], 0x00FF00L, 0xFF00FFL);
      CopyToBuff2(i, j, offset, coord_x[*blue],  coord_y[*blue],  0x0000FFL, 0xFFFF00L);
    }
  }
  
  memcpy(bpp32, screen_buf, screen_size);
}

/********************************************************************************
 ********************************************************************************
 ********************************************************************************
 ********************************************************************************
 ********************************************************************************/

#define LARGEUR_ONDE 20.0
#define VITESSE_ONDE 0.2

static int onde_table_size;
static long onde_table[1024];

static void init_onde_table() {
  double x, max;
  
  onde_table_size = 0;
  max = 2.0 * 3.14 * LARGEUR_ONDE;

  for(x = 0; x < max && onde_table_size < 1024; x += VITESSE_ONDE) {
    onde_table[onde_table_size++] = (long) (60.0 * sin(x / LARGEUR_ONDE) + 30.0);
  }
}

static void ondulation() {
  static long offset, i, j, k;
  static long delta;

  memset(screen_buf, 0, screen_size);

  for(j = 0; j < height; j++) {
    offset = j * pixel_per_line;

    delta = onde_table[(j + k) % onde_table_size];

    for (i = 0; i < width; i++) {
      if(delta + i >= 0 && delta + i < width)
	screen_buf[offset+i+delta] = screen_orig[offset+i];
    }
  }
  k += 20;

  memcpy(bpp32, screen_buf, screen_size);
}

/********************************************************************************
 ********************************************************************************
 ********************************************************************************
 ********************************************************************************
 ********************************************************************************/

inline void CopyToBuff3(unsigned long offset,
		 long delta,
		 long x,
		 unsigned long mask, unsigned int inv_mask)
{
  static unsigned long pos;

  pos = offset + x + delta;
  screen_buf[pos] = ( screen_buf[pos] & inv_mask ) | (screen_orig[offset + x] & mask);
}

static void ondulation_couleur() {
  static long offset, i, j;
  static long delta_red, delta_green, delta_blue;
  static long k_R = 0, k_G = 3, k_B = 10;

  memset(screen_buf, 0, screen_size);

  for(j = 0; j < height; j++) {
    offset = j * pixel_per_line;

    delta_red   = onde_table[(j + k_R) % onde_table_size];
    delta_green = onde_table[(j + k_G) % onde_table_size];
    delta_blue  = onde_table[(j + k_B) % onde_table_size];

    for (i = 0; i < width; i++) {
      if(delta_red + i >= 0 && delta_red + i < width)
	CopyToBuff3(offset, delta_red,   i, 0xFF0000L, 0x00FFFFL);

      if(delta_green + i >= 0 && delta_green + i < width)
	CopyToBuff3(offset, delta_green, i, 0x00FF00L, 0xFF00FFL);

      if(delta_blue + i >= 0 && delta_blue + i < width)
	CopyToBuff3(offset, delta_blue,  i, 0x0000FFL, 0xFFFF00L);
    }
  }

  k_R += 10;
  k_G += 20;
  k_B += 15;

  memcpy(bpp32, screen_buf, screen_size);
}

/********************************************************************************
 ********************************************************************************
 ********************************************************************************
 ********************************************************************************
 ********************************************************************************/

static void drapeau() {
  static long offset, i, j, k;
  static long delta_x, delta_y;
  static int coord;

  //  memset(screen_buf, 0, screen_size);

  for(j = 0; j < height; j++) {
    offset = j * pixel_per_line;

    for (i = 0; i < width; i++) {
      coord = (j + i + k) % max_coord;
      delta_x = coord_x[coord];
      delta_y = coord_y[coord];

      if(delta_x + i < 0 || delta_x + i > width)
	continue;

      if(delta_y + j < 0 || delta_y + j > height)
	continue;

      if(j > 900)
	 continue;

      //	printf("%ld -> %ld / %d\n", offset + i, (delta_y + j) * pixel_per_line + delta_x + i, screen_size);

      screen_buf[(delta_y + j) * pixel_per_line + delta_x + i] = screen_orig[offset + i];
    }
  }

  k += 10;

  memcpy(bpp32, screen_buf, screen_size);
}

/********************************************************************************
 ********************************************************************************
 ********************************************************************************
 ********************************************************************************
 ********************************************************************************/

static void TV_effect() {
  static long offset, i, j, k = 0;
  static unsigned int brightness;
  static unsigned long val_red, val_green, val_blue;

#define FONCE    210
#define CLAIR    245
#define LARGEUR  3

  brightness = FONCE;

  for(j = 0; j < height; j++) {
    offset = j * pixel_per_line;

    if((k+j) % LARGEUR == 0) {
      if(brightness == FONCE)
	brightness = CLAIR;
      else
	brightness = FONCE;
    }

    for (i = 0; i < width; i++) {
      /* rouge */
      val_red = (screen_orig[offset+i] & 0xFF0000L) >> 16;
      val_red = (val_red * brightness) >> 8;

      /* vert */
      val_green = (screen_orig[offset+i] & 0x00FF00L) >> 8;
      val_green = (val_green * brightness) >> 8;

      /* bleu */
      val_blue = (screen_orig[offset+i] & 0x0000FFL);
      val_blue = (val_blue * brightness) >> 8;

      bpp32[offset+i] = val_red << 16 | val_green << 8 | val_blue;
    }
  }

  k++;
  usleep(200000);
  //  memcpy(bpp32, screen_buf, screen_size);
}

/********************************************************************************
 ********************************************************************************
 ********************************************************************************
 ********************************************************************************
 ********************************************************************************/

int main (int argc, char *argv[]) {
   unsigned int i;
   unsigned int R, G, B;

   dpy = XOpenDisplay((char *) 0);       
   if(dpy == NULL) {
     fprintf(stderr, "Can't open display\n");
     exit(1);
   }

   screen = DefaultScreen(dpy);

   if (cpt++ || !is_DGA_available())  // check, if DGA is available
     return 1;                        // exit if not

   XF86DGAGetViewPortSize(dpy, screen, &width, &height);
   XF86DGAGetVideo(dpy, screen, &address, 
                   &pixel_per_line, &bank_size, &ram);

   for(i = 0; i < NSIG; i++)
     signal(i, signal_handler);

   srandom (getpid());

   if(XF86DGAForkApp(screen)) {  // read man-page: returns to non DGA-mode if client exits
      printf("Unable to fork application for safety reason!\n\r");
      ByeBye();
      return 1;                  // quit program
   }

   XF86DGADirectVideo(dpy, screen, XF86DGADirectGraphics);    
   XF86DGASetViewPort(dpy, screen, 0,0);

   screen_size = pixel_per_line * height * sizeof(unsigned long);
   screen_orig = (unsigned long *) malloc(screen_size);
   screen_buf = (unsigned long *) malloc(screen_size);
   memcpy(screen_orig, address, screen_size);
   bpp32 = (long *)address;

   /* 
     width /= 2;
     height /= 2;
   */   

   if(argc >= 1 && argv[1]) {
     switch (atoi(argv[1])) {
     case 0:  /* random color plane */
       while(1) {
	 color_offset();
       }
       break;
     case 1:   /* balayage */
       init_sin_table();
       
       while(1) {
	 balayage();
       }
       break;
     case 2:    /* color plane rotation */
       init_coord_table(30.0, 1.0);
       R = 0;
       G = max_coord / 3;
       B = 2 * G;   
       
       while(1) {
	 circle(&R, &G, &B);
	 R = (R+1) % max_coord;
	 G = (G+1) % max_coord;
	 B = (B+1) % max_coord;
       }
       break;
     case 3:    /* ondulation */
       init_onde_table();
       
       while(1) {
	 ondulation();
       }
       break;
     case 4:    /* conbinaison décalage des plans de couleur + ondulation */
       init_onde_table();
       
       while(1) {
	 ondulation_couleur();
       }
       break;
     case 5:    /* drapeau */
       init_coord_table(2.0, 2.0);
       
       while(1) {
	 drapeau();
       }
       break;
     case 6:    /* TV */
       while(1) {
	 TV_effect();
       }
       break;
     default:
       printf("Effet inconnu\n");
       break;
     }     
   }

   ByeBye();

   return 0;
}
