using System;
using System.Collections.Generic;
using System.Text;
using LibUsbDotNet;
using LibUsbDotNet.Main;

namespace Salamax
{
  public class SilverlinkCable : USBDevice
  {
    private UsbDevice _device = null;
    private UsbEndpointReader _reader = null;
    private UsbEndpointWriter _writer = null;
    private Queue<KeyValuePair<byte, byte[]>> _packets = new Queue<KeyValuePair<byte, byte[]>>();
    private List<byte> _packetBuffer = new List<byte>();

    public event EventHandler<EventArgs> ReceivedStaticDataRequest;
    public event EventHandler<ChallengeEventArgs> ReceivedChallenge1;
    public event EventHandler<ChallengeEventArgs> ReceivedChallenge2;

    public override bool IsConnected
    {
      get
      {
        return (_device != null && _device.IsOpen);
      }
    }

    public override void Find()
    {
      bool wasValid = (_device != null && _device.IsOpen);

      //Find the silverlink cable
      bool found = _FindDevice();

      if (found)
      {
        if (wasValid)
          _reader.Device.Close();

        if (!wasValid)
        {
          Logger.WriteLine("Silverlink cable found, initializing...", Logger.Type.Information);

          //Set configuration 1
          int transferred;
          var setup = new UsbSetupPacket(0x00, 0x09, 0x0001, 0x0000, 0x0000);
          if (!_device.ControlTransfer(ref setup, IntPtr.Zero, 0, out transferred))
            Logger.WriteLine("Failed to set silverlink cable configuration.", Logger.Type.Error);

          //Set up the incoming and outgoing bulk pipes
          _reader = _device.OpenEndpointReader(ReadEndpointID.Ep01, 0x40, EndpointType.Bulk);
          _reader.DataReceivedEnabled = true;
          _reader.DataReceived += _reader_DataReceived;
          _writer = _device.OpenEndpointWriter(WriteEndpointID.Ep02, EndpointType.Bulk);

          Logger.WriteLine("Silverlink cable initialized.", Logger.Type.Information);
        }
      }
      else
        if (_device != null)
        {
          _device = null;
          Logger.WriteLine("Silverlink cable disconnected.", Logger.Type.Warning);
        }
    }

    public override void Disconnect()
    {
      if (_device != null && _device.IsOpen)
      {
        _device.Close();
      }
    }

    public int ForwardData(byte type, byte[] data, int length)
    {
      var buffer = new byte[length + 3];
      int ret;

      buffer[0] = type;
      buffer[1] = (byte)(length & 0xFF);
      buffer[2] = (byte)((length >> 8) & 0xFF);
      for (int i = 0; i < length; i++)
        buffer[i + 3] = data[i];

      Logger.WriteLine("Forwarding data to calculator...", Logger.Type.Information);
      if (_writer.Transfer(buffer, 0, buffer.Length, 5000, out ret) == ErrorCode.Success)
      {
        string msg = "Data forwarded to calculator: ";
        for (int i = 0; i < length; i++)
          msg += data[i].ToString("X2") + " ";
        if (length + 3 != ret)
          Logger.WriteLine(String.Format("Sent {0} bytes, but only {1} bytes transferred!", length + 3, ret), Logger.Type.Warning);
        Logger.WriteLine(msg, Logger.Type.Information);
      }
      else
        Logger.WriteLine("Forwarding data failed.", Logger.Type.Error);

      return ret;
    }

    private bool _FindDevice()
    {
      bool ret = false;

      foreach (object reg in LibUsbDotNet.LibUsb.LibUsbRegistry.DeviceList)
      {
        if (reg.GetType() == typeof(LibUsbDotNet.LibUsb.LibUsbRegistry))
        {
          var usb = reg as LibUsbDotNet.LibUsb.LibUsbRegistry;

          if (usb.Vid == 0x0451 && usb.Pid == 0xE001)
          {
            _device = usb.Device;
            ret = true;
            break;
          }
        }
      }

      return ret;
    }

    private void _reader_DataReceived(object sender, EndpointDataEventArgs e)
    {
      string msg = "Raw received data from calculator: ";
      for (int i = 0; i < e.Count; i++)
      {
        _packetBuffer.Add(e.Buffer[i]);
        msg += e.Buffer[i].ToString("X2") + " ";
      }
      Logger.WriteLine(msg, Logger.Type.Information);

      if (_packetBuffer.Count >= 3)
      {
        //We might have a complete packet, check the length
        int length = _packetBuffer[1] | (_packetBuffer[2] << 8);

        if (_packetBuffer.Count >= (length + 3))
        {
          //We have a complete packet, rip it out

          //First rip out the header
          byte type = _packetBuffer[0];
          _packetBuffer.RemoveAt(0);
          _packetBuffer.RemoveAt(0);
          _packetBuffer.RemoveAt(0);

          //Now get the data out
          var data = new byte[length];
          for (int i = 0; i < length; i++)
          {
            data[i] = _packetBuffer[0];
            _packetBuffer.RemoveAt(0);
          }

          //Save this in our queue of packets to process
          lock (_packets)
          {
            _packets.Enqueue(new KeyValuePair<byte, byte[]>(type, data));
          }

          _HandlePackets();
        }
      }
    }

    private void _HandlePackets()
    {
      KeyValuePair<byte, byte[]> packet = new KeyValuePair<byte, byte[]>();
      bool retrieved = false;

      lock (_packets)
      {
        if (_packets.Count > 0)
        {
          packet = _packets.Dequeue();
          retrieved = true;
        }
      }

      if (retrieved)
      {
        retrieved = false;

        //Display it!
        string msg = String.Format("Received calculator packet, type {0}, data: ", packet.Key.ToString("X2"));
        for (int i = 0; i < packet.Value.Length; i++)
          msg += packet.Value[i].ToString("X2") + " ";
        Logger.WriteLine(msg.Trim(), Logger.Type.Information);

        //Now handle it
        if (packet.Key == 0x81)
        {
          if (ReceivedStaticDataRequest != null)
            ReceivedStaticDataRequest(this, EventArgs.Empty);
        }
        else if (packet.Key == 0x82)
        {
          if (ReceivedChallenge1 != null)
            ReceivedChallenge1(this, new ChallengeEventArgs(packet.Value));
        }
        else if (packet.Key == 0x87)
        {
          if (ReceivedChallenge2 != null)
            ReceivedChallenge2(this, new ChallengeEventArgs(packet.Value));
        }
        else if (packet.Key == 0x90)
        {
          Logger.WriteLine("Calculator has been successfully initialized as an Xbox 360 controller.", Logger.Type.Information);
        }
      }
    }
  }
}
