using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace Kruzya.TelegramBot.Core
{
public sealed class Core
{
///
/// The Core API version.
///
public UInt32 ApiVersion => 1;
///
/// The array that contains all modules instances.
///
public Module[] Modules => _modules.ToArray();
///
/// The current bot configuration.
///
public IConfiguration Configuration => _configuration;
#region Private properties
///
/// The array that contains all modules instances.
///
private List _modules = new List();
///
/// The current bot configuration.
///
private IConfiguration _configuration;
///
/// The all directories for loading our module assemblies.
///
private List _directories
{
get
{
// For start, load all assemblies in required directory.
// And register in internal temporary array.
var modulesDirectory = new List(new string[]
{
Path.Join(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "modules"),
Path.Join(Environment.CurrentDirectory, "modules")
});
// Also lookup load directories from config.
modulesDirectory.AddRange(_configuration.GetSection("ModuleDirectories").GetChildren().Select(q => q.Value)
.ToList());
return modulesDirectory;
}
}
#endregion
public Core(IConfiguration configuration)
{
_configuration = configuration;
Start();
}
///
/// Initializes all required submodules.
///
private void Start()
{
var types = new List();
foreach (var directory in _directories)
{
if (!Directory.Exists(directory))
{
continue;
}
foreach (var file in Directory.EnumerateFiles(directory, "*.dll", SearchOption.AllDirectories))
{
var assemblyTypes = LoadAssembly(file);
if (assemblyTypes == null)
{
continue;
}
types.AddRange(assemblyTypes);
}
}
// Instantiate them.
foreach (var module in types)
{
EnsureLoaded(module);
}
}
///
/// Loads the assembly and appends all Module types to list.
///
/// The full path to assembly file
/// if success, null otherwise.
private IEnumerable LoadAssembly(string path)
{
Assembly assembly;
try
{
assembly = Assembly.LoadFile(path);
}
catch (Exception)
{
// don't load any invalid module.
return null;
}
return assembly.GetExportedTypes().Where(x => !x.IsAbstract && x.IsSubclassOf(typeof(Module)));
}
///
/// Ensures if module is loaded and started. Note that call can change module event chain.
///
/// The Module type.
/// Module instance.
public T EnsureLoaded()
where T : Module
{
return (T) EnsureLoaded(typeof(T));
}
///
/// Ensures if module is loaded and started. Note that call can change module event chain.
///
/// The Module type.
/// Module instance.
/// The given type is not extends the Module class
public Module EnsureLoaded(Type moduleType)
{
if (!moduleType.IsSubclassOf(typeof(Module)))
{
throw new Exception("Received incorrect module type");
}
var module = _modules.FirstOrDefault(module => module.GetType() == moduleType);
if (module == null)
{
module = (Module) Activator.CreateInstance(moduleType, this);
Console.WriteLine($"Started module {module.GetType().FullName}");
_modules.Add(module);
}
return module;
}
}
}