using ReportGeneration.Interface;
using System;
using System.IO;
using System.Text;

namespace ReportGeneration.Abstract;

public abstract class StreamWriterBase : IStreamWriter
{
  private bool _isOpen;
  private bool _isClosed;

  /// <summary>
  /// Underlying <see cref="Stream"/>
  /// </summary>
  private Stream Stream { get; }

  /// <summary>
  /// Internal <see cref="StreamWriter"/> for generating the output <see cref="string"/>
  /// </summary>
  protected TextWriter Writer { get; }

  /// <summary>
  /// Constructor; Configures the
  /// <see cref="StreamWriterBase"/> to write to the memory
  /// </summary>
  protected StreamWriterBase() : this(new MemoryStream()) { }

  /// <inheritdoc cref="StreamWriterBase(System.IO.Stream, Encoding)"/>
  protected StreamWriterBase(Stream stream) : this(stream, Encoding.UTF8) { }

  /// <summary>
  /// Constructor; Configures the <see cref="StreamWriterBase"/>
  /// to write to the specified <paramref name="stream"/>
  /// </summary>
  /// <param name="stream">The <see cref="Stream"/> to write to</param>
  /// <param name="encoding">Text <see cref="Encoding"/> of the written data</param>
  protected StreamWriterBase(Stream stream, Encoding encoding)
  {
    Stream = stream;
    Writer = new StreamWriter(stream, encoding);
  }

  #region Control

  public void Open()
  {
    if (_isOpen)
    {
      throw new InvalidOperationException($"{GetType()} has already been opened");
    }

    if (_isClosed)
    {
      throw new InvalidOperationException($"Cannot call open on a closed {GetType()}");
    }

    _isOpen = true;

    OnOpen();
  }

  public void Close()
  {
    if (_isClosed)
    {
      throw new InvalidOperationException($"{GetType()} has already been closed");
    }

    if (!_isOpen)
    {
      throw new InvalidOperationException($"{GetType()} has never been opened");
    }

    _isClosed = true;

    OnClose();
    Writer.Flush();
  }

  /// <summary>
  /// Called once the internal writer has been initialized
  /// and the <see cref="Stream"/> is ready for writing
  /// </summary>
  protected virtual void OnOpen() { }

  /// <summary>
  /// Called once the document is about to be closed
  /// </summary>
  protected virtual void OnClose() { }

  #endregion

  #region Reading

  /// <inheritdoc />
  public StreamReader Read()
  {
    Writer.Flush();
    Stream.Seek(0, SeekOrigin.Begin);
    return new StreamReader(Stream, Writer.Encoding);
  }

  #endregion

  #region Writing

  public IStreamWriter Write(IStreamWriter writer)
  {
    using var reader = writer.Read();
    return Write(reader);
  }

  public IStreamWriter Write(StreamReader reader)
  {
    int bytesRead;
    char[] buffer = new char[4096];

    while ((bytesRead = reader.Read(buffer, 0, buffer.Length)) > 0)
    {
      Writer.Write(buffer, 0, bytesRead);
    }

    return this;
  }

  public IStreamWriter Write(string? text = default)
  {
    Writer.Write(text);
    return this;
  }

  public IStreamWriter WriteLine(string? text = default)
  {
    Writer.WriteLine(text);
    return this;
  }

  /// <summary>
  /// Creates and configures the <typeparamref name="T"/> using the given functions
  /// </summary>
  /// <typeparam name="T">Type of the <see cref="IStreamWriter"/></typeparam>
  /// <param name="makeFunc">Function used to generate the <typeparamref name="T"/></param>
  /// <param name="configFunc">Function used to configure the <typeparamref name="T"/></param>
  /// <returns></returns>
  public IStreamWriter Write<T>(Func<T> makeFunc, Action<T> configFunc)
    where T : IStreamWriter
  {
    using var writer = makeFunc();

    writer.Open();
    configFunc(writer);
    writer.Close();

    Write(writer);
    return this;
  }
  
  #endregion

  #region IDisposable

  private bool _disposed;

  protected virtual void Dispose(bool disposing)
  {
    if (disposing)
    {
      try
      {
        // Close document
        Close();
      }
      catch (InvalidOperationException)
      {
        // ignore
      }
      finally
      {
        // Dispose stream
        Stream.Dispose();
      }
    }
  }

  /// <inheritdoc />
  public void Dispose()
  {
    if (_disposed)
    {
      return;
    }

    _disposed = true;
    Dispose(true);
    GC.SuppressFinalize(this);
  }

  #endregion
}
