﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using GoToTags.Nfc;
using GoToTags.Nfc.Ndef;
using GoToTags.Nfc.Util;
using GoToTags.Platform;
using GoToTags.Nfc.Records;
using GoToTags.Nfc.Reader;

namespace GoToTags.Platform.Nfc
{   
    /// <summary>
    /// Integrates NFC tag functionality into the Platform using the GoToTags .NET NFC SDK.
    /// </summary>
    public sealed class NfcController : IDisposable
    {
        static NfcController()
        {
            Record.RegisterRecordType(new RecordType("GoToTagsNfcTag", "GoToTags Nfc Tag", typeof(NfcTagRecord), int.MaxValue, NfcTagRecord.ToNfcTagRecord));
        }

        ///// <summary>
        ///// This method is used when this library is compiled by GoToTags with an embedded license code fot the .NET NFC SDK.
        ///// </summary>
        ///// <param name="platformClient"></param>
        //public NfcController(Client platformClient)
        //    : this(platformClient, "")
        //{
        //}

        public NfcController(Client platformClient, string licenceCode)
        {
            if (platformClient == null)
                throw new ArgumentNullException("The Platform client must not be null.");

            if (string.IsNullOrWhiteSpace(licenceCode))
                throw new ArgumentNullException("A license code must be provided for the GoToTags .NET NFC SDK.");

            // unlock the GoToTags .NET NFC SDK
            // http://www.gototags.com/docs/display/NETNFCSDK
            Licensing.Unlock(licenceCode);

            this.PlatformClient = platformClient;

            this.StopCertPropSvc();

            this.NfcAdapter = NfcAdapter.DefaultAdapter;
        }

        private Client PlatformClient { get; set; }

        #region NfcAdpater

        private NfcAdapter nfcAdapter;

        private NfcAdapter NfcAdapter
        {
            get { return this.nfcAdapter; }
            set
            {
                this.nfcAdapter = value;

                if (this.nfcAdapter != null && !string.IsNullOrWhiteSpace(this.nfcAdapter.Reader))
                {
                    this.nfcAdapter.ReaderPollInterval = TimeSpan.FromMilliseconds(100);
                    this.nfcAdapter.StatusChanged += NfcAdapter_StatusChanged;
                }
            }
        }

        #endregion

        #region Tag

        public Device CreateWriteNfcTag(Device device = null, bool makeReadOnly = true)
        {
            var createdDevice = this.CreateNfcTag(device);

            if (createdDevice == null)
                throw new NullReferenceException("The device must not be null.");

            try
            {
                this.EncodeNfcTag(createdDevice, makeReadOnly);
            }
            catch (Exception ex)
            {
                if (createdDevice != null)
                    this.PlatformClient.DeleteDevice(createdDevice.Id);

                throw ex;
            }

            return createdDevice;
        }

        public Device CreateNfcTag(Device device = null)
        {
            if (device == null)
                device = new Device();

            if (!string.IsNullOrWhiteSpace(device.Id))
                throw new ArgumentException("The device must be a new device.");

            var nfcTag = this.GetNfcTag();
            this.CheckForNfcTag(nfcTag);

            using (var ndef = NdefTechnology.Get(nfcTag))
            {
                ndef.Connect();

                var nfcTagUid = ndef.TagUIDString();
                device.Uid = nfcTagUid;

                if (string.IsNullOrWhiteSpace(device.Identity))
                    device.Identity = nfcTagUid;

                device.DeviceType = DeviceType.Tag;
                device.TagType = TagType.Nfc;

                // tags can not have these properties
                device.Browser = null;
                device.Os = null;

                device.Hardware = new DeviceHardware()
                {
                    Name = nfcTag.TagType.GetDescription()
                };

                device = this.PlatformClient.CreateDevice(device);
            }

            return device;
        }

        public PlatformNfcTag EncodeNfcTag(Device device, bool makeReadOnly = true)
        {
            var nfcTag = this.GetNfcTag();
            this.CheckForNfcTag(nfcTag);

            var platformNfcTag = new PlatformNfcTag();

            using (var ndef = NdefTechnology.Get(nfcTag))
            {
                ndef.Connect();

                if (!ndef.IsWritable)
                    throw new ApplicationException("The nfc tag is not writable.");

                platformNfcTag.Uid = ndef.TagUIDString();

                var nfcTagRecord = new NfcTagRecord(device);
                ndef.WriteNdefMessage(nfcTagRecord);

                platformNfcTag.Record = nfcTagRecord;

                if (makeReadOnly && ndef.CanMakeReadOnly)
                {
                    ndef.MakeReadOnly();
                    platformNfcTag.ReadOnly = true;
                }
                else
                {
                    platformNfcTag.ReadOnly = false;
                }
            }

            return platformNfcTag;
        }

        #endregion

        #region Readers

        public void UseNfcReader(string reader)
        {
            this.NfcAdapter = NfcAdapter.Adapter(reader);
        }

        public string GetNfcReader()
        {
            if (this.HasNfcReader())
                return this.NfcAdapter.Reader;
            else
                return null;
        }

        public List<string> GetNfcReaders(bool scanSerialPorts = false)
        {
            return this.NfcAdapter.SupportedReaders(scanSerialPorts).ToList();
        }

        public bool HasNfcReader()
        {
            return this.NfcAdapter != null;
        }

        private void CheckForNfcReader()
        {
            if (!this.HasNfcReader())
                throw new ApplicationException("An NFC reader is not available.");
        }

        #endregion

        #region Nfc

        public PlatformNfcTag ReadPlatformNfcTag()
        {
            var nfcTag = this.GetNfcTag();
            this.CheckForNfcTag(nfcTag);

            using (var ndef = NdefTechnology.Get(nfcTag))
            {
                ndef.Connect();

                var message = ndef.GetNdefMessage();

                if (message != null && message.Length > 0)
                {
                    var ndefRecord = message.Records[0];

                    var nfcTagRecord = NfcTagRecord.ToNfcTagRecord(ndefRecord) as NfcTagRecord;

                    var platformNfcTag = new PlatformNfcTag()
                    {
                        Uid = ndef.TagUIDString(), 
                        ReadOnly = !ndef.IsWritable, 
                        Record = nfcTagRecord
                    };

                    return platformNfcTag;
                }
            }

            return null;
        }

        public bool HasNfcTag()
        {
            this.CheckForNfcReader();

            return this.GetNfcTag() != null;
        }

        private void CheckForNfcTag(Tag nfcTag)
        {
            if (nfcTag == null)
                throw new ArgumentNullException("The NFC tag must not be null.");
        }

        private Tag GetNfcTag()
        {
            if (this.HasNfcReader())
                return this.NfcAdapter.GetTag();
            else
                return null;
        }

        #endregion

        #region CertPropSvc

        private void StopCertPropSvc()
        {
            try
            {
                if (CertPropSvc.IsRunning())
                    CertPropSvc.StopService();
            }
            catch (Exception)
            {
            }
        }

        #endregion

        #region Events

        public event EventHandler<NfcTagStatusChangedEventArgs> NfcTagStatusChanged;

        private void NfcAdapter_StatusChanged(object sender, StatusChangedEventArgs statusChangedEventArgs)
        {
            if (this.NfcTagStatusChanged != null)
            {
                this.NfcTagStatusChanged(this, new NfcTagStatusChangedEventArgs() { HasTag = statusChangedEventArgs.IsTagPresent });
            }
        }

        #endregion

        public void Dispose()
        {
            try
            {
                if (this.NfcAdapter != null)
                    this.NfcAdapter.Dispose();
            }
            catch (Exception)
            { }
            finally
            {
                this.NfcAdapter = null;
                this.PlatformClient = null;
            }
        }
    }
}
