#include "parallel.h"
#include <stdlib.h>
#include <assert.h>
#include <stdio.h>

#ifdef __linux__
  #include <sys/io.h>
  #include <unistd.h>
  #include <sys/types.h>

  #define  inportb   inb
  #define  outportb(A,B)  outb(B, A)  
#endif

#ifdef __dos__
  #include <dos.h>
#endif

/*********************************************************************
*              Set up a new parallel port. 
*********************************************************************/
parallel_t * parallel_Init (long int base_address) {
  parallel_t * self;

  self = malloc(sizeof(parallel_t));
  assert(self != NULL);
  
  self->ControlInversions = 0;
  self->StatusInversions  = 0;
  self->DataInversions    = 0;
  self->LastControl       = 0;
  self->LastData          = 0;
  self->BaseAddress       = base_address;
#ifdef __linux__
  if (geteuid() != 0) {
    fprintf(stderr,"ERROR: this programs needs root permisions to access parallel port. Please use root user to run or setuid bit.\n");
    abort();
  }
  ioperm(self->BaseAddress,3,1);
#endif


  return self;
}

/**************************************************************
*                 Free Resources
**************************************************************/
void parallel_Destroy (parallel_t * self) {
  if (self != NULL) {
#ifdef __linux__
    ioperm(self->BaseAddress, 3, 0);
#endif
    free(self);
  }
}

/*******************************************************
*                  Read the Data port (i/o)
*******************************************************/
unsigned int parallel_ReadData (parallel_t * self) {
  return (unsigned int) inportb (self->BaseAddress)^self->DataInversions;
}

/*******************************************************
*                Read the Status port (i)
*******************************************************/
unsigned int parallel_ReadStatus(parallel_t * self) {
  return (unsigned int) inportb (self->BaseAddress + 1)^self->StatusInversions;
}

/********************************************************
*                Read the control port (o)         
********************************************************/
unsigned int parallel_ReadControl (parallel_t * self) {
  return (unsigned int) self->LastControl^self->ControlInversions;
}

/*********************************************************
*                Read a bit form the Data Port
*********************************************************/
int parallel_ReadDataBit (parallel_t * self, int bit) {
  int i;
  i = parallel_ReadData (self);
  if (i & (0x01 << bit))
    return 1;
  return 0;
}

/*********************************************************
*                Read a bit form the Data Port
*********************************************************/
int parallel_ReadControlBit (parallel_t * self, int bit) {
  int i;
  i = parallel_ReadControl (self);
  if (i & (0x01 << bit))
    return 1;
  return 0;
}

/*********************************************************
*                Read a bit form the Status Port
*********************************************************/
int parallel_ReadStatusBit (parallel_t * self, int bit) {
  int i;
  i = parallel_ReadStatus (self);
  if (i & (0x01 << bit))
    return 1;
  return 0;
}

/************************************************************
*               Write to the Control port
************************************************************/
void parallel_WriteControl (parallel_t * self, unsigned int value) {

  if ((value & 0xFF) != (self->LastControl & 0xFF))
    outportb(self->BaseAddress + 2,(value ^ self->ControlInversions) & 0xFF);  
    
  self->LastControl = value;
}

/************************************************************
*               Write to the Data port
************************************************************/
void parallel_WriteData (parallel_t * self, unsigned int value) {

  if ((value&0xFF) != (self->LastData&0xFF))
    outportb(self->BaseAddress,(value^self->DataInversions)&0xFF);  

    
  self->LastData = value;
}

/*************************************************************
*                Write a bit to the data port
*************************************************************/
void parallel_WriteDataBit (parallel_t * self, int bit, unsigned value) {
  int i;
  i = self->LastData;
  if (value) {
    i |= (0x01 << bit);
  } else {
    i &= ~(0x01 << bit);
  }
  parallel_WriteData (self, i);
}

/*************************************************************
*                Write a bit to the control port
*************************************************************/
void parallel_WriteControlBit (parallel_t * self, int bit, unsigned value) {
  int i;
  i = self->LastControl;
  if (value) {
    i |= (0x01 << bit);
  } else {
    i &= ~(0x01 << bit);
  }
  parallel_WriteControl (self, i);
}

// now we need a nice merged format to unify it all into one clump

/*
17 bit field: (INT32)
D0.D1.D2.D3.D4.D5.D6.D7.STROBE.AUTOFEED.INITIALIZE.SELECT_IN.ERROR.SELECTED.PAPER.ACK.BUSY

unsigned long int ports;

0 && data && ((control && 0x0F) << 8) && ((status && 0xF8) << 9)

*/

/**********************************************
*          unified port read
**********************************************/
unsigned long parallel_Read(parallel_t * self) {
   return (0x00000000 & parallel_ReadData(self) & ((parallel_ReadControl(self) & 0x0F) << 8) && ((parallel_ReadStatus(self) & 0xF8) << 9));
} 

/**********************************************
*              unified port write
**********************************************/
void parallel_Write(parallel_t * self, unsigned long In ) {
  parallel_WriteData( self, In & 0xFF );
  parallel_WriteControl(self,  (In >> 8) & 0x0F );
}

/***********************************************
*              unified port bit read
***********************************************/
int parallel_ReadBit(parallel_t * self, int bit){
  if (bit <= 7) {         // part of data
    return parallel_ReadDataBit (self, bit); 
  } else if (bit <= 11) { // part of Control
    return parallel_ReadControlBit (self, (bit - 8)); 
  } else if (bit <= 16) { // part of Status
    return parallel_ReadStatusBit (self, (bit - 11)); 
  } else {
    return 0;
  }
}

/************************************************
*               unified port bit write
************************************************/
void parallel_WriteBit(parallel_t * self, int bit, unsigned value){
  if (bit <= 7) {         // part of data
    parallel_WriteDataBit (self, bit, value); 
  } else if (bit <= 11) { // part of Control
    parallel_WriteControlBit (self, (bit - 8), value); 
  } 
}

/*
test
void parallel_Test() {
flash: 
 D0
 D1
 D2
 D3
 D4
 D5
 D6
 D7
 stobe
 linefeed
 reset
 select
feedback tests
  D0 ->
    error
    selectin
    paper
    ack
    busy
    
  
}
*/
