#region CPL License
/*
Nuclex Framework
Copyright (C) 2002-2014 Nuclex Development Labs
This library is free software; you can redistribute it and/or
modify it under the terms of the IBM Common Public License as
published by the IBM Corporation; either version 1.0 of the
License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
IBM Common Public License for more details.
You should have received a copy of the IBM Common Public
License along with this library
*/
#endregion
#if UNITTEST && WINDOWS
using System;
using System.Collections.Generic;
using System.Globalization;
using Microsoft.Win32;
using NUnit.Framework;
namespace Nuclex.Support.Settings {
  /// Unit tests for the windows registry settings store
  [TestFixture]
  internal class WindowsRegistryStoreTest {
    #region class TestContext
    /// Sets up a temporary registry key for the unit test
    private class TestContext : IDisposable {
      /// Initializes a new test context
      public TestContext() {
        this.keyName = Guid.NewGuid().ToString();
        this.registryKey = Registry.CurrentUser.CreateSubKey(this.keyName);
        this.store = new WindowsRegistryStore(this.registryKey, writable: true);
      }
      /// Immediately frees all resources owned by the test context
      public void Dispose() {
        if(this.store != null) {
          this.store.Dispose();
          this.store = null;
          this.registryKey = null;
        } else if(this.registryKey != null) {
          this.registryKey.Dispose();
          this.registryKey = null;
        }
        if(this.keyName != null) {
          Registry.CurrentUser.DeleteSubKeyTree(this.keyName);
          this.keyName = null;
        }
      }
      /// Store created on a temporary registry key
      public WindowsRegistryStore Store {
        get { return this.store; }
      }
      /// Name of the temporary registry key
      private string keyName;
      /// Registry key (ownership transfered to the store)
      private RegistryKey registryKey;
      /// Store that is accessing the registry key
      private WindowsRegistryStore store;
    }
    #endregion // class TestContext
    /// Verifies that new instances of the registry store can be created
    [Test]
    public void CanBeCreated() {
      Assert.That(
        () => { using(var context = new TestContext()) { } }, Throws.Nothing
      );
    }
    /// Verifies that new instances of the registry store can be created
    [Test]
    public void RegistryHivesCanBeOpened() {
      Assert.That(
        () => {
          using(
            var store = new WindowsRegistryStore(
              RegistryHive.CurrentUser, "", writable: false
            )
          ) { }
        },
        Throws.Nothing
      );
    }
    /// Verifies that booleans can be stored in the registry
    [Test]
    public void BooleansCanBeStored() {
      using(var context = new TestContext()) {
        context.Store.Set(null, "test", true);
        Assert.That(context.Store.Get(null, "test"), Is.True);
        context.Store.Set(null, "test", false);
        Assert.That(context.Store.Get(null, "test"), Is.False);
      }
    }
    /// Verifies that integers can be stored in the registry
    [Test]
    public void IntegersCanBeStored() {
      using(var context = new TestContext()) {
        context.Store.Set(null, "test", 123);
        Assert.That(context.Store.Get(null, "test"), Is.EqualTo(123));
        context.Store.Set(null, "test", 456);
        Assert.That(context.Store.Get(null, "test"), Is.EqualTo(456));
      }
    }
    /// Verifies that floats can be stored in the registry
    [Test]
    public void FloatsCanBeStored() {
      float testValue = float.Parse("123.456", CultureInfo.InvariantCulture);
      using(var context = new TestContext()) {
        context.Store.Set(null, "test", testValue);
        Assert.That(context.Store.Get(null, "test"), Is.EqualTo(testValue));
        testValue = float.Parse("654.321", CultureInfo.InvariantCulture);
        context.Store.Set(null, "test", testValue);
        Assert.That(context.Store.Get(null, "test"), Is.EqualTo(testValue));
      }
    }
    /// Verifies that strings can be stored in the registry
    [Test]
    public void StringsCanBeStored() {
      using(var context = new TestContext()) {
        context.Store.Set(null, "test", "hello world");
        Assert.That(context.Store.Get(null, "test"), Is.EqualTo("hello world"));
        context.Store.Set(null, "test", "world hello");
        Assert.That(context.Store.Get(null, "test"), Is.EqualTo("world hello"));
      }
    }
    /// Verifies that long integers can be stored in the registry
    [Test]
    public void LongIntegersCanBeStored() {
      using(var context = new TestContext()) {
        context.Store.Set(null, "test", long.MaxValue);
        Assert.That(context.Store.Get(null, "test"), Is.EqualTo(long.MaxValue));
        context.Store.Set(null, "test", long.MinValue);
        Assert.That(context.Store.Get(null, "test"), Is.EqualTo(long.MinValue));
      }
    }
    /// Verifies that string arrays can be stored in the registry
    [Test]
    public void StringArraysCanBeStored() {
      string[] english = new string[] { "one", "two", "three" };
      string[] german = new string[] { "eins", "zwei", "drei" };
      using(var context = new TestContext()) {
        context.Store.Set(null, "test", english);
        Assert.That(context.Store.Get(null, "test"), Is.EquivalentTo(english));
        context.Store.Set(null, "test", german);
        Assert.That(context.Store.Get(null, "test"), Is.EquivalentTo(german));
      }
    }
    /// Verifies that byte arrays can be stored in the registry
    [Test]
    public void ByteArraysCanBeStored() {
      byte[] ascending = new byte[] { 1, 2, 3 };
      byte[] descending = new byte[] { 9, 8, 7 };
      using(var context = new TestContext()) {
        context.Store.Set(null, "test", ascending);
        Assert.That(context.Store.Get(null, "test"), Is.EquivalentTo(ascending));
        context.Store.Set(null, "test", descending);
        Assert.That(context.Store.Get(null, "test"), Is.EquivalentTo(descending));
      }
    }
    /// Verifies that strings can be stored in the registry
    [Test]
    public void ValuesCanBeStoredInCategories() {
      using(var context = new TestContext()) {
        context.Store.Set("main", "test", "hello world");
        string value;
        Assert.That(context.Store.TryGet(null, "test", out value), Is.False);
        Assert.That(context.Store.Get("main", "test"), Is.EqualTo("hello world"));
      }
    }
    /// Verifies that the subkeys of a registry key can be enumerated
    [Test]
    public void CategoriesCanBeEnumerated() {
      string[] names = new string[] { "one", "two", "three" };
      using(var context = new TestContext()) {
        context.Store.Set(names[0], "sol", 21);
        context.Store.Set(names[1], "sol", 42);
        context.Store.Set(names[2], "sol", 84);
        Assert.That(context.Store.EnumerateCategories(), Is.EquivalentTo(names));
      }
    }
    /// Verifies that the values under a registry subkey can be enumerated
    [Test]
    public void OptionsInCategoryCanBeEnumerated() {
      string[] names = new string[] { "one", "two", "three" };
      using(var context = new TestContext()) {
        context.Store.Set("test", names[0], 1);
        context.Store.Set("test", names[1], 2);
        context.Store.Set("test", names[2], 3);
        var optionInfos = new List(context.Store.EnumerateOptions("test"));
        Assert.That(optionInfos.Count, Is.EqualTo(3));
      }
    }
    /// Verifies that the values under a registry key can be enumerated
    [Test]
    public void RootOptionsCanBeEnumerated() {
      string[] names = new string[] { "one", "two", "three" };
      using(var context = new TestContext()) {
        context.Store.Set(null, names[0], 1);
        context.Store.Set(null, names[1], 2);
        context.Store.Set(null, names[2], 3);
        var optionInfos = new List(context.Store.EnumerateOptions(null));
        Assert.That(optionInfos.Count, Is.EqualTo(3));
        string[] actualNames = new string[] {
          optionInfos[0].Name, optionInfos[1].Name, optionInfos[2].Name
        };
        Assert.That(actualNames, Is.EquivalentTo(names));
      }
    }
    /// 
    ///   Verifies that accessing an option that doesn't exist throws an exception
    /// 
    [Test]
    public void AccessingNonExistingOptionThrowsException() {
      using(var context = new TestContext()) {
        Assert.That(
          () => context.Store.Get(null, "doesn't exist"),
          Throws.Exception.AssignableTo()
        );
      }
    }
    /// 
    ///   Verifies that accessing a category that doesn't exist throws an exception
    /// 
    [Test]
    public void AccessingNonExistingCategoryThrowsException() {
      using(var context = new TestContext()) {
        Assert.That(
          () => context.Store.Get("doesn't exist", "test"),
          Throws.Exception.AssignableTo()
        );
      }
    }
    /// 
    ///   Verifies that values can be removed from a registry key
    /// 
    [Test]
    public void ValuesCanBeRemovedFromRoot() {
      using(var context = new TestContext()) {
        context.Store.Set(null, "nothing", "short-lived");
        Assert.That(context.Store.Remove(null, "nothing"), Is.True);
        Assert.That(context.Store.Remove(null, "nothing"), Is.False);
        Assert.That(context.Store.EnumerateOptions(), Is.Empty);
      }
    }
    /// 
    ///   Verifies that values can be removed from the subkey of a registry key
    /// 
    [Test]
    public void ValuesCanBeRemovedFromCategory() {
      using(var context = new TestContext()) {
        context.Store.Set("limbo", "nothing", "short-lived");
        Assert.That(context.Store.Remove("limbo", "nothing"), Is.True);
        Assert.That(context.Store.Remove("limbo", "nothing"), Is.False);
        Assert.That(context.Store.EnumerateOptions("limbo"), Is.Empty);
      }
    }
    /// 
    ///   Verifies that values can be removed from a non-existing subkey without
    ///   causing an error
    /// 
    [Test]
    public void RemovingValueFromNonExistingCategoryCanBeHandled() {
      using(var context = new TestContext()) {
        Assert.That(context.Store.Remove("empty", "nothing"), Is.False);
      }
    }
    /// 
    ///   Verifies that the store identifies the types of values stored in
    ///   a registry when they are enumerated
    /// 
    [Test]
    public void ValueTypesAreIdentifiedWhenEnumerating() {
      Type[] types = new Type[] {
        typeof(int),
        typeof(long),
        typeof(byte[]),
        typeof(string),
        typeof(string[])
      };
      using(var context = new TestContext()) {
        context.Store.Set(null, "0", 123);
        context.Store.Set(null, "1", 456L);
        context.Store.Set(null, "2", new byte[] { 7, 8, 9 });
        context.Store.Set(null, "3", "text");
        context.Store.Set(null, "4", new string[] { "many", "words" });
        var optionInfos = new List(context.Store.EnumerateOptions());
        for(int index = 0; index < optionInfos.Count; ++index) {
          int typeIndex = int.Parse(optionInfos[index].Name);
          Assert.That(optionInfos[index].OptionType, Is.EqualTo(types[typeIndex]));
        }
      }
    }
  }
} // namespace Nuclex.Support.Settings
#endif // UNITTEST