/*
 * AddressSpace.cs
 *
 * The class representing the memory map or address space of a machine.
 *
 * Copyright © 2003, 2011 Mike Murphy
 *
 */
using System;

namespace EMU7800.Core
{
    public sealed class AddressSpace
    {
        public MachineBase M { get; private set; }

        readonly int AddrSpaceShift;
        readonly int AddrSpaceSize;
        readonly int AddrSpaceMask;

        readonly int PageShift;
        readonly int PageSize;

        readonly IDevice[] MemoryMap;
        IDevice Snooper;

        public byte DataBusState { get; private set; }

        public override string ToString()
        {
            return "EMU7800.Core.AddressSpace";
        }

        public byte this[ushort addr]
        {
            get
            {
                if (Snooper != null)
                {
                    // here DataBusState is just facilitating a dummy read to the snooper device
                    // the read operation may have important side effects within the device
                    DataBusState = Snooper[addr];
                }
                var pageno = (addr & AddrSpaceMask) >> PageShift;
                var dev = MemoryMap[pageno];
                DataBusState = dev[addr];
                return DataBusState;
            }
            set
            {
                DataBusState = value;
                if (Snooper != null)
                {
                    Snooper[addr] = DataBusState;
                }
                var pageno = (addr & AddrSpaceMask) >> PageShift;
                var dev = MemoryMap[pageno];
                dev[addr] = DataBusState;
            }
        }

        public void Map(ushort basea, ushort size, IDevice device)
        {
            if (device == null)
                throw new ArgumentNullException("device");

            for (int addr = basea; addr < basea + size; addr += PageSize)
            {
                var pageno = (addr & AddrSpaceMask) >> PageShift;
                MemoryMap[pageno] = device;
            }

            LogDebug("{0}: Mapped {1} to ${2:x4}:${3:x4}", this, device, basea, basea + size - 1);
        }

        public void Map(ushort basea, ushort size, Cart cart)
        {
            if (cart == null)
                throw new ArgumentNullException("cart");

            cart.Attach(M);
            var device = (IDevice)cart;
            if (cart.RequestSnooping)
            {
                Snooper = device;
            }
            Map(basea, size, device);
        }

        #region Constructors

        public AddressSpace(MachineBase m, int addrSpaceShift, int pageShift)
        {
            if (m == null)
                throw new ArgumentNullException("m");

            M = m;

            AddrSpaceShift = addrSpaceShift;
            AddrSpaceSize  = 1 << AddrSpaceShift;
            AddrSpaceMask = AddrSpaceSize - 1;

            PageShift = pageShift;
            PageSize = 1 << PageShift;

            MemoryMap = new IDevice[1 << addrSpaceShift >> PageShift];
            IDevice nullDev = new NullDevice(M);
            for (var pageno=0; pageno < MemoryMap.Length; pageno++)
            {
                MemoryMap[pageno] = nullDev;
            }
        }

        #endregion

        #region Serialization Members

        public AddressSpace(DeserializationContext input, MachineBase m, int addrSpaceShift, int pageShift) : this(m, addrSpaceShift, pageShift)
        {
            if (input == null)
                throw new ArgumentNullException("input");

            input.CheckVersion(1);
            DataBusState = input.ReadByte();
        }

        public void GetObjectData(SerializationContext output)
        {
            if (output == null)
                throw new ArgumentNullException("output");

            output.WriteVersion(1);
            output.Write(DataBusState);
        }

        #endregion

        #region Helpers

        [System.Diagnostics.Conditional("DEBUG")]
        void LogDebug(string format, params object[] args)
        {
            if (M == null || M.Logger == null)
                return;
            M.Logger.WriteLine(format, args);
        }

        #endregion
    }
}