/**
 * Linux parallel port userspace driver (ppdev) for the
 *  Nixdorf 0043/44 punch card reader with
 *  buffer 0377.01 (8 bit encoding)
 *
 * Copyright (c) March 2009 Sven Koeppel
 * 
 * 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 3 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, see
 * <http://www.gnu.org/licenses/>.
 *
 **/
 
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <time.h>
#include <fcntl.h>

#include <sys/ioctl.h>
#include <linux/ppdev.h>
#include <linux/parport.h>

#include <sys/time.h>
#include <sys/select.h>

#define PARPORT_DEVICE "/dev/parport0"

int parport_fd;

typedef enum {
	MAGNET_ON,
	MAGNET_OFF
} magnet_status_t;

void set_magnet(magnet_status_t target_status) {
	unsigned char mask = (target_status==MAGNET_OFF ? PARPORT_CONTROL_AUTOFD : 0x0);
	if(ioctl(parport_fd, PPWCONTROL, &mask)) {
		perror("Setting magnet status (control lines)");
		exit(0);
	}
}

int mtime(long long since) {
        /* gibt Millisekunden zurueck seit since. since sind dabei die
           Millisekunden seit der Unix-Epoche. Ein initiales since kann
           per mtime(0) gefunden werden. */
        struct timeval time;
        gettimeofday(&time, NULL);
              // Sec*10^3
        return (time.tv_sec * 1000 + (long long)(time.tv_usec / 1000)) - since;
}

void print_bit(char* label, int pos, unsigned char byte) {
	int value = ((byte >> pos) & 0x01)==0 ? 0 : 1;
	if(label == NULL)
		printf("%i=%i ", pos, value);
	else
		printf("%s=%i ", label, value);
}

void run_real_program() {
	long long abs_time = mtime(0);
	for(;;) { // for cards
		printf("\n------- Starting for a new card. ------\n", mtime(abs_time));
		printf("[%i abs] Starting magnet...\n", mtime(abs_time));
		long long card_time = mtime(0);
		set_magnet(MAGNET_ON);
		
		// clear the interrupt counter:
		unsigned char irqc;
		ioctl(parport_fd, PPCLRIRQ, &irqc);
		//usleep(1500*1000); // 1,5 Sec -- Magnete sind Traege!

		magnet_status_t magnet_status = MAGNET_ON;
		int col_counter = 0; // counts the columns that have been read in
		int missed_counter=0;// how many columns=intterupts I have missed
		int had_card = 0;    // switch whether we have had a card
		int can_finish = 0;  // true if we can finish the card loop.
		for(; !can_finish;) {
			fflush(NULL);
			unsigned char data;
			unsigned char status;

			struct timeval select_timeout;
			select_timeout.tv_sec = 2;
			select_timeout.tv_usec = 0;
			
			fd_set rfds;
			FD_ZERO (&rfds);
			FD_SET (parport_fd, &rfds);
			//printf("Waiting for interrupt...\n");
			if (!select (parport_fd + 1, &rfds, NULL, NULL, &select_timeout)) {
				// didn't fetch an interrupt
				printf("[%i cyc] Could not fetch any interrupt!\n", mtime(card_time));
				printf("Will break scanning this card.\n");
				break;
			}
			
			// control the magnet...
			if(magnet_status == MAGNET_ON && mtime(card_time) > 500) {
				printf("[%i cyc] Turning magnet off\n",
					mtime(card_time));
				set_magnet(MAGNET_OFF);
				magnet_status = MAGNET_OFF;
			}
			
			// we've got that interrupt
			// fetch current STATUS register values
			if(ioctl(parport_fd, PPRSTATUS, &status)) {
				perror("Status data readout");
				exit(1);
			}
		
			// display status:
			printf("[%i cyc] ",(int)mtime(card_time));
			if(status & PARPORT_STATUS_SELECT) {
				// we've got a card, so read next column
				col_counter++;
				// and remind that we actually have at least read one
				// column from a card
				had_card = 1;
			
				if(ioctl(parport_fd, PPRDATA, &data)) {
					perror("Interrupt data readout");
					exit(1);
				}
			
				printf("%0i. column: ", col_counter);
				int pos;
				for(pos=0; pos<8; print_bit(NULL, pos++, data));
				printf("| status: ");
				print_bit("ERROR", 3, status);
				print_bit("SEL", 4, status);
				print_bit("PE", 5, status);
				print_bit("ACK", 6, status);
				print_bit("BUSY", 7, status);
				printf("\n");
			} else if(!had_card) {
				printf("No card yet.\n");
				if(mtime(card_time) > 900) {
					printf("[%i cyc] Being bored. Canceling card scanning\n", mtime(card_time));
					can_finish = 1;
				}
			} else {
				printf("Card gone! Finish successfullly\n");
				can_finish = 1;
			}
			
			if(col_counter > 90) {
				printf("[%i cyc] Aborting! Too many cols!", mtime(card_time));
				can_finish = 1;
			}
		
			// clear the interrupt
			unsigned char irqc;
			ioctl(parport_fd, PPCLRIRQ, &irqc);
			if(irqc > 1) {
				missed_counter += irqc-1;
				printf("[%i cyc] MISSED %d INTTERUPT%s!\n",
					mtime(card_time), irqc-1, irqc == 2 ? "S" : "");
			}
		} // for columns
		
		printf("[%i cyc] Disabling magnet...\n", mtime(card_time));
		set_magnet(MAGNET_OFF);
		printf("+------------------------------------------+\n");
		printf("|                CARD REPORT               |\n");
		printf("| Card read in time:  %i ms\n", mtime(card_time));
		printf("| Columns read in:    %0i\n", col_counter);
		printf("| Interrupts missed:  %0i\n", missed_counter);
		printf("| Sum:                %0i\n", col_counter+missed_counter);
		printf("| Diff to 80 cols:    %0i\n", 80-(col_counter+missed_counter));
		if(col_counter == 80)
			printf("|       C O N G R A T U L A T I O N S      |\n");
		else if(col_counter+missed_counter == 80)
			printf("|        YOU ARE SIMPLY TOO SLOW!!!        |\n");
		else if(col_counter+missed_counter < 80)
			printf("|       THAT'S THE READER'S FAULT!         |\n");
		else if(col_counter > 80)
			printf("|       READ IN *MORE* THAN ONE CARD!      |\n");
		else
			printf("|           PRETTY MYSTERIOUS.             |\n");
		printf("+------------------------------------------+\n");
		sleep(2);
	} // for all the cards
} // function

void magnet_testing_program() {
	printf("Looping.\n");

	int x = 10;
	//for(x=0; x<10; x++) {
	while(x > 0) {
		printf("m = Magnet, o = Magnet off, q = Quit\n> ");
		fflush(NULL);
		switch(getchar()) {
			case '\n': break;
			case 'm':
				printf("Magnet is ON!\n");
				set_magnet(MAGNET_ON); break;
			case 'q':
				printf("Quitting\n");
				x = -42;
				break;
			case 'o':
			default:
				printf("Magnet is OFF!\n");
				set_magnet(MAGNET_OFF);
		}
	}
}

int main(int arvc, char** argv) {
	printf("opening device %s...\n", PARPORT_DEVICE);
	parport_fd = open(PARPORT_DEVICE, O_RDWR);
	if(parport_fd == -1) {
		perror("opening device failed");
		return 1;
	}
	
	printf("claiming port...\n");
	if(ioctl(parport_fd, PPCLAIM)) {
		perror("claiming port (PPCLAIM)");
		return 1;
	}
	
	printf("setting compatibility mode...\n");
	int mode = IEEE1284_MODE_COMPAT; //BYTE; //COMPAT;
	if(ioctl(parport_fd, PPNEGOT, &mode)) {
		perror("Setting compatibilty mode (PPNEGOT)");
		return 1;
	}
	
	printf("Disabling data line drivers...\n");
	mode = 1;
	if(ioctl(parport_fd, PPDATADIR, &mode)) {
		perror("Setting PPDATADIR");
		return 1;
	}
	
	printf("Ready to start.\n");
	printf("Setting up control mask\n");
	
	set_magnet(MAGNET_OFF);
	//magnet_testing_program();
	
	// now fetch interrupts and give out data at these times.
	// never use the magnet.
	//simple_reading_program();
	
	run_real_program();

	printf("Finished. Turning magnet off and quitting.\n");
	set_magnet(MAGNET_OFF);
	return 0;
} // main


