/* 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];

static inline void shifted_memcpy(void *dest_in, void *source_in, int len)
{
    int *dest = dest_in;
    int *source = source_in;

    len >>= 2;

    while(len--)
	*dest++ = (*source++ << 8);
}

/* RGB_swapped_memcopy returns the amount of bytes unhandled */
static inline int RGB_swapped_memcpy(char *dest, char *source, int len)
{
    int rest, tmp;

    tmp = len/3;
    rest = len - 3*tmp;
    len = tmp;

    while(len--) {
	*dest++ = source[2];
	*dest++ = source[1];
	*dest++ = source[0];
	source += 3;
    }

    return rest;
}

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++) {
					   gr_writeb(colors[pixel], offset);
					   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: if (__svgalib_cur_info.bytesperpixel == 4) {
			x <<= 2;
			if (MODEFLAGS & RGB_MISORDERED) {
			    unsigned long offset;
			    int segment, free;

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

			    if (free < length) {
				    vga_setpage(segment);
				    shifted_memcpy(GM + offset, colors, free);
   		    		    vga_setpage(segment + 1);
				    shifted_memcpy(GM, colors+free, length-free);
			    } else {
				    vga_setpage(segment);
				    shifted_memcpy(GM + offset, colors, length);
			    }
			} else {
			    goto SegmentedCopy;
			}
		    break;
		    }

		    x *= 3;
	            if(MODEFLAGS & RGB_MISORDERED) {
			unsigned long offset;
			int segment, free;

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

			if (free < length) {
			    int i;

			    vga_setpage(segment);
			    i = RGB_swapped_memcpy(GM + offset, colors, free);
			    colors += (free - i);

			    switch(i) {
				case 2:
				    gr_writeb(colors[2], 0xfffe);
				    gr_writeb(colors[2], 0xffff);
				    break;
				case 1:
				    gr_writeb(colors[2], 0xffff);
				    break;
			    }

   		    	    vga_setpage(segment + 1);

			    switch(i) {
				case 1:
				    gr_writeb(colors[1], 0);
				    gr_writeb(colors[0], 1);
				    i = 3 - i;
				    free += i;
				    colors += 3;
				    break;
				case 2:
				    gr_writeb(colors[0], 0);
				    i = 3 - i;
				    free += i;
				    colors += 3;
				    break;
			    }

			    RGB_swapped_memcpy(GM + i, colors, length-free);
			} else {
			    vga_setpage(segment);
			    RGB_swapped_memcpy(GM + offset, colors, length);
			}
	            } else {
	                goto SegmentedCopy;
		    }
    }    

    return 0;
}

