/* VGAlib version 1.2 - (c) 1993 Tommy Frandsen 		   */
/*								   */
/* This library is free software; you can redistribute it and/or   */
/* modify it without any restrictions. This library is distributed */
/* in the hope that it will be useful, but without any warranty.   */

/* Multi-chipset support Copyright 1993 Harm Hanemaayer */
/* partially copyrighted (C) 1993 by Hartmut Schirmer */

#include <stdio.h>
#include "vga.h"
#include "libvga.h"


/* used to decompose color value into bits (for fast scanline drawing) */
union bits {
    struct {
        unsigned char bit3;
        unsigned char bit2;
        unsigned char bit1;
        unsigned char bit0;
    } b;
    unsigned int i;
};

/* color decompositions */
static union bits color16[16] = {{0,0,0,0},
			         {0,0,0,1},
			      	 {0,0,1,0},
			         {0,0,1,1},
			         {0,1,0,0},
			         {0,1,0,1},
			         {0,1,1,0},
			         {0,1,1,1},
			         {1,0,0,0},
			         {1,0,0,1},
			         {1,0,1,0},
			         {1,0,1,1},
			         {1,1,0,0},
			         {1,1,0,1},
			         {1,1,1,0},
			         {1,1,1,1}};

/* display plane buffers (for fast scanline drawing) */
/* 256 bytes -> max 2048 pixel per line (2/16 colors)*/
static unsigned char plane0[256];
static unsigned char plane1[256];
static unsigned char plane2[256];
static unsigned char plane3[256];


int vga_drawscanline(int line, unsigned char* colors)
{
    if ((CI.colors == 2) || (CI.colors > 256))
	return vga_drawscansegment(colors, 0, line, CI.xbytes);
    else
	return vga_drawscansegment(colors, 0, line, CI.xdim);
} 


int vga_drawscansegment(unsigned char* colors, int x, int y, int length)
{
    /* both length and x must divide with 8 */

    if (MODEX) goto modeX;
    switch (CI.colors) {
        case 16:
            {
       		int i, j, k, first, last, page, l1, l2;
                int offset;
		union bits bytes;
                unsigned char* address;

                k = 0;
                for(i = 0; i < length; i += 8) {
                    bytes.i = 0;
                    first = i;
                    last  = i+8;
                    for(j = first; j < last; j++)
                       bytes.i = (bytes.i<<1) | color16[colors[j]].i;
		    plane0[k]   = bytes.b.bit0;
		    plane1[k]   = bytes.b.bit1;
		    plane2[k]   = bytes.b.bit2;
		    plane3[k++] = bytes.b.bit3;
                }

                offset = (y*CI.xdim + x)/8;
		vga_setpage((page = offset>>16));
		l1 = 0x10000 - (offset &= 0xffff);
		length /= 8;
		if (l1 > length) l1 = length;
		l2 = length - l1; 
		
                address = GM + offset;

		/* disable Set/Reset Register */
	    	port_out(0x01, GRA_I ); 
    		port_out(0x00, GRA_D ); 

		/* write to all bits */
	        port_out(0x08, GRA_I ); 
    		port_out(0xFF, GRA_D );   

		/* select map mask register */
	    	port_out(0x02, SEQ_I ); 

                /* write plane 0 */
    		port_out(0x01, SEQ_D ); 
	        memcpy(address, plane0, l1);

                /* write plane 1 */
    		port_out(0x02, SEQ_D ); 
	        memcpy(address, plane1, l1);

                /* write plane 2 */
    		port_out(0x04, SEQ_D ); 
	        memcpy(address, plane2, l1);

                /* write plane 3 */
    		port_out(0x08, SEQ_D ); 
	        memcpy(address, plane3, l1);

		if (l2 > 0) {
		    vga_setpage(page+1);

                    /* write plane 0 */
    		    port_out(0x01, SEQ_D ); 
	            memcpy( GM, &plane0[l1], l2);

                    /* write plane 1 */
    		    port_out(0x02, SEQ_D ); 
	            memcpy( GM, &plane1[l1], l2);

                    /* write plane 2 */
    		    port_out(0x04, SEQ_D ); 
	            memcpy( GM, &plane2[l1], l2);

                    /* write plane 3 */
    		    port_out(0x08, SEQ_D ); 
	            memcpy( GM, &plane3[l1], l2);
		}

                /* restore map mask register */
    		port_out(0x0F, SEQ_D ); 
  
		/* enable Set/Reset Register */
	        port_out(0x01, GRA_I );
                port_out(0x0F, GRA_D );
	    }
            break;
        case 2 :
	    {
		/* disable Set/Reset Register */
	    	port_out(0x01, GRA_I ); 
	        port_out(0x00, GRA_D );

		/* write to all bits */
	        port_out(0x08, GRA_I ); 
    		port_out(0xFF, GRA_D );   

		/* write to all planes */
	    	port_out(0x02, SEQ_I ); 
    		port_out(0x0F, SEQ_D ); 

	    	memcpy( GM + (y*CI.xdim + x)/8, colors, length);

                /* restore map mask register */
    		port_out(0x0F, SEQ_D ); 
  
		/* enable Set/Reset Register */
	    	port_out(0x01, GRA_I ); 
    		port_out(0x0F, GRA_D );   
	      }
            break;
	case 256:
            {
	      switch (CM) {
		  case G320x200x256: /* linear addressing - easy and fast */
		                     memcpy(GM+y*CI.xdim+x,colors,length);
				     return 0;
	          case G320x240x256:
	          case G320x400x256:
	          case G360x480x256:
modeX:
				     {
				       int first, last, offset, pixel, plane;

				       /* select map mask register */ 
				       port_out(0x02, SEQ_I); 

				       for(plane = 0; plane < 4; plane++) {
					 /* select plane */
					 port_out(1 << plane, SEQ_D);   

					 pixel = plane;
					 first = (y*CI.xdim + x)/4;
					 last  = (y*CI.xdim + x + length)/4;
					 for(offset = first; offset < last; offset++) {
					   GM[offset] = colors[pixel];  
					   pixel += 4;
					 }
				       }
				     }
				     return 0;
	      }
	      {
		unsigned long offset;
		int segment, free;

  SegmentedCopy:
		offset  = y*CI.xbytes+x;
		segment = offset >> 16;
		free	= ((segment+1)<<16)-offset;
		offset  &= 0xFFFF;

		if (free < length) {
			vga_setpage(segment);
			memcpy(GM + offset, colors, free);
   		    	vga_setpage(segment + 1);
			memcpy(GM, colors+free, length-free);
		} else {
			vga_setpage(segment);
			memcpy(GM + offset, colors, length);
		}
	      }
	    }  
            break;
        case 32768:
	case 65536: x *= 2;
	            goto SegmentedCopy;
	case 1<<24: x*=3;
	            goto SegmentedCopy;
    }    

    return 0;
}

