/*
 * Cart.cs
 *
 * An abstraction of a game cart.  Attributable to Kevin Horton's Bankswitching
 * document, the Stella source code, and Eckhard Stolberg's 7800 Bankswitching Guide. 
 *
 * Copyright © 2003, 2004, 2010, 2011 Mike Murphy
 *
 */
using System;

namespace EMU7800.Core
{
    public abstract class Cart : IDevice
    {
        static int _multicartBankSelector;

        protected MachineBase M { get; set; }
        protected internal byte[] ROM { get; set; }

        #region IDevice Members

        public virtual void Reset() { }

        public abstract byte this[ushort addr] { get; set; }

        #endregion

        public virtual void Attach(MachineBase m)
        {
            if (m == null)
                throw new ArgumentNullException("m");
            if (M != null && M != m)
                throw new InvalidOperationException("Cart already attached to a different machine.");
            M = m;
        }

        public virtual void StartFrame()
        {
        }

        public virtual void EndFrame()
        {
        }

        protected internal virtual bool RequestSnooping
        {
            get { return false; }
        }

        /// <summary>
        /// Creates an instance of the specified cart.
        /// </summary>
        /// <param name="romBytes"></param>
        /// <param name="cartType"></param>
        /// <exception cref="Emu7800Exception">Specified CartType is unexpected.</exception>
        public static Cart Create(byte[] romBytes, CartType cartType)
        {
            if (cartType == CartType.None)
            {
                switch (romBytes.Length)
                {
                    case 2048:
                        cartType = CartType.A2K;
                        break;
                    case 4096:
                        cartType = CartType.A4K;
                        break;
                    case 8192:
                        cartType = CartType.A8K;
                        break;
                    case 16384:
                        cartType = CartType.A16K;
                        break;
                    case 32768:
                        cartType = CartType.A32K;
                        break;
                }
            }

            switch (cartType)
            {
                case CartType.A2K:     return new CartA2K(romBytes);
                case CartType.A4K:     return new CartA4K(romBytes);
                case CartType.A8K:     return new CartA8K(romBytes);
                case CartType.A8KR:    return new CartA8KR(romBytes);
                case CartType.A16K:    return new CartA16K(romBytes);
                case CartType.A16KR:   return new CartA16KR(romBytes);
                case CartType.DC8K:    return new CartDC8K(romBytes);
                case CartType.PB8K:    return new CartPB8K(romBytes);
                case CartType.TV8K:    return new CartTV8K(romBytes);
                case CartType.CBS12K:  return new CartCBS12K(romBytes);
                case CartType.A32K:    return new CartA32K(romBytes);
                case CartType.A32KR:   return new CartA32KR(romBytes);
                case CartType.MN16K:   return new CartMN16K(romBytes);
                case CartType.DPC:     return new CartDPC(romBytes);
                case CartType.M32N12K: return new CartA2K(romBytes, _multicartBankSelector++);
                case CartType.A7808:   return new Cart7808(romBytes);
                case CartType.A7816:   return new Cart7816(romBytes);
                case CartType.A7832P:  return new Cart7832P(romBytes);
                case CartType.A7832:   return new Cart7832(romBytes);
                case CartType.A7848:   return new Cart7848(romBytes);
                case CartType.A78SGP:  return new Cart78SGP(romBytes);
                case CartType.A78SG:   return new Cart78SG(romBytes, false);
                case CartType.A78SGR:  return new Cart78SG(romBytes, true);
                case CartType.A78S9:   return new Cart78S9(romBytes);
                case CartType.A78S4:   return new Cart78S4(romBytes, false);
                case CartType.A78S4R:  return new Cart78S4(romBytes, true);
                case CartType.A78AB:   return new Cart78AB(romBytes);
                case CartType.A78AC:   return new Cart78AC(romBytes);
                default:
                    throw new Emu7800Exception("Unexpected CartType: " + cartType);
            }
        }

        protected void LoadRom(byte[] romBytes, int multicartBankSize, int multicartBankNo)
        {
            if (romBytes == null)
                throw new ArgumentNullException("romBytes");

            ROM = new byte[multicartBankSize];
            Buffer.BlockCopy(romBytes, multicartBankSize*multicartBankNo, ROM, 0, multicartBankSize);
        }

        protected void LoadRom(byte[] romBytes, int minSize)
        {
            if (romBytes == null)
                throw new ArgumentNullException("romBytes");

            if (romBytes.Length >= minSize)
            {
                ROM = romBytes;
            }
            else
            {
                ROM = new byte[minSize];
                Buffer.BlockCopy(romBytes, 0, ROM, 0, romBytes.Length);
            }
        }

        protected void LoadRom(byte[] romBytes)
        {
            LoadRom(romBytes, romBytes.Length);
        }

        protected Cart()
        {
        }

        #region Serialization Members

        protected Cart(DeserializationContext input)
        {
            if (input == null)
                throw new ArgumentNullException("input");

            input.CheckVersion(1);
        }

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

            output.WriteVersion(1);
        }

        #endregion
    }
}