using System;

using Contralto.Memory;

namespace Contralto.CPU
{
    public partial class AltoCPU
    {
        // Task:
        // Base task class: provides implementation for non-task-specific microcode execution and
        // state.  Task subclasses implement and execute Task-specific behavior and are called into
        // by the base class as necessary.
        public abstract class Task
        {
            public Task(AltoCPU cpu)
            {
                _wakeup = false;
                _mpc = 0xffff;  // invalid, for sanity checking
                _taskType = TaskType.Invalid;
                _cpu = cpu;

                _block = false;
            }

            public int Priority
            {
                get { return (int)_taskType; }
            }

            public bool Wakeup
            {
                get { return _wakeup; }
            }

            public ushort MPC
            {
                get { return _mpc; }
            }

            /// <summary>
            /// Indicates whether the current uInstruction asserts BLOCK.
            /// Used by hardware for various tasks.
            /// </summary>
            public bool BLOCK
            {
                get { return _block; }
            }

            public virtual void Reset()
            {
                // From The Alto Hardware Manual (section 2, "Initialization"):
                // "...each task start[s] at the location which is its task number"
                // 
                _mpc = (ushort)_taskType;
                _rdRam = false;
            }

            public virtual void BlockTask()
            {
                _wakeup = false;
            }

            public virtual void WakeupTask()
            {
                _wakeup = true;
            }

            public bool ExecuteNext()
            {
                // TODO: cache microinstructions (or pre-decode them) to save consing all these up every time.
                MicroInstruction instruction = UCodeMemory.GetInstruction(_mpc, _taskType);

                // Grab BLOCK bit so that other tasks / hardware can look at it
                _block = instruction.F1 == SpecialFunction1.Block;
               
                //Console.WriteLine("R5:{0},R6:{1},IR:{2} - {3}:{4}", OctalHelpers.ToOctal(_cpu._r[5]), OctalHelpers.ToOctal(_cpu._r[6]), OctalHelpers.ToOctal(_cpu._ir), OctalHelpers.ToOctal(_mpc), UCodeDisassembler.DisassembleInstruction(instruction, _taskType));


                return ExecuteInstruction(instruction);
            }

            /// <summary>
            /// ExecuteInstruction causes the Task to execute the next instruction (the one
            /// _mpc is pointing to).  The base implementation covers non-task specific logic; subclasses may
            /// provide their own overrides.
            /// </summary>
            /// <returns>True if a task switch has been requested by a TASK instruction, false otherwise.</returns>
            protected virtual bool ExecuteInstruction(MicroInstruction instruction)
            {
                bool nextTask = false;
                bool swMode = false;
                bool block = false;       
                ushort aluData;
                ushort nextModifier;
                _loadR = false;
                _loadS = false;
                _rSelect = 0;
                _busData = 0;


                Shifter.SetMagic(false);
                Shifter.SetDNS(false, 0);

                //
                // Wait for memory state machine if a memory operation is requested by this instruction and
                // the memory isn't ready yet.                
                //                
                if (instruction.MemoryAccess) 
                {                    
                    if (!_cpu._system.MemoryBus.Ready(instruction.MemoryOperation))
                    {
                        // Suspend operation for this cycle.
                        return false;
                    }
                }

                // If we have a modified next field from the last instruction, make sure it gets applied to this one.
                nextModifier = _nextModifier;
                _nextModifier = 0;

                _rSelect = instruction.RSELECT;

                // Give tasks the chance to modify parameters early on (like RSELECT)
                ExecuteSpecialFunction2Early(instruction);

                // Select BUS data.
                if (!instruction.ConstantAccess)
                {
                    // Normal BUS data (not constant ROM access).
                    switch (instruction.BS)
                    {
                        case BusSource.ReadR:
                            _busData = _cpu._r[_rSelect];
                            break;

                        case BusSource.LoadR:
                            _busData = 0;       // "Loading R forces the BUS to 0 so that an ALU function of 0 and T may be executed simultaneously"
                            _loadR = true;
                            break;

                        case BusSource.None:
                            _busData = 0xffff;  // "Enables no source to the BUS, leaving it all ones"
                            break;

                        case BusSource.TaskSpecific1:
                        case BusSource.TaskSpecific2:
                            _busData = GetBusSource((int)instruction.BS);        // task specific -- call into specific implementation
                            break;

                        case BusSource.ReadMD:
                            _busData = _cpu._system.MemoryBus.ReadMD();
                            break;

                        case BusSource.ReadMouse:                            
                            _busData = _cpu._system.Mouse.PollMouseBits();
                            break;

                        case BusSource.ReadDisp:
                            // "The high-order bits of IR cannot be read directly, but the displacement field of IR (8 low order bits),
                            // may be read with the <-DISP bus source.  If the X field of the instruction is zero (i.e. it specifies page 0
                            // addressing) then the DISP field of the instruction is put on BUS[8-15] and BUS[0-7] is zeroed.  If the X
                            // field of the instruction is nonzero (i.e. it specifies PC-relative or base-register addressing) then the DISP
                            // field is sign-extended and put on the bus."
                            // NB: the "X" field of the NOVA instruction is IR[6-7]                            
                            _busData = (ushort)(_cpu._ir & 0xff);

                            if ((_cpu._ir & 0x300) != 0)
                            {
                                // sign extend if necessary
                                if ((_cpu._ir & 0x80) == 0x80)
                                {
                                    _busData |= (0xff00);
                                }
                            }
                            break;

                        default:
                            throw new InvalidOperationException(String.Format("Unhandled bus source {0}.", instruction.BS));                            
                    }
                }
                else
                {
                    // See also comments below.
                    _busData = ControlROM.ConstantROM[(instruction.RSELECT << 3) | ((uint)instruction.BS)];
                }

                // Constant ROM access:
                // "The constant memory is gated to the bus by F1=7, F2=7, or BS>4.  The constant memory is addressed by the
                // (8 bit) concatenation of RSELECT and BS.  The intent in enabling constants with BS>4 is to provide a masking
                // facility, particularly for the <-MOUSE and <-DISP bus sources.  This works because the processor bus ANDs if
                // more than one source is gated to it.  Up to 32 such mask contans can be provided for each of the four bus sources
                // > 4."
                // NOTE also:
                // "Note that the [emulator task F2] functions which replace the low bits of RSELECT with IR affect only the 
                // selection of R; they do not affect the address supplied to the constant ROM."
                // Hence we use the unmodified RSELECT value here and above.
                if ((int)instruction.BS > 4 ||
                    instruction.ConstantAccess)
                {
                    _busData &= ControlROM.ConstantROM[(instruction.RSELECT << 3) | ((uint)instruction.BS)];
                }

                //
                // If there was a RDRAM operation last cycle, we AND in the uCode RAM data here.
                //
                if (_rdRam)
                {
                    _busData &= UCodeMemory.ReadRAM();
                    _rdRam = false;
                }

                //
                // Let F1s that need to modify bus data before the ALU runs do their thing
                // (this is just used by the emulator RSNF...)
                //
                ExecuteSpecialFunction1Early(instruction);

                // Do ALU operation
                aluData = ALU.Execute(instruction.ALUF, _busData, _cpu._t, _skip);

                //
                // If there was a WRTRAM operation last cycle, we write the uCode RAM here
                // using the results of this instruction's ALU operation.
                //
                if (_wrtRam)
                {
                    UCodeMemory.WriteRAM(aluData, _cpu._m);
                    _wrtRam = false;
                }

                //
                // If there was an SWMODE operation last cycle, we set the flag to ensure it
                // takes effect at the end of this cycle.
                //
                if (_swMode)
                {
                    _swMode = false;
                    swMode = true;
                }

                // Reset shifter op
                Shifter.SetOperation(ShifterOp.None, 0);

                //
                // Do Special Functions
                //
                switch (instruction.F1)
                {
                    case SpecialFunction1.None:
                        // Do nothing.  Well, that was easy.
                        break;

                    case SpecialFunction1.LoadMAR:  
                        // Do MAR or XMAR reference based on whether F2 is MD<-, indicating an extended memory reference.
                        _cpu._system.MemoryBus.LoadMAR(aluData, _taskType, instruction.F2 == SpecialFunction2.StoreMD);
                        break;

                    case SpecialFunction1.Task:
                        nextTask = true;            // Yield to other more important tasks
                        break;

                    case SpecialFunction1.Block:
                        // Technically this is to be invoked by the hardware device associated with a task.
                        // That logic would be circuituous and unless there's a good reason not to that is discovered
                        // later, I'm just going to directly block the current task here.
                        _cpu.BlockTask(this._taskType);

                        // Let task-specific behavior take place at the end of this cycle.
                        block = true;                        
                        break;

                    case SpecialFunction1.LLSH1:
                        Shifter.SetOperation(ShifterOp.ShiftLeft, 1);
                        break;

                    case SpecialFunction1.LRSH1:
                        Shifter.SetOperation(ShifterOp.ShiftRight, 1);
                        break;

                    case SpecialFunction1.LLCY8:
                        Shifter.SetOperation(ShifterOp.RotateLeft, 8);
                        break;

                    case SpecialFunction1.Constant:
                        // Ignored here; handled by Constant ROM access logic above.
                        break;

                    default:
                        // Let the specific task implementation take a crack at this.
                        ExecuteSpecialFunction1(instruction);
                        break;
                }

                switch (instruction.F2)
                {
                    case SpecialFunction2.None:
                        // Nothing!
                        break;

                    case SpecialFunction2.BusEq0:
                        if (_busData == 0)
                        {
                            _nextModifier = 1;
                        }
                        break;

                    case SpecialFunction2.ShLt0:
                        //
                        // Note:
                        // "the value of SHIFTER OUTPUT is determined by the value of L as the microinstruction
                        // *begins* execution and the shifter function specified during the  *current* microinstruction.
                        //
                        // Since we haven't modifed L yet, and we've selected the shifter function above, we're good to go here.
                        //
                        if ((short)Shifter.DoOperation(_cpu._l, _cpu._t) < 0)
                        {
                            _nextModifier = 1;
                        }
                        break;

                    case SpecialFunction2.ShEq0:
                        // See note above.
                        if (Shifter.DoOperation(_cpu._l, _cpu._t) == 0)
                        {
                            _nextModifier = 1;
                        }
                        break;

                    case SpecialFunction2.Bus:
                        // Select bits 6-15 (bits 0-9 in modern parlance) of the bus
                        _nextModifier = (ushort)(_busData & 0x3ff);
                        break;

                    case SpecialFunction2.ALUCY:
                        // ALUC0 is the carry produced by the ALU during the most recent microinstruction
                        // that loaded L.  It is *not* the carry produced during the execution of the microinstruction
                        // that contains the ALUCY function.
                        _nextModifier = _cpu._aluC0;
                        break;

                    case SpecialFunction2.StoreMD:
                        // Special case for XMAR; if F1 is a LoadMAR we do nothing here;
                        // the handler for LoadMAR will load the correct bank.
                        if (instruction.F1 != SpecialFunction1.LoadMAR)
                        {
                            _cpu._system.MemoryBus.LoadMD(_busData);
                        }
                        break;

                    case SpecialFunction2.Constant:
                        // Ignored here; handled by Constant ROM access logic above.
                        break;

                    default:
                        // Let the specific task implementation take a crack at this.
                        ExecuteSpecialFunction2(instruction);
                        break;
                }
               
                // We always do the shifter operation; DNS may need its output.
                //
                Shifter.DoOperation(_cpu._l, _cpu._t);

                //
                // Write back to registers:
                //
                // Do writeback to selected R register from shifter output.
                //
                if (_loadR)
                {
                    _cpu._r[_rSelect] = Shifter.Output;
                }

                // Do writeback to selected R register from M
                if (_loadS)
                {
                    _cpu._s[_cpu._rb][_rSelect] = _cpu._m;
                }

                // Load T
                if (instruction.LoadT)
                {
                    // Does this operation change the source for T?                    
                    _cpu._t = instruction.LoadTFromALU ? aluData : _busData;

                    //
                    // Control RAM: "...the control RAM address is specified by the control RAM
                    // address register... which is loaded from the ALU output whenver T is loaded
                    // from its source."
                    //
                    UCodeMemory.LoadControlRAMAddress(aluData);                    
                }

                // Load L (and M) from ALU outputs.
                if (instruction.LoadL)
                {
                    _cpu._l = aluData;
                    _cpu._m = aluData;

                    // Save ALUC0 for use in the next ALUCY special function.
                    _cpu._aluC0 = (ushort)ALU.Carry;
                }

                //
                // Execute special functions that happen late in the cycle
                //                
                ExecuteSpecialFunction2Late(instruction);

                //
                // Switch banks if the last instruction had an SWMODE F1;
                // this depends on the value of the NEXT field in this instruction
                //
                if (swMode)
                {
                    //Console.WriteLine("SWMODE NEXT {0} Modifier {1}", Conversion.ToOctal(instruction.NEXT), Conversion.ToOctal(nextModifier));
                    UCodeMemory.SwitchMode((ushort)(instruction.NEXT | nextModifier), _taskType);
                }

                //
                // Do task-specific BLOCK behavior if the last instruction had a BLOCK F1.
                //
                if (block)
                {
                    ExecuteBlock();
                }

                //
                // Select next address, using the address modifier from the last instruction.
                //
                _mpc = (ushort)(instruction.NEXT | nextModifier);

                return nextTask;
            }

            protected virtual ushort GetBusSource(int bs)
            {
                // Nothing by default.
                return 0;
            }

            protected virtual void ExecuteSpecialFunction1Early(MicroInstruction instruction)
            {
                // Nothing by default
            }

            protected virtual void ExecuteSpecialFunction1(MicroInstruction instruction)
            {
                // Nothing by default
            }

            /// <summary>
            /// Used to allow Task-specific F2s that need to modify RSELECT to do so.
            /// </summary>
            /// <param name="f2"></param>
            protected virtual void ExecuteSpecialFunction2Early(MicroInstruction instruction)
            {
                // Nothing by default.
            }

            protected virtual void ExecuteSpecialFunction2(MicroInstruction instruction)
            {
                // Nothing by default.
            }

            protected virtual void ExecuteSpecialFunction2Late(MicroInstruction instruction)
            {
                // Nothing by default.
            }

            /// <summary>
            /// Allows task-specific handling for BLOCK microinstructions.
            /// (Disk and Display BLOCKs have side effects apart from removing Wakeup from the task, for example).
            /// </summary>
            protected virtual void ExecuteBlock()
            {
                // Nothing by default
            }

            //
            // Per uInstruction Task Data:
            // Modified by both the base Task implementation and any subclasses
            //
            // TODO: maybe instead of these being shared (which feels kinda bad)
            // these could be encapsulated in an object and passed to subclass implementations?            
            protected ushort _busData;        // Data placed onto the bus (ANDed from multiple sources)
            protected ushort _nextModifier;   // Bits ORed onto the NEXT field of the current instruction
            protected uint _rSelect;        // RSELECT field from current instruction, potentially modified by task
            protected bool _loadS;          // Whether to load S from M at and of cycle
            protected bool _loadR;          // Whether to load R from shifter at end of cycle.
            protected bool _rdRam;          // Whether to load uCode RAM onto the bus during the next cycle.
            protected bool _wrtRam;         // Whether to write uCode RAM from M and ALU outputs during the next cycle.
            protected bool _swMode;         // Whether to switch uCode banks during the next cycle.


            //
            // Global Task Data
            //
            protected AltoCPU _cpu;
            protected ushort _mpc;
            protected TaskType _taskType;
            protected bool _wakeup;

            protected bool _block;

            // Emulator Task-specific data.  This is placed here because it is used by the ALU and it's easier to reference in the
            // base class even if it does break encapsulation.  See notes in the EmulatorTask class for meaning.        
            protected int _skip;
        }
    }
}
