/// <summary>
        /// Check activation key file for valid licence key or trial period value
        /// </summary>
        /// <param name="activationKeyFileFullPath">The absolute local path where the activation key file exists</param>
        /// <param name="keyByteSet2"> </param>
        /// <param name="licenceKeyFileEncryptionString">An encryption key which the licence key XML will be decrypted with</param>
        /// <param name="friendlyApplicationName">A friendly application name for error reporting e.g. 'My Licenced Application'</param>
        /// <param name="keyByteSet1"> </param>
        public void CheckActivationKeyFile(string activationKeyFileFullPath, KeyByteSet keyByteSet1, KeyByteSet keyByteSet2, string licenceKeyFileEncryptionString, string friendlyApplicationName)
        {
            if (!File.Exists(activationKeyFileFullPath))
            {
                throw new ActivationKeyFileNotPresentException("The product activation key file '" + activationKeyFileFullPath + "' was expected at '" + activationKeyFileFullPath + "', but was not found. Please visit the vendor website to obtain a product activation key file.");
            }

            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.Load(activationKeyFileFullPath);

            XmlNodeList activationCodeEncrypted = xmlDoc.GetElementsByTagName("Key");

            var activationKeyEncrypted = activationCodeEncrypted[0].InnerText;

            if (!String.IsNullOrEmpty(activationKeyEncrypted))
            {
                string clearTextActivationKey = String.Empty;

                try
                {
                    clearTextActivationKey = ActivationKeyDecryption.Decrypt(activationKeyEncrypted, licenceKeyFileEncryptionString);
                }
                catch
                {
                    throw new ActivationKeyInvalidException(string.Format("The licence key that allows {0} to run is invalid. Please check that the key file exists in the executing directory, and has not been modified.", friendlyApplicationName));
                }

                // If a check is made, that means that the free trial build period has expired

                if(clearTextActivationKey.ToLower() == "trial")
                {
                    throw new TrialPeriodExpiredException(string.Format("The trial period for {0} has expired.", friendlyApplicationName));
                }

                var pkvLicenceKeyResult = new PkvKeyCheck().CheckKey(clearTextActivationKey, new [] { keyByteSet1, keyByteSet2 }, 8, null);

                if (pkvLicenceKeyResult != PkvLicenceKeyResult.KeyGood)
                {
                    throw new ActivationKeyInvalidException(string.Format("The licence key that allows {0} to run is invalid. Please check that the key file exists in the executing directory, and has not been modified.", friendlyApplicationName));
                }
            }
        }
        /// <summary>
        /// Generate a new key given a seed value. This seed should be unique so that where licences are blacklisted, 
        /// we only blacklist one key. Store the seed when generating new licences, or put in place some other mechanism so that
        /// the key will not be repeated for the same application. This seed does not necessarily have to be randomised.
        /// </summary>
        /// <param name="seed">Random number</param>
        /// <param name="keyByteSets">A list of key bytes that will be used to produce the key</param>
        /// <returns></returns>
        public string MakeKey(int seed, KeyByteSet[] keyByteSets)
        {
            if (keyByteSets.Length < 2)
            {
                throw new InvalidOperationException("The KeyByteSet array must be of length 2 or greater.");
            }

            // Check that array is in correct order as this will cause errors if passed in incorrectly

            Array.Sort(keyByteSets, new KeyByteSetComparer());

            bool allKeyByteNosDistinct = true;

            var keyByteCheckedNos = new List<int>();

            int maxKeyByteNo = 0;

            foreach (var keyByteSet in keyByteSets)
            {
                if(!(keyByteCheckedNos.Contains(keyByteSet.KeyByteNo)))
                {
                    keyByteCheckedNos.Add(keyByteSet.KeyByteNo);

                    if(keyByteSet.KeyByteNo > maxKeyByteNo)
                    {
                        maxKeyByteNo = keyByteSet.KeyByteNo;
                    }
                }
                else
                {
                    allKeyByteNosDistinct = false;
                    break;
                }
            }

            if(!allKeyByteNosDistinct)
            {
                throw new InvalidOperationException("The KeyByteSet array contained at least 1 item with a duplicate KeyByteNo value.");
            }

            if(maxKeyByteNo != keyByteSets.Length)
            {
                throw new InvalidOperationException("The values for KeyByteNo in each KeyByteSet item must be sequential and start with the number 1.");
            }

            // Note these seed value, with random numbers need to be repeated in check function.
            // The more of these values the better, but they will also increase the length of the
            // key by 2 chars each. Changing the length of the key is not something to be done
            // without testing, since some operations depend on certain portions of the key
            // being found at specific indexes.

            var keyBytes = new byte[keyByteSets.Length];

            for(int i = 0; i < keyByteSets.Length; i++)
            {
                keyBytes[i] = GetKeyByte(
                                    seed, 
                                    keyByteSets[i].KeyByteA, 
                                    keyByteSets[i].KeyByteB, 
                                    keyByteSets[i].KeyByteC
                              );
            }

            // The key string begins with a hexidecimal string of the seed

            string result = seed.ToString("X8"); // 8 digit hex;

            for(int i = 0; i < keyBytes.Length; i++)
            {
                result = result + keyBytes[i].ToString("X2");
            }

            result = result + GetChecksum(result);

            // Insert hyphens every 6 chars for readability

            int startPos = 7;

            while (startPos < (result.Length - 1))
            {
                result = result.Insert(startPos, "-");

                startPos = startPos + 7;
            }

            return result;
        }
        public void Test_pkv_licence_key_generation_and_verification()
        {
            var pkvLicenceKey = new PkvLicenceKeyGenerator();

            var pkvKeyCheck = new PkvKeyCheck();

            string key = String.Empty;

            KeyByteSet[] keyByteSets = new []
                                               {
                                                   new KeyByteSet(1, 58, 6, 97),
                                                   new KeyByteSet(2, 96, 254, 23),
                                                   new KeyByteSet(3, 11, 185, 69), 
                                                   new KeyByteSet(4, 2, 93, 41),
                                                   new KeyByteSet(5, 62, 4, 234),
                                                   new KeyByteSet(6, 200, 56, 49), 
                                                   new KeyByteSet(7, 89, 45,142), 
                                                   new KeyByteSet(8, 6, 88, 32)
                                               };

            // Change these to a random key byte set from the above array to test key verification with

            KeyByteSet kbs1 = keyByteSets[3];
            KeyByteSet kbs2 = keyByteSets[7];
            KeyByteSet kbs3 = keyByteSets[4];

            // The check project also uses a class called KeyByteSet, but with
            // separate name spacing to achieve single self contained dll

            KeyByteSet keyByteSet1 = new KeyByteSet(kbs1.KeyByteNo, kbs1.KeyByteA, kbs1.KeyByteB, kbs1.KeyByteC); // Change no to test others
            KeyByteSet keyByteSet2 = new KeyByteSet(kbs2.KeyByteNo, kbs2.KeyByteA, kbs2.KeyByteB, kbs2.KeyByteC);
            KeyByteSet keyByteSet3 = new KeyByteSet(kbs3.KeyByteNo, kbs3.KeyByteA, kbs3.KeyByteB, kbs3.KeyByteC);

            for (int i = 0; i < 10000; i++)
            {
                int seed = new Random().Next(0, Int32.MaxValue);

                key = pkvLicenceKey.MakeKey(seed, keyByteSets);

                // Check that check sum validation passes

                Assert.True(pkvKeyCheck.CheckKeyChecksum(key, keyByteSets.Length));

                // Check using full check method

                Assert.True(pkvKeyCheck.CheckKey(
                                            key,
                                            new[] { keyByteSet1, keyByteSet2, keyByteSet3 },
                                            keyByteSets.Length,
                                            null
                                        ) == PkvLicenceKeyResult.KeyGood, "Failed on iteration " + i
                            );

                // Check that erroneous check sum validation fails

                Assert.False(pkvKeyCheck.CheckKeyChecksum(key.Remove(23, 1) + "A", keyByteSets.Length)); // Change key by replacing 17th char
            }

            // Check a few random inputs

            Assert.False(pkvKeyCheck.CheckKey("adcsadrewf",
                                            new[] { keyByteSet1, keyByteSet2 },
                                            keyByteSets.Length,
                                            null) == PkvLicenceKeyResult.KeyGood
                        );
            Assert.False(pkvKeyCheck.CheckKey("",
                                            new[] { keyByteSet1, keyByteSet2 },
                                            keyByteSets.Length,
                                            null) == PkvLicenceKeyResult.KeyGood
                        );
            Assert.False(pkvKeyCheck.CheckKey("123",
                                            new[] { keyByteSet1, keyByteSet2 },
                                            keyByteSets.Length,
                                            null) == PkvLicenceKeyResult.KeyGood
                        );
            Assert.False(pkvKeyCheck.CheckKey("*()",
                                            new[] { keyByteSet1, keyByteSet2 },
                                            keyByteSets.Length,
                                            null) == PkvLicenceKeyResult.KeyGood
                        );
            Assert.False(pkvKeyCheck.CheckKey("dasdasdasgdjwqidqiwd21887127eqwdaishxckjsabcxjkabskdcbq2e81y12e8712",
                                            new[] { keyByteSet1, keyByteSet2 },
                                            keyByteSets.Length,
                                            null) == PkvLicenceKeyResult.KeyGood
                        );
        }
        public void Test_PKV_licence_key_generation_and_verification_with_random_key_bytes_key_byte_qty_and_verification_key_byte_selection()
        {
            var pkvLicenceKey = new PkvLicenceKeyGenerator();

            var pkvKeyCheck = new PkvKeyCheck();

            for (int i = 0; i < 10000; i++)
            {
                int randomKeyByteSetsLength = new Random().Next(2, 400);

                KeyByteSet[] keyByteSets = new KeyByteSet[randomKeyByteSetsLength];

                for (int j = 0; j < randomKeyByteSetsLength; j++)
                {
                    var random = new Random();

                    var kbs = new KeyByteSet
                                  (
                                      j + 1,
                                      (byte)random.Next(0, 256),
                                      (byte)random.Next(0, 256),
                                      (byte)random.Next(0, 256)
                                  );

                    keyByteSets[j] = kbs;
                }

                // Select a random key byte set to test key verification with

                KeyByteSet kbs1 = keyByteSets[new Random().Next(0, randomKeyByteSetsLength)];
                KeyByteSet kbs2 = keyByteSets[new Random().Next(0, randomKeyByteSetsLength)];

                // The check project also uses a class called KeyByteSet, but with
                // separate name spacing to achieve single self contained dll

                KeyByteSet keyByteSet1 = new KeyByteSet(kbs1.KeyByteNo, kbs1.KeyByteA, kbs1.KeyByteB, kbs1.KeyByteC); // Change no to test others
                KeyByteSet keyByteSet2 = new KeyByteSet(kbs2.KeyByteNo, kbs2.KeyByteA, kbs2.KeyByteB, kbs2.KeyByteC);

                int seed = new Random().Next(0, Int32.MaxValue);

                string key = pkvLicenceKey.MakeKey(seed, keyByteSets);

                // Check that check sum validation passes

                Assert.True(pkvKeyCheck.CheckKeyChecksum(key, keyByteSets.Length));

                // Check using full check method

                Assert.True(pkvKeyCheck.CheckKey(
                                            key,
                                            new[] { keyByteSet1, keyByteSet2 },
                                            keyByteSets.Length,
                                            null
                                        ) == PkvLicenceKeyResult.KeyGood, "Failed on iteration " + i
                            );

            }
        }
        /// <summary>
        /// Check a given key for validity
        /// </summary>
        /// <param name="key">The full key</param>
        /// <param name="keyByteSetsToCheck">The KeyBytes that are to be tested in this check</param>
        /// <param name="totalKeyByteSets">The total number of KeyBytes used to make the key</param>
        /// <param name="blackListedSeeds">Any seed values (hex string representation) that should be banned</param>
        /// <returns></returns>
        public PkvLicenceKeyResult CheckKey(
            string key, 
            KeyByteSet[] keyByteSetsToCheck,
            int totalKeyByteSets, 
            string[] blackListedSeeds
        )
        {
            key = FormatKeyForCompare(key);

            PkvLicenceKeyResult result = PkvLicenceKeyResult.KeyInvalid;

            bool checksumPass = CheckKeyChecksum(key, totalKeyByteSets);

            if (checksumPass)
            {
                if (blackListedSeeds != null && blackListedSeeds.Length > 0)
                {
                    // Test key against our black list

                    // Example black listed seed: 111111 (Hex val). Producing keys with the same 
                    // seed and key bytes will produce the same key, so using a seed such as a user id
                    // can provide a mechanism for tracking the source of any keys that are found to
                    // be used out of licence terms.

                    for (int i = 0; i < blackListedSeeds.Length; i++)
                    {
                        if (key.StartsWith(blackListedSeeds[i]))
                        {
                            result = PkvLicenceKeyResult.KeyBlackListed;
                        }
                    }
                }

                if (result != PkvLicenceKeyResult.KeyBlackListed)
                {
                    // At this point, the key is either valid or forged,
                    // because a forged key can have a valid checksum.
                    // We now test the "bytes" of the key to determine if it is
                    // actually valid.

                    // When building your release application, use conditional defines
                    // or comment out most of the byte checks!  This is the heart
                    // of the partial key verification system. By not compiling in
                    // each check, there is no way for someone to build a keygen that
                    // will produce valid keys.  If an invalid keygen is released, you
                    // simply change which byte checks are compiled in, and any serial
                    // number built with the fake keygen no longer works.

                    // Note that the parameters used for PKV_GetKeyByte calls MUST
                    // MATCH the values that PKV_MakeKey uses to make the key in the
                    // first place!

                    result = PkvLicenceKeyResult.KeyPhoney;

                    int seed;

                    bool seedParsed = int.TryParse(key.Substring(0, 8), System.Globalization.NumberStyles.HexNumber, null, out seed);

                    if (seedParsed)
                    {
                        string keyBytes;
                        byte b;
                        int keySubstringStart;

                        // The using of conditional compilation for the key byte checks
                        // means that we define the portions of the key that are checked
                        // at runtime. The advantage of this is that if someone creates
                        // a keygen using this source code, we can change the key bytes that
                        // are checked, so subsequent releases will not work with keys generated by 
                        // the key generator.

                        foreach (var keyByteSet in keyByteSetsToCheck)
                        {
                            keySubstringStart = GetKeySubstringStart(keyByteSet.KeyByteNo);

                            if (keySubstringStart - 1 > key.Length)
                            {
                                throw new InvalidOperationException("The KeyByte check position is out of range. You may have specified a check KeyByteNo that did not exist in the original key generation.");
                            }
                            
                            keyBytes = key.Substring(keySubstringStart, 2);
                            b = GetKeyByte(seed, keyByteSet.KeyByteA, keyByteSet.KeyByteB, keyByteSet.KeyByteC);

                            if (keyBytes != b.ToString("X2"))
                            {
                                // If true, then it means the key is either good, or was made
                                // with a keygen derived from "this" release.

                                return result; // Return result in failed state 
                            }
                        }

                        result = PkvLicenceKeyResult.KeyGood;
                    }
                }
            }

            return result;
        }