/*
 *  Copyright (C) 2002-2006  The DOSBox Team
 *
 *  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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */


#include "dosbox.h"

#include "parport.h"
#include "tcplpt.h"
#include "callback.h"
#include "pic.h"
#include <stdio.h>

#include "printer_charmaps.h"

CTCPLPT::CTCPLPT (Bitu nr, Bit8u initIrq, CommandLine* cmd)
                              :CParallel (cmd, nr,initIrq) {
    // Before ANYTHING else, init WSA so we have matched Startup/Cleanup calls
    WSADATA wsaData;
    int err_code = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (err_code) {
        LOG_MSG("parallel%d: WSA failed to init. Err Code: 0x%x",nr+1, err_code); return;
    }

	InstallationSuccessful = false;
	
	std::string str;

	// add a formfeed when closing?
	if(cmd->FindStringBegin("addFF",str,false))	addFF = true;
	else addFF = false;

	// add a formfeed when closing?
	if(cmd->FindStringBegin("addLF",str,false))	addLF = true;
	else addLF = false;

	// find the codepage
	Bitu temp=0;
	codepage_ptr = NULL;
	if(cmd->FindStringBegin("cp:",str,false)) {
		if(sscanf(str.c_str(), "%u",&temp)!=1) {
			LOG_MSG("parallel%d: Invalid codepage parameter.",nr+1);
			return;
		} else {
			Bitu i = 0;
			while(charmap[i].codepage!=0) {
				if(charmap[i].codepage==temp) {
					codepage_ptr = charmap[i].map;
					break;
				}
				i++;
			}
		}
	}
	
	temp=0;
	if(!cmd->FindStringBegin("ip:",ip,false)) {
        LOG_MSG("parallel%d: No TCP port specified.",nr+1);
        return;
    }

    std::string portname;
    if(cmd->FindStringBegin("port:",portname,false)) {
		if(sscanf(portname.c_str(), "%u",&port)!=1) {
			LOG_MSG("parallel%d: Invalid port parameter.",nr+1);
			return;
		}
	}
    else {
        LOG_MSG("parallel%d: No TCP port specified.",nr+1);
        return;
    }
    
    addrinfo  aiSpecs;
    addrinfo* p_aiResult = NULL;

    memset(&aiSpecs, 0, sizeof(aiSpecs));
    aiSpecs.ai_flags    = AI_NUMERICHOST;
    aiSpecs.ai_family   = AF_UNSPEC;
    aiSpecs.ai_socktype = SOCK_STREAM;
    aiSpecs.ai_protocol = IPPROTO_TCP;

    err_code = getaddrinfo(ip.c_str(), portname.c_str(), &aiSpecs, &p_aiResult);
    if (err_code) {
        LOG_MSG("parallel%d: Invalid ip/port: %s:%s. Err Code: 0x%x; Err Msg: %s",
                nr+1, ip.c_str(), portname.c_str(), err_code, gai_strerror(err_code));
        return;
    }

    skDest = socket(p_aiResult->ai_family, p_aiResult->ai_socktype, p_aiResult->ai_protocol);
    if (skDest == INVALID_SOCKET) {
        LOG_MSG("parallel%d: Failed to create socket: 0x%x.",nr+1, WSAGetLastError());
        freeaddrinfo(p_aiResult); return;
    }

    if (connect(skDest, p_aiResult->ai_addr, (int)p_aiResult->ai_addrlen)) {
        LOG_MSG("parallel%d: Failed to connect: 0x%x.",nr+1, WSAGetLastError());
        freeaddrinfo(p_aiResult); return;
    }
    
    freeaddrinfo(p_aiResult);
    p_aiResult = NULL;

	InstallationSuccessful = true;
}

CTCPLPT::~CTCPLPT () {
    WSACleanup();

	// close file
    closesocket(skDest);
    skDest = INVALID_SOCKET;
	// remove tick handler
	removeEvent(0);
}

bool CTCPLPT::Putchar(Bit8u val)
{	
#if PARALLEL_DEBUG
	log_par(dbg_putchar,"putchar  0x%2x",val);
	if(dbg_plainputchar) fprintf(debugfp,"%c",val);
#endif
	
	// write to file (or not)
	lastUsedTick = PIC_Ticks;
	
	if(codepage_ptr!=NULL) {
		Bit16u extchar = codepage_ptr[val];
		if(extchar & 0xFF00) {
            char c = (char)(extchar >> 8);
            send(skDest, &c, sizeof(c), 0); 
        }
        char c = (char)(extchar & 0xFF);
        send(skDest, &c, sizeof(c), 0); 
	} else
        send(skDest, (char*)(&val), sizeof(val), 0); 

	if(addLF) {
		if((lastChar == 0x0d) && (val != 0x0a)) {
            char c = 0xa;
            send(skDest, &c, sizeof(c), 0); 
		}
		lastChar = val;
	}

	return true;
}
Bitu CTCPLPT::Read_PR() {
	return datareg;
}
Bitu CTCPLPT::Read_COM() {
	return 0;
}
Bitu CTCPLPT::Read_SR() {
	Bit8u status =0x9f;
	if(!ack) status |= 0x40;
	ack=false;
	return status;
}

void CTCPLPT::Write_PR(Bitu val) {
	datareg = (Bit8u)val;
}
void CTCPLPT::Write_CON(Bitu val) {
	// init printer if bit 4 is switched on
	// ...
	autofeed = ((val & 0x02)!=0); // autofeed adds 0xa if 0xd is sent

	// data is strobed to the parallel printer on the falling edge of strobe bit
	if((!(val&0x1)) && (controlreg & 0x1)) {
		Putchar(datareg);
		if(autofeed && (datareg==0xd)) Putchar(0xa);
		ack = true;
	}
	controlreg=val;
}
void CTCPLPT::Write_IOSEL(Bitu val) {
	// not needed for file printing functionality
}
void CTCPLPT::handleUpperEvent(Bit16u type) {
    if (skDest == INVALID_SOCKET) {
	    if(lastUsedTick + timeout < PIC_Ticks) {
		    if(addFF) {
                char c = 12;
                send(skDest, &c, sizeof(c), 0); 
		    }
		
		    lastChar = 0;
	    } else {
		    // Port has been touched in the meantime, try again later
		    float new_delay = (float)((timeout + 1) - (PIC_Ticks - lastUsedTick));
		    setEvent(0, new_delay);
	    }
    }
}

