////////////////////////////////////////////////////////////////////////////////
//
//  Microsoft Research Singularity
//
//  Copyright (c) Microsoft Corporation.  All rights reserved.
//

using Microsoft.Singularity.Channels;
using Microsoft.Singularity.Io;
using Microsoft.Singularity.Configuration;
using Microsoft.Singularity.Hal.Acpi;

using System;
using System.Collections;
using System.Diagnostics;
using System.Text;

namespace Microsoft.Singularity.Drivers
{
    [CLSCompliant(false)]
    public class AcpiBus : IBusDevice
    {
        [DriverCategory]
        [Signature("/root/acpi0")]
        [EnumeratesDevice("/acpi/...")]
        class MyConfig : DriverCategoryDeclaration
        {
        }

        private AcpiDevice[]! deviceInfoEntries;
        delegate IoRange IoRangeTransformer(IoRange);

        public AcpiBus(AcpiDevice[]! deviceInfoEntries)
        {
            this.deviceInfoEntries = deviceInfoEntries;
        }

        private static IoRange ProcessExceptions(string deviceId, IoRange resource) {
            // Some Dells like the Dell Precision 490 make the HPET range
            // read-only - but it needs to be read-write.
            if (deviceId == "/pnp/PNP0103" && resource is IoMemoryRange &&
                !((IoMemoryRange)resource).Writable) {
                IoMemoryRange resourceMemoryRange = (IoMemoryRange)resource;
                return new IoMemoryRange(resourceMemoryRange.RangeBase, resourceMemoryRange.RangeLength,
                                         true/*readable*/, true/*writable*/);
            }
            else {
                return resource;
            }
        }

        public void Initialize()
        {
        }

        public void Finalize()
        {
        }

        public PnpConfig! ReportConfig()
        {
            // TODO: What resources does the root ACPI "bus" have? Any?
            return new PnpConfig(new string[] { "" }, new ArrayList());
        }

        private IoConfig! ResourceDescriptorsToIoConfig(string deviceId, ResourceDescriptor[] resourceDescriptors)
        {
            ArrayList resourceList = new ArrayList();
            string[]! ids = new string[1];
            ids[0] = deviceId;

            foreach (ResourceDescriptor resourceDescriptor in resourceDescriptors) {
                if (resourceDescriptor is MemoryRangeDescriptor) {
                    MemoryRangeDescriptor descriptor = (MemoryRangeDescriptor)resourceDescriptor;
                    if (descriptor.ConsumerProducer == ConsumerProducer.Consumes) {
                        // The resource can take on any base address in the range descriptor.Minimum -
                        // descriptor.Maximum. Just use the minimum for now.
                        resourceList.Add(new IoMemoryRange(descriptor.Minimum, descriptor.Length,
                                                           true/*readable*/, descriptor.Writable));
                    }
                }
                else if (resourceDescriptor is IoRangeDescriptor) {
                    IoRangeDescriptor descriptor = (IoRangeDescriptor)resourceDescriptor;
                    if (descriptor.ConsumerProducer == ConsumerProducer.Consumes) {
                        // The resource can take on any base address in the range descriptor.Minimum -
                        // descriptor.Maximum. Just use the minimum for now. We use Access.ReadWrite
                        // since ACPI supplies no information about whether it's writable.
                        resourceList.Add(new IoPortRange((ushort)descriptor.Minimum, (ushort)descriptor.Length,
                                                         Access.ReadWrite));
                    }
                }
                else if (resourceDescriptor is IrqDescriptor) {
                    IrqDescriptor descriptor = (IrqDescriptor)resourceDescriptor;
                    foreach (int interruptNumber in descriptor.InterruptNumbers) {
                        resourceList.Add(new IoIrqRange((byte)interruptNumber, 1));
                    }
                }
                else if (resourceDescriptor is DmaDescriptor) {
                    DmaDescriptor descriptor = (DmaDescriptor)resourceDescriptor;
                    foreach (int dmaChannelNumber in descriptor.DmaChannelNumbers) {
                        resourceList.Add(new IoDmaRange((byte)dmaChannelNumber, 1));
                    }
                }
            }

            ArrayList processedResourceList = new ArrayList();
            foreach (object obj in resourceList) {
                IoRange newResource = (IoRange)ProcessExceptions(deviceId, (IoRange)obj);
                if (newResource != null) {
                    processedResourceList.Add(newResource);
                }
            }

            return new PnpConfig(ids, processedResourceList);
        }

        public SortedList Enumerate()
        {
            SortedList found = new SortedList();

            int node = 0x100;

            foreach (AcpiDevice! deviceInfo in deviceInfoEntries) {
                IoConfig! ioConfig = ResourceDescriptorsToIoConfig(deviceInfo.DeviceId,
                                         (!)deviceInfo.ResourceDescriptors);

                DebugStub.Write("Detected ACPI device ");
                DebugStub.WriteLine(ioConfig.Id);
                System.Text.StringBuilder descriptionBuilder = new System.Text.StringBuilder();
                ioConfig.DumpRanges(descriptionBuilder);
                DebugStub.WriteLine(descriptionBuilder.ToString());

                found.Add(String.Format("/{0,3:x3}", node++), ioConfig);
            }

            return found;
        }
    }
}
