using System;
using System.IO;

namespace Contralto.CPU
{
    static class ControlROM
    {
        static ControlROM()
        {
            Init();
        }

        private static void Init()
        {
            LoadConstants(_constantRoms);
            LoadACSource(_acSourceRoms);
        }

        public static ushort[] ConstantROM
        {
            get { return _constantRom; }
        }

        public static byte[] ACSourceROM
        {
            get { return _acSourceRom; }
        }

        private static void LoadConstants(RomFile[] romInfo)
        {
            _constantRom = new ushort[256];

            foreach (RomFile file in romInfo)
            {
                //
                // Each file contains 256 bytes, each byte containing one nybble in the low 4 bits.
                //
                using (FileStream fs = new FileStream(Path.Combine("ROM", file.Filename), FileMode.Open, FileAccess.Read))
                {
                    int length = (int)fs.Length;
                    if (length != 256)
                    {
                        throw new InvalidOperationException("ROM file should be 256 bytes in length");
                    }

                    byte[] data = new byte[fs.Length];
                    fs.Read(data, 0, (int)fs.Length);

                    // OR in the data
                    for (int i = 0; i < length; i++)
                    {
                        _constantRom[file.StartingAddress + i] |= (ushort)((DataMapConstantRom(data[AddressMapConstantRom(i)]) & 0xf) << file.BitPosition);
                    }
                }
            }

            // And invert all bits
            for (int i = 0; i < _constantRom.Length; i++)
            {
               _constantRom[i] = (ushort)((~_constantRom[i]) & 0xffff);
            } 
        }

        private static void LoadACSource(RomFile romInfo)
        {
            _acSourceRom = new byte[256];
            
            using (FileStream fs = new FileStream(Path.Combine("ROM", romInfo.Filename), FileMode.Open, FileAccess.Read))
            {
                int length = (int)fs.Length;
                if (length != 256)
                {
                    throw new InvalidOperationException("ROM file should be 256 bytes in length");
                }
                byte[] data = new byte[fs.Length];                
                fs.Read(data, 0, (int)fs.Length);

                // Copy in the data, modifying the address as required.
                for (int i = 0; i < length; i++)
                {
                    _acSourceRom[i] = (byte)((~data[AddressMapACSourceRom(i)]) & 0xf);
                }
            }
        }

        private static int AddressMapConstantRom(int address)
        {            
            // Descramble the address bits as they are in no sane order.    
            // (See 05a_AIM.pdf, pg. 5 (Page 9 of the orginal docs))
            int[] addressMapping = { 7, 2, 1, 0, 3, 4, 5, 6 };            
  
            int mappedAddress = 0;

            for (int i = 0; i < addressMapping.Length; i++)
            {
                if ((address & (1 << i)) != 0)
                {
                    mappedAddress |= (1 << (addressMapping[i]));
                }
            }            
            return mappedAddress;
        }

        private static int DataMapConstantRom(int data)
        {            
            // Reverse bits 0-4.
            int mappedData = 0;

            for (int i = 0; i < 4; i++)
            {
                if ((data & (1 << i)) != 0)
                {
                    mappedData |= (1 << (3-i));
                }
            }

            return mappedData;
        }

        private static int AddressMapACSourceRom(int data)
        {
            // Reverse bits 0-7.
            int mappedData = 0;

            for (int i = 0; i < 8; i++)
            {
                if ((data & (1 << i)) != 0)
                {
                    mappedData |= (1 << (7 - i));
                }
            }

            // And invert data lines
            return (~mappedData) & 0xff;
        }

        private static RomFile[] _constantRoms =
            {               
                new RomFile("c0", 0x000, 12),
                new RomFile("c1", 0x000, 8),
                new RomFile("c2", 0x000, 4),
                new RomFile("c3", 0x000, 0),                
            };

        private static RomFile _acSourceRoms = new RomFile("2kctl.u3", 0x000, 0);

        private static ushort[] _constantRom;
        private static byte[] _acSourceRom;
    }
}
