#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;

using Volpe.Cafe.ModelSettings;
using Volpe.Cafe.Reporting.Generators;
using Volpe.Cafe.Reporting.ModelingReports;
using Volpe.Cafe.VehicleSimulation;

namespace Volpe.Cafe.Reporting
{
    /// <summary>
    /// Provides a default modeling report generator for the compliance model.
    /// </summary>
    [Serializable]
    public sealed class Reporter : IReporter, IDisposable
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="Reporter"/> class using the specified path for report files.
        /// </summary>
        /// <param name="reportingLevel">The level of reporting to use.</param>
        /// <param name="reportPath">The root path where all report files will be written.</param>
        /// <exception cref="ArgumentOutOfRangeException">reporting level is outside of allowed range.</exception>
        /// <exception cref="ArgumentNullException">report path is null.</exception>
        /// <exception cref="ArgumentException">report path is empty.</exception>
        public Reporter(ReportingLevel reportingLevel, string reportPath)
        {
            if (reportingLevel.IsDisabled()) {
                ThrowHelper.InvalidReportingLevel(nameof(reportingLevel));
            }
            if (reportPath == null) {
                ThrowHelper.ReportPathIsNull(nameof(reportPath));
            }
            if (reportPath.Trim().Length == 0) {
                ThrowHelper.ReportPathIsEmpty(nameof(reportPath));
            }

            this.ReportingLevel = reportingLevel;
            this.ReportPath = reportPath;
        }

        private bool _cancelRequested = false;
        private bool _isRunning = false;
        private string _status = null;
        private List<ReportFileWriter> _modelingReports;

        public event EventHandler RunningChanged;
        public event EventHandler StatusChanged;

        public object SyncRoot { get; } = new object();

        public bool IsDisposed { get; private set; }

        public ReportingLevel ReportingLevel { get; }

        public string ReportPath { get; private set; }

        public bool IsRunning
        {
            get => this._isRunning;
            private set {
                if (this._isRunning != value) {
                    this._isRunning = value;
                    this.OnRunningChanged();
                }
            }
        }

        public string Status
        {
            get => this._status;
            private set {
                if (this._status != value) {
                    this._status = value;
                    this.OnStatusChanged();
                }
            }
        }

        public void Close()
        {
            this.Dispose();
        }

        public void Dispose()
        {
            lock (this.SyncRoot) {
                if (this.IsDisposed) {
                    return;
                }

                if (this.IsRunning) {
                    this.CancelReporting();
                    while (this.IsRunning) {
                        Thread.Sleep(8);
                    }
                }

                this.CloseReports();

                this.RunningChanged = null;
                this.StatusChanged = null;
                this.ReportPath = null;
                this.IsDisposed = true;
            }

            GC.SuppressFinalize(this);
        }

        public void GenerateReports(Scenario scen, ReportingData data, ModelingSettings settings)
        {
            this.ThrowIfDisposed();
            if (scen == null) {
                return;
            }

            string currentFilePath = "N/A";
            bool reportingFailed = false;

            try {
                this._cancelRequested = false;
                this.IsRunning = true;
                this.Status = "Generating reports ...";
                this.OpenReports();

                for (int i = 0; i < this._modelingReports.Count; i++) {
                    var report = this._modelingReports[i];
                    if (this._cancelRequested) {
                        break;
                    }

                    var dataGenerator = report?.GetDataGenerator(scen, data, settings);
                    if (dataGenerator == null) {
                        continue;
                    }

                    currentFilePath = report.Path;
                    this.Status = $"Writing data for: {report.FileName}";

                    foreach (var values in dataGenerator) {
                        if (this._cancelRequested) {
                            break;
                        }
                        report.WriteValues(values);
                    }

                    report.Flush();
                }

                if (scen == settings.Scenarios.ScenarioList.Last()) {
                    this.CloseReports();

                    // for the last scenario only, split large reports if settings allow
                    if (this.ReportingLevel.HasSplitLargeReports()) {
                        if (this.ReportingLevel.HasVehicleAndComponent()) {
                            this.Status = $"Splitting data by scenario for: {ReportNames.Vehicles}";
                            ReportFileSplitter.SplitByScenario(this.GetPath(ReportNames.Vehicles));
                        }

                        if (this.ReportingLevel.HasDiagnosticReports()) {
                            this.Status = $"Splitting data by scenario for: {ReportNames.VehiclesDiagnostic}";
                            ReportFileSplitter.SplitByScenario(this.GetPath(ReportNames.VehiclesDiagnostic));
                            this.Status = $"Splitting data by scenario for: {ReportNames.VehicleBatteryAndMotor}";
                            ReportFileSplitter.SplitByScenario(this.GetPath(ReportNames.VehicleBatteryAndMotor));
                        }
                    }
                }
            }
            catch (IOException ex) {
                reportingFailed = true;
                Console.WriteLine(ex.ToString());
                throw ex;
            }
            catch (Exception ex) {
                reportingFailed = true;
                Console.WriteLine(ex.ToString());
                throw new IOException($"Error writing file: {currentFilePath}", ex);
            }
            finally {
                string state = this._cancelRequested ? "canceled"
                             : reportingFailed ? "failed"
                             : "completed";
                this.Status = $"Reporting for scenario {scen.Index} {state}.";
                this.IsRunning = false;
            }
        }

        public void SafeGenerateReports(Scenario scen, ReportingData data, ModelingSettings settings)
        {
            this.ThrowIfDisposed();
            lock (this.SyncRoot) {
                this.GenerateReports(scen, data, settings);
            }
        }

        public void CancelReporting()
        {
            this.ThrowIfDisposed();
            if (!this._cancelRequested) {
                this._cancelRequested = true;
            }
        }

        private void CloseReports()
        {
            this._modelingReports?.ForEach(report => report?.Close());
            this._modelingReports = null;
        }

        private void OpenReports()
        {
            if (this._modelingReports == null) {
                Directory.CreateDirectory(this.ReportPath);
                this._modelingReports = new List<ReportFileWriter>();

                if (this.ReportingLevel.HasMinimal()) {
                    OpenMinimal();
                }
                if (this.ReportingLevel.HasAnnualEffectsAndCosts()) {
                    OpenAnnualEffectsAndCosts();
                }
                if (this.ReportingLevel.HasVehicleAndComponent()) {
                    OpenVehicleAndComponent();
                }
                if (this.ReportingLevel.HasDiagnosticReports()) {
                    OpenDiagnosticReports();
                }
            }

            // local helper functions
            void OpenMinimal()
            {
                this._modelingReports.AddRange(new ReportFileWriter[] {
                    new ComplianceReport(this.GetPath(ReportNames.Compliance)),
                    new ConsumerCostsReport(this.GetPath(ReportNames.ConsumerCosts)),
                    new SocietalEffectsReport(this.GetPath(ReportNames.SocietalEffects)),
                    new SocietalCostsReport(this.GetPath(ReportNames.SocietalCosts)),
                    new TechUtilizationReport(this.GetPath(ReportNames.TechUtilization)),

                    // incremental report variants
                    new ComplianceIncrementalReport(this.GetPath(ReportNames.ComplianceIncremental)),
                    new ConsumerCostsIncrementalReport(this.GetPath(ReportNames.ConsumerCostsIncremental)),
                    new SocietalEffectsIncrementalReport(this.GetPath(ReportNames.SocietalEffectsIncremental)),
                    new SocietalCostsIncrementalReport(this.GetPath(ReportNames.SocietalCostsIncremental))
                });
            }

            void OpenAnnualEffectsAndCosts()
            {
                this._modelingReports.AddRange(new ReportFileWriter[] {
                    new AnnualSocietalEffectsReport(this.GetPath(ReportNames.AnnualSocietalEffects)),
                    new AnnualSocietalEffectsSummaryReport(this.GetPath(ReportNames.AnnualSocietalEffectsSummary)),
                    new AnnualSocietalCostsReport(this.GetPath(ReportNames.AnnualSocietalCosts)),
                    new AnnualSocietalCostsSummaryReport(this.GetPath(ReportNames.AnnualSocietalCostsSummary))
                });
            }

            void OpenVehicleAndComponent()
            {
                this._modelingReports.AddRange(new ReportFileWriter[] {
                    new VehiclesReport(this.GetPath(ReportNames.Vehicles)),
                    new PlatformsReport(this.GetPath(ReportNames.Platforms)),
                    new EnginesReport(this.GetPath(ReportNames.Engines)),
                    new TransmissionsReport(this.GetPath(ReportNames.Transmissions))
                });
            }

            void OpenDiagnosticReports()
            {
                this._modelingReports.Add(new VehiclesDiagnosticReport(this.GetPath(ReportNames.VehiclesDiagnostic)));

                if (SimulationDatabase.HasExtendedData) {
                    this._modelingReports.Add(
                        new VehiclesBatteryMotorReport(this.GetPath(ReportNames.VehicleBatteryAndMotor)));
                }
            }
        }

        private string GetPath(string fileName)
        {
            return Path.Combine(this.ReportPath, fileName);
        }

        private void OnRunningChanged()
        {
            this.RunningChanged?.Invoke(this, EventArgs.Empty);
        }

        private void OnStatusChanged()
        {
            this.StatusChanged?.Invoke(this, EventArgs.Empty);
        }

        private class ReportNames
        {
            // ReportingLevel.Minimal
            public const string Compliance = "compliance_report.csv";
            public const string ConsumerCosts = "consumer_costs_report.csv";
            public const string SocietalEffects = "societal_effects_report.csv";
            public const string SocietalCosts = "societal_costs_report.csv";
            public const string TechUtilization = "technology_utilization_report.csv";

            // ReportingLevel.AnnualEffectsAndCosts
            public const string AnnualSocietalEffects = "annual_societal_effects_report.csv";
            public const string AnnualSocietalEffectsSummary = "annual_societal_effects_summary_report.csv";
            public const string AnnualSocietalCosts = "annual_societal_costs_report.csv";
            public const string AnnualSocietalCostsSummary = "annual_societal_costs_summary_report.csv";

            // ReportingLevel.VehicleAndComponent
            public const string Vehicles = "vehicles_report.csv";
            public const string Platforms = "platforms_report.csv";
            public const string Engines = "engines_report.csv";
            public const string Transmissions = "transmissions_report.csv";

            // ReportingLevel.DiagnosticReports
            public const string VehiclesDiagnostic = "vehicles_diagnostic_report.csv";
            public const string VehicleBatteryAndMotor = "vehicles_battery_motor_report.csv";

            // ReportingLevel.Incremental + Minimal
            public const string ComplianceIncremental = "compliance_report_incr.csv";
            public const string ConsumerCostsIncremental = "consumer_costs_report_incr.csv";
            public const string SocietalEffectsIncremental = "societal_effects_report_incr.csv";
            public const string SocietalCostsIncremental = "societal_costs_report_incr.csv";
        }

    }
}
