﻿using Microsoft.Win32;
using System;
using System.Diagnostics;
using System.IO;
using unoidl.com.sun.star.beans;
using unoidl.com.sun.star.bridge;
using unoidl.com.sun.star.frame;
using unoidl.com.sun.star.lang;
using unoidl.com.sun.star.uno;

namespace LibreOffice_MailMerge
{
  class LibreOffice : IDisposable
  {
    // LibreOffice process.
    private Process process;

    // LibreOffice user profile directory.
    public string UserProfilePath { get; private set; }

    public XComponentContext Context { get; private set; }
    public XMultiComponentFactory ServiceManager { get; private set; }
    public XDesktop2 Desktop { get; private set; }

    public LibreOffice()
    {
      const string name = "MyProjectName";

      UserProfilePath = Path.Combine(Path.GetTempPath(), name);
      CleanUserProfile();

      InitializeEnvironment();

      var arguments = $"-env:UserInstallation={new Uri(UserProfilePath)} --accept=pipe,name={name};urp --headless --nodefault --nofirststartwizard --nologo --nolockcheck";

      process = new Process();
      process.StartInfo.UseShellExecute = false;
      process.StartInfo.FileName = "soffice";
      process.StartInfo.Arguments = arguments;
      process.StartInfo.CreateNoWindow = true;

      process.StartInfo.EnvironmentVariables["tmp"] = UserProfilePath;

      process.Start();
      var xLocalContext = uno.util.Bootstrap.defaultBootstrap_InitialComponentContext();
      var xLocalServiceManager = xLocalContext.getServiceManager();
      var xUnoUrlResolver = (XUnoUrlResolver)xLocalServiceManager.createInstanceWithContext("com.sun.star.bridge.UnoUrlResolver", xLocalContext);

      for (int i = 0; i <= 10; i++)
      {
        try
        {
          ServiceManager = (XMultiComponentFactory)xUnoUrlResolver.resolve($"uno:pipe,name={name};urp;StarOffice.ServiceManager");
          break;
        }
        catch (unoidl.com.sun.star.connection.NoConnectException)
        {
          System.Threading.Thread.Sleep(1000);
          if (Equals(i, 10))
          {
            throw;
          }
        }
      }

      Context = (XComponentContext)((XPropertySet)ServiceManager).getPropertyValue("DefaultContext").Value;
      Desktop = (XDesktop2)ServiceManager.createInstanceWithContext("com.sun.star.frame.Desktop", Context);
    }

    /// <summary>
    /// Set up the environment variables for the process.
    /// </summary>
    private void InitializeEnvironment()
    {
      var nodes = new RegistryHive[] { RegistryHive.CurrentUser, RegistryHive.LocalMachine };

      foreach (var node in nodes)
      {
        var key = RegistryKey.OpenBaseKey(node, RegistryView.Registry32).OpenSubKey(@"SOFTWARE\LibreOffice\UNO\InstallPath");

        if (key != null && key.ValueCount > 0)
        {
          var unoPath = key.GetValue(key.GetValueNames()[key.ValueCount - 1]).ToString();

          Environment.SetEnvironmentVariable("PATH", $"{unoPath};{Environment.GetEnvironmentVariable("PATH")}", EnvironmentVariableTarget.Process);
          Environment.SetEnvironmentVariable("URE_BOOTSTRAP", new Uri(Path.Combine(unoPath, "fundamental.ini")).ToString(), EnvironmentVariableTarget.Process);
          return;
        }
      }

      throw new System.Exception("LibreOffice not found.");
    }

    /// <summary>
    /// Delete LibreOffice user profile directory.
    /// </summary>
    private void CleanUserProfile()
    {
      if (Directory.Exists(UserProfilePath))
      {
        Directory.Delete(UserProfilePath, true);
      }
    }

    #region IDisposable Support

    private bool disposed = false;

    protected virtual void Dispose(bool disposing)
    {
      if (!disposed)
      {
        if (disposing)
        {

        }

        if (Desktop != null)
        {
          Desktop.terminate();
          Desktop = null;
          ServiceManager = null;
          Context = null;
        }

        if (process != null)
        {
          // Wait LibreOffice process.
          if (!process.WaitForExit(5000))
          {
            process.Kill();
          }

          process.Dispose();
        }

        CleanUserProfile();

        disposed = true;
      }
    }

    ~LibreOffice()
    {
      Dispose(false);
    }

    public void Dispose()
    {
      Dispose(true);
      GC.Collect();
      GC.SuppressFinalize(this);
    }

    #endregion
  }
}
