using Elwig.Models.Dtos;
using ScottPlot;
using System;
using System.Collections.Generic;
using System.Data.Entity.Core.Common.CommandTrees.ExpressionBuilder;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Threading.Tasks;

namespace Elwig.Helpers.Export {
    public class OdsFile : IDisposable {

        protected readonly string FileName;
        protected readonly ZipArchive ZipArchive;
        protected StreamWriter? Content;
        private readonly List<string> _tables;

        public OdsFile(string filename) {
            FileName = filename;
            File.Delete(filename);
            ZipArchive = ZipFile.Open(FileName, ZipArchiveMode.Create);
            _tables = new List<string>();
        }

        public void Dispose() {
            AddTrailer().GetAwaiter().GetResult();
            ZipArchive?.Dispose();
            GC.SuppressFinalize(this);
        }

        private async Task AddHeader() {
            var mimetype = ZipArchive.CreateEntry("mimetype", CompressionLevel.NoCompression);
            using (var writer = new StreamWriter(mimetype.Open(), Utils.UTF8)) {
                await writer.WriteAsync("application/vnd.oasis.opendocument.spreadsheet");
            }

            var manifest = ZipArchive.CreateEntry("META-INF/manifest.xml");
            using (var writer = new StreamWriter(manifest.Open(), Utils.UTF8)) {
                await writer.WriteAsync("""
                    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
                    <manifest:manifest xmlns:manifest="urn:oasis:names:tc:opendocument:xmlns:manifest:1.0" manifest:version="1.3">
                     <manifest:file-entry manifest:full-path="/" manifest:version="1.3" manifest:media-type="application/vnd.oasis.opendocument.spreadsheet"/>
                     <manifest:file-entry manifest:full-path="content.xml" manifest:media-type="text/xml"/>
                     <manifest:file-entry manifest:full-path="styles.xml" manifest:media-type="text/xml"/>
                     <manifest:file-entry manifest:full-path="meta.xml" manifest:media-type="text/xml"/>
                     <manifest:file-entry manifest:full-path="settings.xml" manifest:media-type="text/xml"/>
                    </manifest:manifest>

                    """);
            }

            var styles = ZipArchive.CreateEntry("styles.xml");
            using (var writer = new StreamWriter(styles.Open(), Utils.UTF8)) {
                await writer.WriteAsync("""
                    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
                    <office:document-styles xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" office:version="1.3">
                    </office:document-styles>

                    """);
            }

            var meta = ZipArchive.CreateEntry("meta.xml");
            using (var writer = new StreamWriter(meta.Open(), Utils.UTF8)) {
                var now = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ");
                await writer.WriteAsync($"""
                    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
                    <office:document-meta xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:dc="http://purl.org/dc/elements/1.1/" office:version="1.3">
                     <office:meta>
                      <meta:generator>Elwig {App.Version}</meta:generator>
                      <meta:initial-creator>Elwig</meta:initial-creator>
                      <dc:creator>Elwig</dc:creator>
                      <meta:creation-date>{now}</meta:creation-date>
                      <dc:date>{now}</dc:date>
                     </office:meta>
                    </office:document-meta>

                    """);
            }

            var content = ZipArchive.CreateEntry("content.xml");
            Content = new StreamWriter(content.Open(), Utils.UTF8);
            await Content.WriteAsync("""
                <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
                <office:document-content xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" office:version="1.3">
                 <office:automatic-styles>
                  <style:default-style style:family="table-cell">
                   <style:text-properties fo:language="de" fo:country="AT"/>
                  </style:default-style>
                  <style:style style:name="default" style:family="table-cell">
                   <style:table-cell-properties style:vertical-align="top"/>
                  </style:style>
                  <style:style style:name="header" style:family="table-cell" style:parent-style-name="default">
                   <style:table-cell-properties style:text-align-source="fix" style:repeat-content="false"/>
                   <style:paragraph-properties fo:text-align="center"/>
                   <style:text-properties fo:font-weight="bold" style:font-weight-asian="bold" style:font-weight-complex="bold"  fo:font-size="16pt"/>
                  </style:style>
                  <style:style style:name="th" style:family="table-cell" style:parent-style-name="default">
                   <style:table-cell-properties style:text-align-source="fix" style:repeat-content="false"/>
                   <style:paragraph-properties fo:text-align="center"/>
                   <style:text-properties fo:font-weight="bold" style:font-weight-asian="bold" style:font-weight-complex="bold"/>
                  </style:style>
                 </office:automatic-styles>
                 <office:body>
                  <office:spreadsheet>
                   <table:calculation-settings table:case-sensitive="false" table:search-criteria-must-apply-to-whole-cell="true" table:use-wildcards="true" table:use-regular-expressions="false" table:automatic-find-labels="false"/>

                """);
        }

        private async Task AddTrailer() {
            if (Content == null) await AddHeader();
            if (Content == null) return;

            await Content.WriteAsync("""
                  </office:spreadsheet>
                 </office:body>
                </office:document-content>

                """);
            Content.Close();
            Content.Dispose();
            Content = null;

            var settings = ZipArchive.CreateEntry("settings.xml");
            using (var writer = new StreamWriter(settings.Open(), Utils.UTF8)) {
                await writer.WriteAsync("""
                    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
                    <office:document-settings xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ooo="http://openoffice.org/2004/office" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" office:version="1.3">
                     <office:settings>
                      <config:config-item-set config:name="ooo:view-settings">
                       <config:config-item-map-indexed config:name="Views">
                        <config:config-item-map-entry>
                         <config:config-item-map-named config:name="Tables">

                    """);

                foreach (var tbl in _tables) {
                    await writer.WriteAsync($"""
                              <config:config-item-map-entry config:name="{tbl}">
                               <config:config-item config:name="VerticalSplitMode" config:type="short">2</config:config-item>
                               <config:config-item config:name="VerticalSplitPosition" config:type="int">3</config:config-item>
                               <config:config-item config:name="PositionBottom" config:type="int">3</config:config-item>
                              </config:config-item-map-entry>

                        """);
                }

                await writer.WriteAsync("""
                         </config:config-item-map-named>
                        </config:config-item-map-entry>
                       </config:config-item-map-indexed>
                      </config:config-item-set>
                     </office:settings>
                    </office:document-settings>

                    """);
            }
        }

        public async Task AddTable<T>(DataTable<T> table) {
            if (Content == null) await AddHeader();
            if (Content == null) return;
            var totalSpan = table.ColumnSpans.Sum(s => s.Item2);

            _tables.Add(table.FullName);

            await Content.WriteAsync(
                $"    <table:table table:name=\"{table.FullName}\">\r\n" +
                $"     <table:table-column table:default-cell-style-name=\"default\"/>\r\n" +
                $"     <table:table-row>\r\n" +
                FormatCell(table.FullName, colSpan: totalSpan, style: "header") +
                $"     </table:table-row>\r\n" +
                $"     <table:table-row>\r\n" +
                $"      <table:table-cell table:number-columns-repeated=\"{totalSpan}\"/>\r\n" +
                $"     </table:table-row>\r\n" +
                $"     <table:table-row>\r\n");
            foreach (var (name, span) in table.ColumnSpans) {
                await Content.WriteAsync(FormatCell(name, colSpan: span, style: "th"));
            }
            await Content.WriteAsync("     </table:table-row>\r\n");

            foreach (var row in table.GetData()) {
                await FormatRow(row);
            }

            await Content.WriteAsync("    </table:table>\r\n");
        }

        protected async Task FormatRow(IEnumerable<object?> row) {
            if (Content == null) throw new InvalidOperationException();
            var arrays = row.Where(c => c is Array).Cast<Array>().Select(c => c.Length).ToArray();
            int rowNum = Math.Max(1, arrays.Length > 0 ? arrays.Max() : 0);
            for (int i = 0; i < rowNum; i++) {
                await Content.WriteAsync("     <table:table-row>\r\n");
                foreach (var data in row) {
                    if (data is Array a) {
                        await Content.WriteAsync(i < a.Length ? FormatCell(a.GetValue(i)) : "      <table:table-cell/>\r\n");
                    } else {
                        await Content.WriteAsync(i == 0 ? FormatCell(data, rowNum) : "      <table:covered-table-cell/>\r\n");
                    }
                }
                await Content.WriteAsync("     </table:table-row>\r\n");
            }
        }

        protected static string FormatCell(object? data, int rowSpan = 1, int colSpan = 1, string? style = "default", bool forceString = false) {
            if (data?.GetType().IsValueType == true && data.GetType().Name.StartsWith("ValueTuple"))
                return string.Join("", data.GetType().GetFields().Select(p => FormatCell(p.GetValue(data), rowSpan, colSpan, style, forceString)));

            var add = (style != null ? $" table:style-name=\"{style}\"" : "") + (rowSpan > 1 || colSpan > 1 ? $" table:number-rows-spanned=\"{rowSpan}\" table:number-columns-spanned=\"{colSpan}\"" : "");
            string c;
            if (!forceString && data == null) {
                c = $"<table:table-cell{add}/>";
            } else if (!forceString && (data is float || data is double || data is byte || data is char ||
                       data is short || data is ushort || data is int || data is uint || data is long || data is ulong)) {
                c = $"<table:table-cell office:value-type=\"float\" office:value=\"{data.ToString()?.Replace(",", ".")}\"{add}><text:p>{data}</text:p></table:table-cell>";
            } else {
                c = $"<table:table-cell office:value-type=\"string\"{add}><text:p>{data}</text:p></table:table-cell>";
            }

            return c = $"      {c}\r\n" + (colSpan > 1 ? $"      <table:covered-table-cell table:number-rows-repeated=\"{colSpan - 1}\"/>\r\n" : "");
        }
    }
}