#region <<< Using Directives >>>
using System;
using System.Collections.Generic;

using DB = Volpe.Cafe.VehicleSimulation.SimulationDatabase;
using TC = Volpe.Cafe.TechnologyClasses;
using TechKey = Volpe.Cafe.TechKeys.TechKey128;
using TI = Volpe.Cafe.TechnologyIndexes;
#endregion

namespace Volpe.Cafe.VehicleSimulation
{
    [Serializable]
    internal class DBParser
    {
        public bool Parse(string fileName, List<string> fileLines, out Dictionary<TechKey, double[]> dict, bool silent)
        {
            DB.LogHelper($"Parsing data file: '{fileName}'.", silent);
            dict = null;

            int errCount = this.ParseHeader(fileName, fileLines[0], silent);
            if (errCount == 0) {
                dict = new Dictionary<TechKey, double[]>(fileLines.Capacity);
                for (int i = 1; i < fileLines.Count; i++) {
                    errCount += this.ParseRecord(fileName, fileLines[i], dict, silent);
                }
            }

            string exitMessage = (errCount == 0)
                ? $"'{fileName}' parsed successfully (no errors detected)!"
                : $"'{fileName}' failed to parse; total number of errors found: {errCount}.";
            DB.LogHelper(exitMessage, silent);

            return (errCount == 0);
        }

        private int ParseHeader(string fileName, string line, bool silent)
        {
            int errCount = 0;
            string[] words = this.SplitLine(line);

            if (words.Length != TC.List.Length + 1 || string.Compare(words[0], "TechKey", true) != 0) {
                errCount = 1;
            }
            else {
                for (int i = 1; i < words.Length; i++) {
                    if (string.Compare(words[i], TC.Names[i - 1], true) != 0) {
                        errCount = 1;
                        break;
                    }
                }
            }

            if (errCount == 1) {
                DB.LogHelper(
                    $"'{fileName}' parse error: Header is not properly formatted. Line read: {line}", silent);
            }

            return errCount;
        }

        private int ParseRecord(string fileName, string line, Dictionary<TechKey, double[]> dict, bool silent)
        {
            string errHdr = $"'{fileName}' parse error: ";
            int errCount = 0;
            TechKey key = TechKey.Empty;

            // validate line, then generate key
            // note: number of values must be num classes + 1 header
            string[] words = this.SplitLine(line);
            if (words.Length != TC.List.Length + 1 || !this.GenerateTechKey(words[0], out key)) {
                DB.LogHelper($"{errHdr}Key generation error. Line read: {line}", silent);
                errCount++;
            }

            // build array of values
            double[] values = new double[TC.List.Length];
            for (int j = 0; j < values.Length; j++) {
                if (!double.TryParse(words[j + 1], out values[j])) {
                    DB.LogHelper($"{errHdr}One or more values is not numeric. Line read: {line}", silent);
                    errCount++;
                }
            }

            // add to database
            if (dict.ContainsKey(key)) {
                DB.LogHelper($"{errHdr}Duplicate entry detected. Line read: {line}", silent);
                errCount++;
            }
            else {
                dict.Add(key, values);
            }

            return errCount;
        }

        private string[] SplitLine(string line)
        {
            string[] words = line.Split(',');
            for (int i = 0; i < words.Length; i++) {
                words[i] = words[i].Trim('"');
            }
            return words;
        }

        private bool GenerateTechKey(string techString, out TechKey key)
        {
            string[] techs = techString.Split(';');
            key = TechKey.Empty;

            for (int i = 0; i < techs.Length; i++) {
                string techName = techs[i].Trim();
                if (techName == string.Empty) {
                    continue;
                }

                int techIndex = TI.TechNamesDict.TryGetValue(techName, out int index) ? index : -1;
                if (techIndex == -1) {
                    return false;
                }

                key |= TechKey.Keys[techIndex];
            }

            return true;
        }

    }
}
