// Copyright (c) 1996 Barry Silverman, Brian Silverman, Vadim Gerasimov.
// Portions Copyright (c) 2009 The Go Authors.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

// This package and spacewar.go implement a simple PDP-1 emulator
// complete enough to run the original PDP-1 video game Spacewar!
//
// They are a translation of the Java emulator pdp1.java in
// http://spacewar.oversigma.com/sources/sources.zip.
//
// See also the PDP-1 handbook at http://www.dbit.com/~greeng3/pdp1/pdp1.html
//
// http://spacewar.oversigma.com/readme.html reads:
//
//	Spacewar! was conceived in 1961 by Martin Graetz, Stephen Russell,
//	and Wayne Wiitanen. It was first realized on the PDP-1 in 1962 by
//	Stephen Russell, Peter Samson, Dan Edwards, and Martin Graetz,
//	together with Alan Kotok, Steve Piner, and Robert A Saunders.
//	Spacewar! is in the public domain, but this credit paragraph must
//	accompany all distributed versions of the program.
//
//	This is the original version! Martin Graetz provided us with a
//	printed version of the source. We typed in in again - it was about
//	40 pages long - and re-assembled it with a PDP-1 assembler written
//	in PERL. The resulting binary runs on a PDP-1 emulator written as
//	a Java applet. The code is extremely faithful to the original. There
//	are only two changes. 1)The spaceships have been made bigger and
//	2) The overall timing has been special cased to deal with varying
//	machine speeds.
//
//	The "a", "s", "d", "f" keys control one of the spaceships. The "k",
//	"l", ";", "'" keys control the other. The controls are spin one
//	way, spin the other, thrust, and fire.
//
//	Barry Silverman
//	Brian Silverman
//	Vadim Gerasimov
//
package pdp1

import (
	"bufio";
	"fmt";
	"os";
	"io";
)

type Word uint32

const mask = 0777777
const sign = 0400000

const (
	_	= iota;	// 00
	opAND;
	opIOR;
	opXOR;
	opXCT;
	_;
	_;
	opCALJDA;

	opLAC;	// 10
	opLIO;
	opDAC;
	opDAP;
	_;
	opDIO;
	opDZM;
	_;

	opADD;	// 20
	opSUB;
	opIDX;
	opISP;
	opSAD;
	opSAS;
	opMUS;
	opDIS;

	opJMP;	// 30
	opJSP;
	opSKP;
	opSFT;
	opLAW;
	opIOT;
	_;
	opOPR;
)

// A Trapper represents an object with a Trap method.
// The machine calls the Trap method to implement the
// PDP-1 IOT instruction.
type Trapper interface {
	Trap(y Word);
}

// An M represents the machine state of a PDP-1.
// Clients can set Display to install an output device.
type M struct {
	AC, IO, PC, OV	Word;
	Mem		[010000]Word;
	Flag		[7]bool;
	Sense		[7]bool;
	Halt		bool;
}


// Step runs a single machine instruction.
func (m *M) Step(t Trapper) os.Error {
	inst := m.Mem[m.PC];
	m.PC++;
	return m.run(inst, t);
}

// Normalize actual 32-bit integer i to 18-bit ones-complement integer.
// Interpret mod 0777777, because 0777777 == -0 == +0 == 0000000.
func norm(i Word) Word {
	i += i>>18;
	i &= mask;
	if i == mask {
		i = 0;
	}
	return i;
}

type UnknownInstrError struct {
	Inst	Word;
	PC	Word;
}

func (e UnknownInstrError) String() string {
	return fmt.Sprintf("unknown instruction %06o at %06o", e.Inst, e.PC);
}

type HaltError Word

func (e HaltError) String() string {
	return fmt.Sprintf("executed HLT instruction at %06o", e);
}

type LoopError Word

func (e LoopError) String() string {
	return fmt.Sprintf("indirect load looping at %06o", e);
}

func (m *M) run(inst Word, t Trapper) os.Error {
	ib, y := (inst>>12)&1, inst&07777;
	op := inst>>13;
	if op < opSKP && op != opCALJDA {
		for n := 0; ib != 0; n++ {
			if n > 07777 {
				return LoopError(m.PC - 1);
			}
			ib = (m.Mem[y] >> 12)&1;
			y = m.Mem[y] & 07777;
		}
	}

	switch op {
	case opAND:
		m.AC &= m.Mem[y];
	case opIOR:
		m.AC |= m.Mem[y];
	case opXOR:
		m.AC ^= m.Mem[y];
	case opXCT:
		m.run(m.Mem[y], t);
	case opCALJDA:
		a := y;
		if ib == 0 {
			a = 64;
		}
		m.Mem[a] = m.AC;
		m.AC = (m.OV << 17) + m.PC;
		m.PC = a+1;
	case opLAC:
		m.AC = m.Mem[y];
	case opLIO:
		m.IO = m.Mem[y];
	case opDAC:
		m.Mem[y] = m.AC;
	case opDAP:
		m.Mem[y] = m.Mem[y] & 0770000 | m.AC & 07777;
	case opDIO:
		m.Mem[y] = m.IO;
	case opDZM:
		m.Mem[y] = 0;
	case opADD:
		m.AC += m.Mem[y];
		m.OV = m.AC >> 18;
		m.AC = norm(m.AC);
	case opSUB:
		diffSigns := (m.AC ^ m.Mem[y])>>17 == 1;
		m.AC += m.Mem[y] ^ mask;
		m.AC = norm(m.AC);
		if diffSigns && m.Mem[y] >> 17 == m.AC >> 17 {
			m.OV = 1;
		}
	case opIDX:
		m.AC = norm(m.Mem[y] + 1);
		m.Mem[y] = m.AC;
	case opISP:
		m.AC = norm(m.Mem[y] + 1);
		m.Mem[y] = m.AC;
		if m.AC & sign == 0 {
			m.PC++;
		}
	case opSAD:
		if m.AC != m.Mem[y] {
			m.PC++;
		}
	case opSAS:
		if m.AC == m.Mem[y] {
			m.PC++;
		}
	case opMUS:
		if m.IO & 1 == 1 {
			m.AC += m.Mem[y];
			m.AC = norm(m.AC);
		}
		m.IO = (m.IO >> 1 | m.AC << 17)&mask;
		m.AC >>= 1;
	case opDIS:
		m.AC, m.IO = (m.AC << 1 | m.IO >> 17)&mask,
			((m.IO << 1 | m.AC >> 17)&mask)^1;
		if m.IO & 1 == 1 {
			m.AC = m.AC + (m.Mem[y] ^ mask);
		} else {
			m.AC = m.AC + 1 + m.Mem[y];
		}
		m.AC = norm(m.AC);
	case opJMP:
		m.PC = y;
	case opJSP:
		m.AC = (m.OV << 17) + m.PC;
		m.PC = y;
	case opSKP:
		cond := y&0100 == 0100 && m.AC == 0 ||
			y&0200 == 0200 && m.AC >> 17 == 0 ||
			y&0400 == 0400 && m.AC >> 17 == 1 ||
			y&01000 == 01000 && m.OV == 0 ||
			y&02000 == 02000 && m.IO >> 17 == 0 ||
			y&7 != 0 && !m.Flag[y&7] ||
			y&070 != 0 && !m.Sense[(y&070)>>3] ||
			y&070 == 010;
		if (ib == 0) == cond {
			m.PC++;
		}
		if y&01000 == 01000 {
			m.OV = 0;
		}
	case opSFT:
		for count := inst&0777; count != 0; count >>= 1 {
			if count&1 == 0 {
				continue;
			}
			switch (inst>>9)&017 {
			case 001:	// rotate AC left
				m.AC = (m.AC << 1 | m.AC >> 17)&mask;
			case 002:	// rotate IO left
				m.IO = (m.IO << 1 | m.IO >> 17)&mask;
			case 003:	// rotate AC and IO left.
				w := uint64(m.AC)<<18 | uint64(m.IO);
				w = w<<1 | w>>35;
				m.AC = Word(w>>18)&mask;
				m.IO = Word(w)&mask;
			case 005:	// shift AC left (excluding sign bit)
				m.AC = (m.AC << 1 | m.AC >> 17)&mask&^sign | m.AC & sign;
			case 006:	// shift IO left (excluding sign bit)
				m.IO = (m.IO << 1 | m.IO >> 17)&mask&^sign | m.IO & sign;
			case 007:	// shift AC and IO left (excluding AC's sign bit)
				w := uint64(m.AC)<<18 | uint64(m.IO);
				w = w<<1 | w>>35;
				m.AC = Word(w>>18)&mask&^sign | m.AC & sign;
				m.IO = Word(w)&mask&^sign | m.AC & sign;
			case 011:	// rotate AC right
				m.AC = (m.AC >> 1 | m.AC << 17)&mask;
			case 012:	// rotate IO right
				m.IO = (m.IO >> 1 | m.IO << 17)&mask;
			case 013:	// rotate AC and IO right
				w := uint64(m.AC)<<18 | uint64(m.IO);
				w = w>>1 | w<<35;
				m.AC = Word(w>>18)&mask;
				m.IO = Word(w)&mask;
			case 015:	// shift AC right (excluding sign bit)
				m.AC = m.AC >> 1 | m.AC & sign;
			case 016:	// shift IO right (excluding sign bit)
				m.IO = m.IO >> 1 | m.IO & sign;
			case 017:	// shift AC and IO right (excluding AC's sign bit)
				w := uint64(m.AC)<<18 | uint64(m.IO);
				w = w>>1;
				m.AC = Word(w>>18) | m.AC & sign;
				m.IO = Word(w)&mask;
			default:
				goto Unknown;
			}
		}
	case opLAW:
		if ib == 0 {
			m.AC = y;
		} else {
			m.AC = y^mask;
		}
	case opIOT:
		t.Trap(y);
	case opOPR:
		if y&0200 == 0200 {
			m.AC = 0;
		}
		if y&04000 == 04000 {
			m.IO = 0;
		}
		if y&01000 == 01000 {
			m.AC ^= mask;
		}
		if y&0400 == 0400 {
			m.PC--;
			return HaltError(m.PC);
		}
		switch i, f := y&7, y&010 == 010; {
		case i == 7:
			for i := 2; i < 7; i++ {
				m.Flag[i] = f;
			}
		case i >= 2:
			m.Flag[i] = f;
		}
	default:
	Unknown:
		return UnknownInstrError{inst, m.PC - 1};
	}
	return nil;
}

// Load loads the machine's memory from a text input file
// listing octal address-value pairs, one per line, matching the
// regular expression ^[ +]([0-7]+)\t([0-7]+).
func (m *M) Load(r io.Reader) os.Error {
	b := bufio.NewReader(r);
	for {
		line, err := b.ReadString('\n');
		if err != nil {
			if err != os.EOF {
				return err;
			}
			break;
		}
		// look for ^[ +]([0-9]+)\t([0-9]+)
		if line[0] != ' ' && line[0] != '+' {
			continue;
		}
		i := 1;
		a := Word(0);
		for ; i < len(line) && '0' <= line[i] && line[i] <= '7'; i++ {
			a = a*8 + Word(line[i]-'0');
		}
		if i >= len(line) || line[i] != '\t' || i == 1 {
			continue;
		}
		v := Word(0);
		j := i;
		for i++; i < len(line) && '0' <= line[i] && line[i] <= '7'; i++ {
			v = v*8 + Word(line[i]-'0');
		}
		if i == j {
			continue;
		}
		m.Mem[a] = v;
	}
	return nil;
}
