/// <summary>
        /// Create new master fork with the data from an exsiting fork
        /// This is used to prune unused forks
        /// The data is trasferred to a new master fork to allow checking the data before deleting the old fork tree.
        /// </summary>
        /// <param name="forkId">Fork to prune</param>
        /// <returns>New master fork id with the data from the old fork</returns>
        public int PruneForks(int forkId)
        {
            var usedKeys    = new HashSet <string>();
            var deletedKeys = new HashSet <string>();
            var valuesToSet = new List <Tuple <string, TDataTypesEnum, byte[], object> >();

            var fork        = ForkProvider.GetFork(forkId);
            var currentFork = fork;

            while (currentFork != null)
            {
                var forkPattern = KeyGenerator.GenerateForkValuePattern(AppId, currentFork.Id);
                var keys        = KeyValueStore.Keys($"{forkPattern}*");

                foreach (var key in keys)
                {
                    var originalKey = key.Substring(forkPattern.Length);

                    // If the key was used/deleted in a lower fork, this key is not relevant
                    if (usedKeys.Contains(originalKey) || deletedKeys.Contains(originalKey))
                    {
                        continue;
                    }

                    if (originalKey.EndsWith(KeyGenerator.NullKeyPostFix))
                    {
                        deletedKeys.Add(originalKey.Substring(0, originalKey.Length - KeyGenerator.NullKeyPostFix.Length));
                    }
                    else
                    {
                        var keyDataCollection = KeyValueStore.GetKeyData(key);

                        foreach (var keyData in keyDataCollection)
                        {
                            valuesToSet.Add(Tuple.Create(originalKey, keyData.Item1, keyData.Item2, keyData.Item3));
                        }

                        usedKeys.Add(originalKey);
                    }
                }

                currentFork = currentFork.Parent;
            }

            var newForkId = CreateFork("master", $"Pruned from {fork.Id}:{fork.Name}");

            var wrapper = GetWrapper(newForkId);

            foreach (var value in valuesToSet)
            {
                wrapper.Set(value.Item2, value.Item1, value.Item3, value.Item4);
            }

            return(newForkId);
        }
        /// <summary>
        /// Merge all the keys from one fork to another fork, a new fork is created from the target with the new data
        /// </summary>
        /// <param name="originForkId">Origin fork id</param>
        /// <param name="targetForkId">Target fork id</param>
        /// <returns>New fork id, Target fork id is the parent</returns>
        public int MergeFork(int originForkId, int targetForkId)
        {
            var usedKeys     = new HashSet <string>();
            var valuesToSet  = new List <Tuple <string, TDataTypesEnum, byte[], object> >();
            var keysToDelete = new Dictionary <string, TDataTypesEnum>();

            var currentFork = ForkProvider.GetFork(originForkId);
            var targetFork  = ForkProvider.GetFork(targetForkId);

            while (!IsCommonParent(currentFork, targetFork))
            {
                if (currentFork == null)
                {
                    throw new ArgumentException($"No common parents between {originForkId} and {targetForkId}");
                }

                var forkPattern = KeyGenerator.GenerateForkValuePattern(AppId, currentFork.Id);
                var keys        = KeyValueStore.Keys($"{forkPattern}*");

                foreach (var key in keys)
                {
                    var originalKey = key.Substring(forkPattern.Length);

                    // If the key was used/deleted in a lower fork, this key is not relevant
                    if (usedKeys.Contains(originalKey) || keysToDelete.ContainsKey(originalKey))
                    {
                        continue;
                    }

                    if (originalKey.EndsWith(KeyGenerator.NullKeyPostFix))
                    {
                        var type = (TDataTypesEnum)BinarySerializerHelper.DeserializeObject(KeyValueStore.Get(KeyValueStore.DefaultType, key, null));
                        keysToDelete.Add(originalKey.Substring(0, originalKey.Length - KeyGenerator.NullKeyPostFix.Length), type);
                    }
                    else
                    {
                        var keyDataCollection = KeyValueStore.GetKeyData(key);

                        foreach (var keyData in keyDataCollection)
                        {
                            valuesToSet.Add(Tuple.Create(originalKey, keyData.Item1, keyData.Item2, keyData.Item3));
                        }

                        usedKeys.Add(originalKey);
                    }
                }

                currentFork = currentFork.Parent;
            }

            var newForkId = CreateFork($"{targetFork.Name} Merge", "", targetFork.Id);

            var wrapper = GetWrapper(newForkId);

            foreach (var keyToDelete in keysToDelete)
            {
                wrapper.Delete(keyToDelete.Value, keyToDelete.Key);
            }

            foreach (var value in valuesToSet)
            {
                wrapper.Set(value.Item2, value.Item1, value.Item3, value.Item4);
            }

            return(newForkId);
        }