using PCSC;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.Threading;
using Interfaces;

namespace CardReaderService
{
    public partial class CardReaderService : ServiceBase
    {
        private Thread _mainWorkThread;
        private bool _stopMainWorkerThread;

        private string _readerName = "";
        private SCardMonitor _cardMonitor;

        private ILogger _logger;

        public CardReaderService()
        {
            InitializeComponent();
        }

        public void Start()
        {
            OnStart(new string[] {});
        }

        protected override void OnStart(string[] args)
        {
            var configPath = string.Concat(System.Reflection.Assembly.GetEntryAssembly().Location, ".config");

            _logger = NinjectHelper.GetInstance().Get<ILogger>();
            _logger.Trace("Starting Service.. Getting available readers");

            var ctxFactory = ContextFactory.Instance;
            using(var context = ctxFactory.Establish(SCardScope.System))
            {
                var readerNames = context.GetReaders();
                if (NoReaderAvailable(readerNames))
                {
                    _logger.Trace("No Card Reader is available, Exiting..");
                    throw new ApplicationException("A card reader must be provided in order to operate.");
                }
                foreach (var reader in readerNames)
                {
                    _logger.Trace("Found reader: {0}", reader);
                }

                var readerNameConfig = ConfigurationHandler.ConfigurationHandler.GetConfiguration("ReaderName");
                if (string.IsNullOrEmpty(readerNameConfig))
                {
                    if (!readerNames.Contains(readerNameConfig))
                    {
                        _logger.Warn("No reader found with the name: {0}, defaulting to first available reader {1}",
                            readerNameConfig, readerNames.First());

                        readerNameConfig=readerNames.First();
                    }
                }
                _logger.Trace("Choosing reader: {0}", readerNameConfig);
                _readerName = readerNameConfig;

                _cardMonitor = new SCardMonitor(ctxFactory, SCardScope.System);
                _cardMonitor.CardInserted += _cardMonitor_CardInserted;
                _cardMonitor.Start(_readerName);

                StartWorkerThread();
            }
        }

        public void StopService()
        {
            OnStop();
        }

        protected override void OnStop()
        {
            _stopMainWorkerThread = true;
            if (_mainWorkThread!= null && _mainWorkThread.IsAlive)
            {
                _mainWorkThread.Join(3000);
                if (_mainWorkThread.IsAlive)
                {
                    _mainWorkThread.Interrupt();
                } 
            }
            if (_cardMonitor == null) return;

            _cardMonitor.Cancel();
            _cardMonitor.Dispose();
            _cardMonitor = null;
        }

        private void StartWorkerThread()
        {
            _stopMainWorkerThread = false;
            _mainWorkThread = new Thread(MainWorkerThread)
            {
                Name = "CardServiceMainThread",
                IsBackground = false
            };
            _mainWorkThread.Start();
        }

        private void _cardMonitor_CardInserted(object sender, CardStatusEventArgs e)
        {
            var ctxFac = ContextFactory.Instance;
            using (var ctx = ctxFac.Establish(SCardScope.System))
            {
                var reader = new SCardReader(ctx);
                var pioSendPci = new IntPtr();
                var rcvBuffer = new byte[256];

                if (_readerName == string.Empty)
                {
                    _logger.Fatal("Reader name is somehow empty...exiting");
                    _stopMainWorkerThread = true;
                    return;
                }
                
                var err = reader.Connect(_readerName, SCardShareMode.Shared, SCardProtocol.T0 | SCardProtocol.T1);

                if (err == SCardError.Success)
                {
                    var uIdcmd = new byte[] { 0xFF, 0xCA, 0x00, 0x00, 0x00 };

                    err = reader.Transmit(pioSendPci, uIdcmd, ref rcvBuffer);
                    if (err != SCardError.Success) return;

                    var uid = ConvertByteUIDToString(rcvBuffer);
                    var atrString = ConvertByteUIDToString(e.Atr);
                    _logger.Trace("Card Inserted, ATR: " + atrString + ", and UID is: " + uid);

                    var postObj = new CardDataPost {CardUId = uid};
                    DataCenterHelper.PostAsync(postObj, "/api/swipedata");
                    _logger.Trace("Posted to Server");
                }
                else
                {
                    _logger.Trace("Reader failed to connect, error: " + Enum.GetName(typeof(SCardError), err));
                }
                reader.Dispose();
            }
        }
        
        private void MainWorkerThread()
        {
            while (!_stopMainWorkerThread)
            {
                //dont actually need to do anything.. but cannot exit right away?
                Thread.Sleep(3000);
            }
        }

        private string ConvertByteUIDToString(byte[] uid)
        {
            var sb = new StringBuilder();
            foreach(var b in uid)
            {
                sb.AppendFormat("{0:X2}", b);
            }
            return sb.ToString();
        }

        private bool NoReaderAvailable(ICollection<string> readerNames)
        {
            return readerNames == null || readerNames.Count < 1;
        }
    }
}
