/*
    JPC: A x86 PC Hardware Emulator for a pure Java Virtual Machine
    Release Version 2.0

    A project from the Physics Dept, The University of Oxford

    Copyright (C) 2007 Isis Innovation Limited

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License version 2 as published by
    the Free Software Foundation.

    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.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 
    Details (including contact information) can be found at: 

    www.physics.ox.ac.uk/jpc
*/

package org.jpc.emulator.peripheral;

import org.jpc.emulator.motherboard.*;
import org.jpc.emulator.HardwareComponent;

/**
 * Emulates a standard 16450 UART.
 */
public class SerialPort implements IOPortCapable, HardwareComponent
{
    private static final byte UART_LCR_DLAB = (byte)0x80; /* Divisor latch access bit */
    
    private static final byte UART_IER_MSI = 0x08; /* Enable Modem status interrupt */
    private static final byte UART_IER_RLSI = 0x04; /* Enable receiver line status interrupt */
    private static final byte UART_IER_THRI = 0x02; /* Enable Transmitter holding register int. */
    private static final byte UART_IER_RDI = 0x01; /* Enable receiver data interrupt */
    
    private static final byte UART_IIR_NO_INT = 0x01; /* No interrupts pending */
    private static final byte UART_IIR_ID = 0x06; /* Mask for the interrupt ID */
    
    private static final byte UART_IIR_MSI = 0x00; /* Modem status interrupt */
    private static final byte UART_IIR_THRI = 0x02; /* Transmitter holding register empty */
    private static final byte UART_IIR_RDI = 0x04; /* Receiver data interrupt */
    private static final byte UART_IIR_RLSI = 0x06; /* Receiver line status interrupt */

    /*
     * These are the definitions for the Modem Control Register
     */
    private static final byte UART_MCR_LOOP = 0x10; /* Enable loopback test mode */
    private static final byte UART_MCR_OUT2 = 0x08; /* Out2 complement */
    private static final byte UART_MCR_OUT1 = 0x04; /* Out1 complement */
    private static final byte UART_MCR_RTS = 0x02; /* RTS complement */
    private static final byte UART_MCR_DTR = 0x01; /* DTR complement */
    
    /*
     * These are the definitions for the Modem Status Register
     */
    private static final byte UART_MSR_DCD = (byte)0x80; /* Data Carrier Detect */
    private static final byte UART_MSR_RI = 0x40; /* Ring Indicator */
    private static final byte UART_MSR_DSR = 0x20; /* Data Set Ready */
    private static final byte UART_MSR_CTS = 0x10; /* Clear to Send */
    private static final byte UART_MSR_DDCD = 0x08; /* Delta DCD */
    private static final byte UART_MSR_TERI = 0x04; /* Trailing edge ring indicator */
    private static final byte UART_MSR_DDSR = 0x02; /* Delta DSR */
    private static final byte UART_MSR_DCTS = 0x01; /* Delta CTS */
    private static final byte UART_MSR_ANY_DELTA = 0x0F; /* Any of the delta bits! */
    
    private static final byte UART_LSR_TEMT = 0x40; /* Transmitter empty */
    private static final byte UART_LSR_THRE = 0x20; /* Transmit-hold-register empty */
    private static final byte UART_LSR_BI = 0x10; /* Break interrupt indicator */
    private static final byte UART_LSR_FE = 0x08; /* Frame error indicator */
    private static final byte UART_LSR_PE = 0x04; /* Parity error indicator */
    private static final byte UART_LSR_OE = 0x02; /* Overrun error indicator */
    private static final byte UART_LSR_DR = 0x01; /* Receiver data ready */

    private static final int[] ioPorts =
	new int[]{0x3f8, 0x2f8, 0x3e8, 0x2e8};
    private static final int[] irqLines = 
	new int[]{4, 3, 4, 3};
    private short divider;
    private byte rbr; /* receiver buffer register */
    private byte ier; /* interrupt enable register */
    private byte iir; /* interrupt I/O register */ /* read only */
    private byte lcr; /* line control register */
    private byte mcr; /* modem control register */
    private byte lsr; /* line status register */ /* read only */
    private byte msr; /* modem status register */
    private byte scr; /* scratch register */

    private boolean thrIPending; /* transmitter holding register interrupt */
    private int irq; /* irq channel */
    private int baseAddress; /* base I/O Port Address */
    private InterruptController irqDevice;

    public SerialPort(int portNumber)
    {
	ioportRegistered = false;
	if (portNumber > 3 || portNumber < 0) {
	    System.err.println(portNumber + " is a stupid number, assuming 0");
	    portNumber = 0;
	}
	this.irq = SerialPort.irqLines[portNumber];
	this.baseAddress = SerialPort.ioPorts[portNumber];

	this.lsr = UART_LSR_TEMT | UART_LSR_THRE;
	this.iir = UART_IIR_NO_INT;
    }

    public int canReceive()
    {
	if (0 == (this.getLSR() & UART_LSR_DR))
	    return 1;
	else
	    return 0;
    }

    public void recieve(byte data)
    {
	this.setRBR(data);
	if (0 == data)
	    this.orLSR(UART_LSR_DR);
	else
	    this.orLSR((byte)(UART_LSR_BI | UART_LSR_DR));
	this.updateIRQ();
    }

    private void updateIRQ()
    {
	if((0 != (this.getLSR() & UART_LSR_DR)) && (0 != (this.getIER() & UART_IER_RDI))) {
	    this.setIIR(UART_IIR_RDI);
	} else if (this.getTHRIPending() && (0 != (this.getIER() & UART_IER_THRI))) {
	    this.setIIR(UART_IIR_THRI);
	} else {
	    this.setIIR(UART_IIR_NO_INT);
	}
	if(this.getIIR() != UART_IIR_NO_INT) {
	    irqDevice.setIRQ(this.getIRQ(), 1);
	} else {
	    irqDevice.setIRQ(this.getIRQ(), 0);
	}
    }


    public void ioPortWriteByte(int address, int data)
    {
	this.ioportWrite(address, data);
    }
    public void ioPortWriteWord(int address, int data){}
    public void ioPortWriteLong(int address, int data){}

    public int ioPortReadByte(int address)
    {
	return this.ioportRead(address);
    }
    public int ioPortReadWord(int address)
    {
	return 0xffff;
    }
    public int ioPortReadLong(int address)
    {
	return (int)0xffffffff;
    }

    public int[] ioPortsRequested()
    {
	int base = this.getAddress();
	return new int[]{base, base + 1, base + 2, base + 3, base + 4, base + 5, base + 6, base + 7};
    }

    private void ioportWrite(int address, int data)
    {
	address &= 7;

	switch(address) {
	default:
	case 0:
	    if (0 != (this.getLCR() & UART_LCR_DLAB)) {
		this.setDivider((short)((this.getDivider() & 0xff00) | data));
	    } else {
		this.setTHRIPending(false);
		this.andLSR((byte)~UART_LSR_THRE);
		this.updateIRQ();
		System.out.print((char)(0xff & data));
		//output data
		this.setTHRIPending(true);
		this.orLSR(UART_LSR_THRE);
		this.orLSR(UART_LSR_TEMT);
		this.updateIRQ();
	    }
	    break;
	case 1:
	    if (0 != (this.getLCR() & UART_LCR_DLAB)) {
		this.setDivider((short)((this.getDivider() & 0x00ff) | (data << 8)));
	    } else {
		this.setIER((byte)(data & 0x0f));
		if (0 != (this.getLSR() & UART_LSR_THRE)) {
		    this.setTHRIPending(true);
		}
		this.updateIRQ();
	    }
	    break;
	case 2:
	    break;
	case 3:
	    this.setLCR((byte)data);
	    break;
	case 4:
	    this.setMCR((byte)(data & 0x1f));
	    break;
	case 5:
	    break;
	case 6:
	    this.setMSR((byte)data);
	    break;
	case 7:
	    this.setSCR((byte)data);
	    break;
	}
    }

    private int ioportRead(int address)
    {
	address &= 7;
	int ret;
	switch(address) {
	default:
	case 0:
	    if(0 != (this.getLCR() & UART_LCR_DLAB)) {
		return this.getDivider() & 0xff;
	    } else {
		this.andLSR((byte)~(UART_LSR_DR | UART_LSR_BI));
		ret = this.getRBR();
		this.updateIRQ();
		return ret;
	    }
	case 1:
	    if (0 != (this.getLCR() & UART_LCR_DLAB)) {
		return (this.getDivider() >> 8) & 0xff;
	    } else {
		return this.getIER();
	    }
	case 2:
	    ret = this.getIIR();
	    if ((ret & 0x7) == UART_IIR_THRI)
		this.setTHRIPending(false);
	    this.updateIRQ();
	    return ret;
	case 3:
	    return this.getLCR();
	case 4:
	    return this.getMCR();
	case 5:
	    return this.getLSR();
	case 6:
	    if (0 != (this.getMCR() & UART_MCR_LOOP)) {
		/* in loopback, the modem output pins are connected to the inputs */
		ret = (this.getMCR() & 0x0c) << 4;
		ret |= (this.getMCR() & 0x02) << 3;
		ret |= (this.getMCR() & 0x01) << 5;
		return ret;
	    } else {
		return this.getMSR();
	    }
	case 7:
	    return this.getSCR();
	}
    }

    private short getDivider()
    {return divider;}
    private void setDivider(short divider)
    {this.divider = divider;}

    private byte getRBR()
    {return rbr;}
    private void setRBR(byte input)
    {rbr = input;}

    private byte getIER()
    {return ier;}
    private void setIER(byte data)
    {ier = data;}

    private byte getIIR()
    {return iir;}
    private void setIIR(byte data)
    {iir = data;}

    private byte getLCR()
    {return lcr;}
    private void setLCR(byte data)
    {lcr = data;}

    private byte getMCR()
    {return mcr;}
    private void setMCR(byte data)
    {mcr = data;}

    private byte getLSR()
    {return lsr;}
    private void setLSR(byte data)
    {lsr = data;}
    private void andLSR(byte mask)
    {this.setLSR((byte)(this.getLSR() & mask));}
    private void orLSR(byte mask)
    {this.setLSR((byte)(this.getLSR() | mask));}

    private byte getMSR()
    {return msr;}
    private void setMSR(byte data)
    {msr = data;}

    private byte getSCR()
    {return scr;}
    private void setSCR(byte data)
    {scr = data;}

    private boolean getTHRIPending()
    {return thrIPending;}
    private void setTHRIPending(boolean data)
    {thrIPending = data;}

    private int getIRQ()
    {return irq;}
    private int getAddress()
    {return baseAddress;}

    private boolean ioportRegistered;

    public void reset()
    {
	irqDevice = null;
	ioportRegistered = false;

	this.lsr = UART_LSR_TEMT | UART_LSR_THRE;
	this.iir = UART_IIR_NO_INT;
    }

    public boolean initialised()
    {
	return ioportRegistered && (irqDevice != null);
    }

    public void acceptComponent(HardwareComponent component)
    {
	if ((component instanceof InterruptController) &&
	    component.initialised()) {
	    irqDevice = (InterruptController)component;
	}
	if ((component instanceof IOPortHandler)
	    && component.initialised()) {
	    ((IOPortHandler)component).registerIOPortCapable(this);
	    ioportRegistered = true;
	}
    }
}
