using Net.Pkcs11Interop.Common;
using Net.Pkcs11Interop.HighLevelAPI;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using IniParser;
using IniParser.Model;
using System.Windows.Forms;

namespace PkcsApp
{
    class Pkcs11Manager : IDisposable
    {
        private Pkcs11 pkcs11 = null;
        private static string pkcs11LibraryPath = "";

        private static Pkcs11Manager instance = null;

        private Pkcs11Manager()
        {
            var ini = new FileIniDataParser();
            


            try
            {
                IniData data = ini.ReadFile(Config.configFile);
                pkcs11LibraryPath = data["Module"]["path"];
            }

            catch (Exception e)
            {
                MessageBox.Show("Could not load path to the Module");
                Environment.Exit(-1);
            }

            pkcs11 = new Pkcs11(pkcs11LibraryPath, AppType.MultiThreaded);
        }


        public static Pkcs11Manager getInstance()
        {
            if (instance != null)
                return instance;
            instance = new Pkcs11Manager();
            return instance;
        }




        public byte[] signZEP(byte[] toSign)
        {
            throw new NotImplementedException();
        }

        public byte[] signEP(byte[] toSign, byte[] key_id)
        {
            byte[] signature = null;


            foreach (Slot slot in pkcs11.GetSlotList(SlotsType.WithTokenPresent))
            {
                using (Session session = slot.OpenSession(SessionType.ReadOnly))
                {
                    byte[] passwd = null;
                    session.Login(CKU.CKU_USER, passwd);

                    List<ObjectAttribute> key_objectAttributes = new List<ObjectAttribute>();
                    key_objectAttributes.Add(new ObjectAttribute(CKA.CKA_CLASS, CKO.CKO_PRIVATE_KEY));
                    key_objectAttributes.Add(new ObjectAttribute(CKA.CKA_TOKEN, true));
                    key_objectAttributes.Add(new ObjectAttribute(CKA.CKA_SIGN, true));
                    key_objectAttributes.Add(new ObjectAttribute(CKA.CKA_ID, key_id));

                    session.FindObjectsInit(key_objectAttributes);
                    List<ObjectHandle> privateKeys = session.FindObjects(2);
                    session.FindObjectsFinal();

                    if (privateKeys.Count < 1)
                    {
                        session.Logout();
                        continue;
                    }



                    ObjectHandle key = privateKeys[0];

                    Mechanism mechanism = new Mechanism(CKM.CKM_RSA_PKCS);

                    using (MemoryStream inputStream = new MemoryStream(toSign))
                    {
                        signature = session.Sign(mechanism, key, inputStream);
                    }

                    session.Logout();
                }
            }
            return signature;
        }

        public Dictionary<string, List<Certificate>> getCertificates()
        {
            Dictionary<string, List<Certificate>> ret_certs = new Dictionary<string, List<Certificate>>();
            foreach (Slot slot in pkcs11.GetSlotList(SlotsType.WithTokenPresent))
            {
                TokenInfo tokenInfo = slot.GetTokenInfo();
                var tokenLabel = tokenInfo.Label;
                ret_certs.Add(tokenLabel, new List<Certificate>());
                using (Session session = slot.OpenSession(SessionType.ReadOnly))
                {
                    byte[] passwd = null;
                    session.Login(CKU.CKU_USER, passwd);

                    List<ObjectAttribute> cert_objectAttributes = new List<ObjectAttribute>();
                    cert_objectAttributes.Add(new ObjectAttribute(CKA.CKA_CLASS, CKO.CKO_CERTIFICATE));
                    cert_objectAttributes.Add(new ObjectAttribute(CKA.CKA_TOKEN, true));



                    session.FindObjectsInit(cert_objectAttributes); // Initialize
                    List<ObjectHandle> certs = session.FindObjects(2); //The maximum number of certificates in one token is 2
                    session.FindObjectsFinal();

                    if (certs.Count < 1)
                    {
                        session.Logout();
                        throw new FormatException();
                    }

                    List<CKA> certs_attributes = new List<CKA>();
                    certs_attributes.Add(CKA.CKA_VALUE);
                    certs_attributes.Add(CKA.CKA_LABEL);
                    certs_attributes.Add(CKA.CKA_ID);
                    

                    foreach (ObjectHandle cert in certs)
                    {
                        cert_objectAttributes = session.GetAttributeValue(cert, certs_attributes);
                        var x509 = new Certificate(cert_objectAttributes[0].GetValueAsByteArray(),
                            tokenLabel, 
                            cert_objectAttributes[1].GetValueAsString(), 
                            cert_objectAttributes[2].GetValueAsByteArray());
                        ret_certs[tokenLabel].Add(x509);
                    }

                    session.Logout();

                }

            }
            return ret_certs;
        }


        public Certificate getTokenSignCertificate(string token)
        {
            var certs = getCertificates();
            return certs[token].Where(x => x.sign).First();
        }




        public void Dispose()
        {
            if (pkcs11 != null)
            {
                pkcs11.Dispose();
            }
        }
    }
}
