using System.Text;
using DataModel.Database;

namespace Processor.Generators;

public class DotnetBigFile : IGenerator
{
    private static readonly List<string> CULTURE_CODES = new();
    private static int DEFAULT_CULTURE_INDEX = -1; 

    public async Task GenerateAsync()
    {
        const string filename = "I18N.cs";
        
        var destPath = await AppSettings.GetGeneratorDotnetDestinationPath();
        destPath = Environment.ExpandEnvironmentVariables(destPath);
        
        var pathFinal = Path.Join(destPath, filename);
        var pathTemp = Path.Join(destPath, filename + ".gen");
        if(File.Exists(pathTemp))
            File.Delete(pathTemp);

        CULTURE_CODES.Clear();
        var cultures = await AppSettings.GetCultureInfos();
        foreach (var (code, _) in cultures)
            CULTURE_CODES.Add(code);

        DEFAULT_CULTURE_INDEX = await AppSettings.GetGeneratorDotnetDefaultCultureIndex();
        DEFAULT_CULTURE_INDEX -= 1; // 1-based to 0-based

        try
        {
            await using var fileStream = new FileStream(pathTemp, FileMode.CreateNew, FileAccess.Write, FileShare.None);
            await using var writer = new StreamWriter(fileStream, Encoding.UTF8);

            await writer.WriteLineAsync($"namespace {await AppSettings.GetGeneratorDotnetNamespace()};");
            await this.CreateStaticClass(writer, "I18N", 0, async (streamWriter, indention) =>
            {
                var indentionString = this.AddIndention(indention);
                var buildTime = DateTime.UtcNow;
                await writer.WriteLineAsync($"{indentionString}public static readonly string BUILD_TIME = \"{buildTime:yyyy.MM.dd HH:mm:ss}\";");
                await writer.WriteLineAsync($"{indentionString}public static readonly long BUILD_TIME_TICKS = {buildTime.Ticks};");
                await writer.WriteLineAsync();
                await writer.WriteLineAsync($"{indentionString}private static int PREVIOUS_CULTURE = -1;");
                
                // Go through the first layer of sections:
                var sections = await SectionProcessor.LoadLayer(0);
                foreach (var section in sections)
                    await this.TransformSection(writer, indention, section);
            });
        }
        finally
        {
            if(new FileInfo(pathTemp).Length > 0)
            {
                if(File.Exists(pathFinal))
                    File.Delete(pathFinal);
                
                File.Move(pathTemp, pathFinal);
            }
        }
    }

    private string AddIndention(int indention) => new string(' ', indention * 3);

    private async Task TransformSection(TextWriter writer, int indention, Section section)
    {
        await this.CreateStaticClass(writer, section.DataKey, indention, async (_, innerIndention) =>
        {
            var textElements = section.TextElements;
            foreach (var textElement in textElements)
                    await this.TransformTextElement(writer, innerIndention, textElement);

            var childSections = await SectionProcessor.GetChildSections(section.DataKey);
            foreach (var childSection in childSections)
                await this.TransformSection(writer, innerIndention, childSection);
        });
    }
    
    private async Task TransformTextElement(TextWriter writer, int indention, TextElement textElement)
    {
        var indentionString = this.AddIndention(indention);
        var indentionPropString = this.AddIndention(indention + 1);
        var indentionPropInner1String = this.AddIndention(indention + 2);
        var indentionPropInner2String = this.AddIndention(indention + 3);
        var indentionPropInner3String = this.AddIndention(indention + 4);

        await writer.WriteLineAsync($"{indentionString}private static string E_{textElement.Code}_CACHE = \"\";");
        await writer.WriteLineAsync($"{indentionString}public static string E_{textElement.Code}");
        await writer.WriteLineAsync($"{indentionString}{{");
        await writer.WriteLineAsync($"{indentionPropString}get");
        await writer.WriteLineAsync($"{indentionPropString}{{");
        await writer.WriteLineAsync($"{indentionPropInner1String}var currentCulture = CultureInfo.CurrentCulture.Name;");
        await writer.WriteLineAsync($"{indentionPropInner1String}if(PREVIOUS_CULTURE == currentCulture.GetHashCode())");
        await writer.WriteLineAsync($"{indentionPropInner2String}return E_{textElement.Code}_CACHE;");
        await writer.WriteLineAsync($"{indentionPropInner1String}else");
        await writer.WriteLineAsync($"{indentionPropInner1String}{{");
        await writer.WriteLineAsync($"{indentionPropInner2String}PREVIOUS_CULTURE = currentCulture.GetHashCode();");
        for (var cultureIndex = 0; cultureIndex < CULTURE_CODES.Count; cultureIndex++)
        {
            if(cultureIndex == 0)
                await writer.WriteLineAsync($"{indentionPropInner2String}if (currentCulture.StartsWith(\"{CULTURE_CODES[cultureIndex]}\", StringComparison.InvariantCultureIgnoreCase))");
            else
                await writer.WriteLineAsync($"{indentionPropInner2String}else if (currentCulture.StartsWith(\"{CULTURE_CODES[cultureIndex]}\", StringComparison.InvariantCultureIgnoreCase))");
            
            await writer.WriteLineAsync($"{indentionPropInner2String}{{");
            var cultureTranslation = textElement.Translations.FirstOrDefault(x => x.Culture == CULTURE_CODES[cultureIndex]);
            var cultureText = cultureTranslation?.Text ?? string.Empty;
            await writer.WriteLineAsync($"{indentionPropInner3String}var text = @\"{Utils.MadeVerbatimStringLiteral(cultureText)}\";");
            await writer.WriteLineAsync($"{indentionPropInner3String}E_{textElement.Code}_CACHE = text;");
            await writer.WriteLineAsync($"{indentionPropInner3String}return text;");
            await writer.WriteLineAsync($"{indentionPropInner2String}}}");            
        }
        
        // Add the default case:
        await writer.WriteLineAsync($"{indentionPropInner2String}else");
        await writer.WriteLineAsync($"{indentionPropInner2String}{{");
        var defaultCultureTranslation = textElement.Translations.FirstOrDefault(x => x.Culture == CULTURE_CODES[DEFAULT_CULTURE_INDEX]);
        var defaultCultureText = defaultCultureTranslation?.Text ?? string.Empty;
        await writer.WriteLineAsync($"{indentionPropInner3String}var text = @\"{Utils.MadeVerbatimStringLiteral(defaultCultureText)}\";");
        await writer.WriteLineAsync($"{indentionPropInner3String}E_{textElement.Code}_CACHE = text;");
        await writer.WriteLineAsync($"{indentionPropInner3String}return text;");
        await writer.WriteLineAsync($"{indentionPropInner2String}}}");
        
        await writer.WriteLineAsync($"{indentionPropInner1String}}}");
        await writer.WriteLineAsync($"{indentionPropString}}}");
        await writer.WriteLineAsync($"{indentionString}}}");
        await writer.WriteLineAsync();
    }
    
    private async Task CreateStaticClass(TextWriter writer, string name, int indention, Func<TextWriter, int, Task> content)    
    {
        var indentionString = this.AddIndention(indention);

        await writer.WriteLineAsync(indentionString);
        await writer.WriteLineAsync($"{indentionString}public static class {name}");
        await writer.WriteLineAsync($"{indentionString}{{");
        await content(writer, indention + 1);
        await writer.WriteLineAsync($"{indentionString}}}");
    }
}