using IniParser.Model;
using System;
using System.Globalization;
using System.IO;
using System.IO.Ports;

namespace BoFilTest.Utils
{
    class ModuleMode
    {
        public const string EMULATED = "emulated";
        public const string REAL = "real";
        public const string NONE = "none";
        public const string LEGACY = "legacy";
        public const string SARTORIUS = "sartorius";
    }

    public static class ConfigHelper
    {
        public static int ConvertToInt(KeyDataCollection section, string key, string name)
        {
            var stored = section[key];
            int result = 0;
            if (!Int32.TryParse(stored, out result))
            {
                throw new InvalidOperationException(name + " value cannot be converted to a number");
            }
            return result;
        }

        public static int ConvertToInt(KeyDataCollection section, string key)
        { 
            return ConvertToInt(section, key, key);
        }

        public static double ConvertToDouble(KeyDataCollection config, string key, string name)
        {
            var stored = config[key];
            if (!double.TryParse(stored, System.Globalization.NumberStyles.Any, CultureInfo.InvariantCulture, out double result))
            {
                throw new InvalidOperationException(key + " value cannot be converted to a number");
            }
            return result;
        }

        public static double ConvertToDouble(KeyDataCollection config, string key)
        {
            return ConvertToDouble(config, key, key);
        }

        public static bool ConvertToBool(KeyDataCollection config, string key)
        {
            var actual = config[key].ToLower();
            return actual == "yes" || actual == "true" || actual == "1";
        }

        public static void WriteBool(this KeyDataCollection config, string key, bool value)
        {
            config[key] = value ? "yes" : "no";
        }
        
        public static void WriteDouble(this KeyDataCollection config, string key, double value)
        {
            config[key] = value.ToString(CultureInfo.InvariantCulture);
        }

        public static void WriteInt(this KeyDataCollection config, string key, int value)
        {
            config[key] = value.ToString();
        }
    }

    public class ExportConfigSection
    {
        private readonly KeyDataCollection section;

        public ExportConfigSection(KeyDataCollection section)
        {
            this.section = section;
        }

        public string TemplatePath()
        {
            return this.section["template.path"];
        }

        public string TemplateSheet()
        {
            return this.section["template.sheet"];
        }

        public string TargetPath()
        {
            return section["target.path"];
        }

        public Tuple<int, int> CellFor(string attributeName)
        {
            var value = this.section[attributeName];
            if (value == null)
                throw new System.InvalidOperationException(Translated.FromKey("CELL_COORDINATE_NOT_DEFINED", attributeName));

            char[] separators = { ':' };
            var parts = value.Split(separators, 2);
            if (parts.Length != 2)
                throw new System.InvalidOperationException(Translated.NotYet("Cell needs two parts"));

            var columnString = parts[0];
            if (columnString.Length != 1)
                throw new System.InvalidOperationException(Translated.NotYet("First part of cell must be a single letter"));

            int column = columnString.ToLower()[0] - 'a';

            var rowString = parts[1];
            int row = 0;
            if (!Int32.TryParse(rowString, out row))
                throw new System.InvalidOperationException(Translated.NotYet("Second part of cell must be a number"));

            // Excel user-facing rows are 1-based, and we want e.g. D:1 to be the same as in excel
            return Tuple.Create(column, row - 1);
        }
    }

    public class GasFlowMeasureConfiguration
    {
        private readonly KeyDataCollection section;

        public GasFlowMeasureConfiguration(KeyDataCollection section)
        {
            this.section = section;
        }

        public string Mode()
        {
            return section["mode"];
        }

        public string ModbusHost()
        {
            return section["modbus.host"];
        }
    }

    public class PressureMeasureConfiguration
    {
        private readonly KeyDataCollection section;

        public PressureMeasureConfiguration(KeyDataCollection section)
        {
            this.section = section;
        }

        public String Mode()
        {
            return this.section["mode"];
        }

        public String DeviceName()
        {
            return this.section["device.name"];
        }
    }

    public class ExtraMeasureConfiguration
    {
        private readonly KeyDataCollection section;

        public ExtraMeasureConfiguration(KeyDataCollection section)
        {
            this.section = section;
        }

        public String Mode()
        {
            return this.section["mode"];
        }

        public String DeviceName()
        {
            return this.section["device.name"];
        }

        public bool Enabled()
        {
            return Mode() != ModuleMode.NONE;
        }
    }

    public class VoegtlinConfiguration
    {
        private readonly KeyDataCollection section;
        public VoegtlinConfiguration(KeyDataCollection section)
        {
            this.section = section;
        }

        public string ModbusPort()
        {
            return section["modbus.serial.port"];
        }

        public byte SlaveAddress()
        {
            return Convert.ToByte(ConfigHelper.ConvertToInt(section, "modbus.slave.address"));
        }
    }

    public class BalanceConfiguration
    {
        private readonly KeyDataCollection section;
        public BalanceConfiguration(KeyDataCollection section)
        {
            this.section = section;
        }

        public String Mode()
        {
            return this.section["mode"];
        }

        public String Port()
        {
            return this.section["serial.port"];
        }

        private int ConvertToInt(string key, string name)
        {
            return ConfigHelper.ConvertToInt(this.section, key, name);
        }
        
        public int BaudRate()
        {
            return ConvertToInt("serial.baud.rate", "Baud rate");
        }

        public Parity Parity()
        {
            var stored = this.section["serial.parity"].ToLower();
            
            switch (stored)
            {
                case "even": return System.IO.Ports.Parity.Even;
                case "none": return System.IO.Ports.Parity.None;
                case "odd": return System.IO.Ports.Parity.Odd;
                case "mark": return System.IO.Ports.Parity.Mark;
                case "space": return System.IO.Ports.Parity.Space;
            }
            throw new InvalidOperationException("Unable to convert parity string "
                + stored + ". Valid values are none, odd, even, mark and space.");
        }

        public int DataBits()
        {
            return ConvertToInt("serial.data.bits", "Data bits");
        }

        public StopBits StopBits()
        {
            switch (ConvertToInt("serial.stop.bits", "Stop bits"))
            {
                case 1: return System.IO.Ports.StopBits.One;
                case 2: return System.IO.Ports.StopBits.Two;
            }
            throw new InvalidOperationException("Unsupported stop bits value");
        }

        public bool StandBy()
        {
            return ConfigHelper.ConvertToBool(this.section, "standby");
        }
    }

    public class DefaultsConfiguration
    {
        KeyDataCollection section;
        public DefaultsConfiguration(KeyDataCollection section)
        {
            this.section = section;
        }

        public string this[string keyName] => this.section[keyName];

        public int AsInt(string keyName)
        {
            return ConfigHelper.ConvertToInt(section, keyName);
        }

        public double AsDouble(string keyName)
        {
            return ConfigHelper.ConvertToDouble(section, keyName);
        }

        public bool AsBool(string keyName)
        {
            return ConfigHelper.ConvertToBool(section, keyName);
        }
    }

    public class Configuration
    {
        private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
        private const string MAIN_SECTION = "Main";
        public const string DEFAULTS_SECTION_NAME = "Defaults";
        private static string FILENAME = "BoFilTest.ini";
        private static string DEFAULTS_FILENAME = "BoFilTest.defaults.ini";


        private readonly IniData config;

        public static void CreateFromDefault()
        {
            try
            {
                File.Copy(DEFAULTS_FILENAME, FILENAME, false);
            }
            catch (IOException e)
            {
                logger.Info("Not copying config defaults: ", e.Message);
            }
        }

        public static Configuration Load()
        {
            var parser = new IniParser.Parser.IniDataParser();
            string filename = FILENAME;
            try
            {
                var data = parser.Parse(File.ReadAllText(filename));
                return new Configuration(data);
            }
            catch (FileNotFoundException e)
            {
                logger.Error("Did not find config file: {0}", e.Message);
                throw new Exception(Translated.FromKey("CONFIGURATION_NOT_FOUND", filename));
            }
        }

        public static Configuration PrepareAndLoad()
        {
            CreateFromDefault();
            return Load();
        }

        public Configuration(IniData config)
        {
            this.config = config;
        }

        public string TestNumberPrefix()
        {
            return this.config[MAIN_SECTION]["test.number.prefix"];
        }

        public GasFlowMeasureConfiguration GasFlowMeasure()
        {
            return new GasFlowMeasureConfiguration(this.config["Gas.Flow.Measure"]);
        }

        public ExportConfigSection Raw()
        {
            return new ExportConfigSection(this.config["Excel.Raw"]);
        }

        public ExportConfigSection Filter()
        {
            return new ExportConfigSection(this.config["Excel.Filter"]);
        }

        public PressureMeasureConfiguration PressureMeasure()
        {
            return new PressureMeasureConfiguration(this.config["Pressure.Measure"]);
        }

        public ExtraMeasureConfiguration ExtraMeasure()
        {
            return new ExtraMeasureConfiguration(this.config["Extra.Measure"]);
        }

        public BalanceConfiguration Balance()
        {
            return new BalanceConfiguration(this.config["Balance"]);
        }

        public DefaultsConfiguration Defaults()
        {
            return new DefaultsConfiguration(this.config[DEFAULTS_SECTION_NAME]);
        }

        internal VoegtlinConfiguration Voegtlin()
        {
            return new VoegtlinConfiguration(this.config["Voegtlin"]);
        }
    }
}
