#include <iostream.h>
#include "tserial.h"
#include <string.h>
#include <dos.h>
#include <bios.h>
#include <conio.h>

// interrupt functions. 
typedef void interrupt far intFunc(...);

// Programmed Interrupt Controller constants.
const PIC0 = 0x20;
const PIC1 = 0x21;
const EOI = 0x20;

// Offsets from port address.
const InterruptEnable = 1, 
      InterruptIdent = 2,
      ModemControl = 4,
      LineStatus = 5,
      ModemStatus = 6;
      
// Value for the modem control register.
const ModemControlValue = 11;

// A serial port circular buffer class.  There
// are only two instances of this class, one
// for each port COM1, and COM2.  They are
// static globals, and are initailized before
// main().  They contain a circular buffer,
// the port number and port address, and the
// interrupt vector they replace, so that 
// they can be restored at destruction.  This
// is not a complete or robust class, and is
// private to this module.  It is intended
// only for use by SerialStream.
class ComBuffer {
public:
// Size of circular buffer
    enum { BufSize = 0x100 };
// Default constructor.  
    ComBuffer();
    ~ComBuffer();
// Set up vector, and enables interrupts
    void hookInterrupt();
// Get a single character from the buffer
    int getc();
// Send a single character to the port
    void putc(int c);
// Read the line status register.
    int status() const
    { 
        return ::inp(portAddr+LineStatus); 
    }
// Check if characters are available
    int avail() const
    { 
        return in != out; 
    }
// Receive a character from port.  This function
// is called by the interrupt routine.
    void receive();
private:
// The mask needed for setting the PIC.
    int interruptMask(int port) const
    { 
        return 1 << (4 - port); 
    }
// The interrupt number for each port.
    int interruptVec(int port) const
    { 
        return 12 - port; 
    }
    char *in, *out, *buff;	// The circular buffer
    int port;			// The port number 0-1
    int portAddr;		// The port address
    intFunc *oldVector;		// The previos vector
    static int initPort;	
};

// initPort is used by the constructor to initialize
// the ports in sequence.
int ComBuffer::initPort = 0;
// Only two ports are set up here.  If you add more
// you need to identify the port addresses and 
// interrupt vectors.
static ComBuffer CommPorts[2];

// Initialize the buffers.  Allocate the circular
// buffer, set the in/out pointers, set the port
// and port address values, and clear the old
// vector value.
ComBuffer::ComBuffer()
    : buff(new char[BufSize]), port(initPort++), 
               oldVector(0)
{
// this line is for sceptics to see initializing.
    cout << "initializing port #" << port << endl;
    portAddr = port ? 0x2f8 : 0x3f8;
    in = out = buff;
}

// Delete the circular buffer.  If the old vector
// is a valid address, restore it.
ComBuffer::~ComBuffer()
{
// another line for sceptics.
    cout << "de-initializing port #" << port << endl;
    if(oldVector)
        ::setvect(interruptVec(port),oldVector);
    delete[] buff;
}

// These two routines reset to PIC, and put
// the new character in the buffer
void interrupt far comInterrupt0(...)
{
    ::outp(PIC0,EOI);
    CommPorts[0].receive();
}

void interrupt far comInterrupt1(...)
{
    ::outp(PIC0,EOI);
    CommPorts[1].receive();
}

// This function sets up the interrupts, and
// enables the PIC
void
ComBuffer::hookInterrupt()
{
    if(oldVector == 0)
        oldVector = ::getvect(interruptVec(port));
    switch(port)
    {
    case 0:
        ::setvect(interruptVec(port),comInterrupt0);
        break;
    case 1:
        ::setvect(interruptVec(port),comInterrupt1);
        break;
    default:
        return;
    }
    ::outp(portAddr+ModemControl,
      ::inp(portAddr+ModemControl)|ModemControlValue);
    ::outp(PIC1,::inp(PIC1) & ~interruptMask(port)); 
    ::outp(portAddr+InterruptEnable,1);
    ::outp(PIC0, EOI);
    ::inp(portAddr);
    ::inp(portAddr+InterruptIdent);
    ::inp(portAddr+ModemStatus);
    status();			// clear status reg.
}

// Receive a byte from the port and place in the 
// circular buffer, called from the interrupt
// handler.
void
ComBuffer::receive()
{
    if(in == buff + BufSize)
        in = buff;		// circular buffer
    *in++ = ::inp(portAddr);
}

// Get a character from the circular buffer.
int
ComBuffer::getc()
{
    while(!avail())
        if(::kbhit())
           return -1;
    if(out == buff + BufSize)
        out = buff;		// circular buffer
    return  *out++;
}

// Send a character directly to the port
void
ComBuffer::putc(int c)
{
    while((status() & 0x20) == 0)
        if(::kbhit())
            return;
    ::outp(portAddr, c);
}

const default_init = _COM_1200|_COM_CHR8|
                     _COM_STOP1|_COM_NOPARITY;

// The open function needs to translate the name
// to the port number, initialize the port, and
// enable interrupts.
int SerialStream::open(const char *name, int, int)
{
    if(::stricmp(name,"COM1") == 0)
        port = 0;
    else if(::stricmp(name, "COM2") == 0)
        port = 1;
    else
        return -1;
    unsigned int stat = ::_bios_serialcom(
                     _COM_INIT,port,default_init);
    buffPtr = CommPorts+port;
    buffPtr->hookInterrupt();
    if(stat & 0xFF00)
        return -1;
    return 0;
}

// The close routine sets the port number to -1
// to indicate it's closed
int
SerialStream::close()
{
    port = -1;
    return 0;
}

// The read routine repeatedly calls ComBuffer::getc
// to fill it's buffer.  len should always be 1.
int SerialStream::read(char *b, size_t len)
{
    for(int i = 0; i < len; i++)
        b[i] = buffPtr->getc();
    return i;
}

// The write routine repeatedly calls ComBuffer::putc
// to empty it's buffer.  len should alway be 1.
int SerialStream::write(char *b, size_t len)
{
    for(int i = 0; i < len; i++)
        buffPtr->putc(b[i]);
    return i;
}

