using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Robust.Shared.Localization;

namespace Content.Shared.Localizations
{
    public static class Units
    {
        public sealed class TypeTable
        {
            public readonly Entry[] E;

            public TypeTable(params Entry[] e) => E = e;

            public sealed class Entry
            {
                // Any item within [Min, Max) is considered to be in-range
                // of this Entry.
                public readonly (double? Min, double? Max) Range;

                // Factor is a number that the value will be multiplied by
                // to adjust it in to the proper range.
                public readonly double Factor;

                // Unit is an ID for Fluent. All Units are prefixed with
                // "units-" internally. Usually follows the format $"{unit-abbrev}-{prefix}".
                //
                // Example: "si-g" is actually processed as "units-si-g"
                //
                // As a matter of style, units for values less than 1 (i.e. mW)
                // should have two dashes before their prefix.
                public readonly string Unit;

                public Entry((double?, double?) range, double factor, string unit)
                {
                    Range = range;
                    Factor = factor;
                    Unit = unit;
                }
            }

            public bool TryGetUnit(double val, [NotNullWhen(true)] out Entry? winner)
            {
                Entry? w = default!;
                foreach (var e in E)
                    if ((e.Range.Min == null || e.Range.Min <= val) && (e.Range.Max == null || val < e.Range.Max))
                        w = e;

                winner = w;
                return w != null;
            }

            public string Format(double val)
            {
                if (TryGetUnit(val, out var w))
                    return (val * w.Factor).ToString() + " " + Loc.GetString("units-" + w.Unit);

                return val.ToString();
            }

            public string Format(double val, string fmt)
            {
                if (TryGetUnit(val, out var w))
                    return (val * w.Factor).ToString(fmt) + " " + Loc.GetString("units-" + w.Unit);

                return val.ToString(fmt);
            }
        }

        public static readonly TypeTable Generic = new TypeTable
        (
            // Table layout. Fite me.
            new TypeTable.Entry(range: ( null, 1e-24), factor:  1e24, unit: "si--y"),
            new TypeTable.Entry(range: (1e-24, 1e-21), factor:  1e21, unit: "si--z"),
            new TypeTable.Entry(range: (1e-21, 1e-18), factor:  1e18, unit: "si--a"),
            new TypeTable.Entry(range: (1e-18, 1e-15), factor:  1e15, unit: "si--f"),
            new TypeTable.Entry(range: (1e-15, 1e-12), factor:  1e12, unit: "si--p"),
            new TypeTable.Entry(range: (1e-12,  1e-9), factor:   1e9, unit: "si--n"),
            new TypeTable.Entry(range: ( 1e-9,  1e-3), factor:   1e6, unit: "si--u"),
            new TypeTable.Entry(range: ( 1e-3,     1), factor:   1e3, unit: "si--m"),
            new TypeTable.Entry(range: (    1,  1000), factor:     1, unit: "si"),
            new TypeTable.Entry(range: ( 1000,   1e6), factor:  1e-4, unit: "si-k"),
            new TypeTable.Entry(range: (  1e6,   1e9), factor:  1e-6, unit: "si-m"),
            new TypeTable.Entry(range: (  1e9,  1e12), factor:  1e-9, unit: "si-g"),
            new TypeTable.Entry(range: ( 1e12,  1e15), factor: 1e-12, unit: "si-t"),
            new TypeTable.Entry(range: ( 1e15,  1e18), factor: 1e-15, unit: "si-p"),
            new TypeTable.Entry(range: ( 1e18,  1e21), factor: 1e-18, unit: "si-e"),
            new TypeTable.Entry(range: ( 1e21,  1e24), factor: 1e-21, unit: "si-z"),
            new TypeTable.Entry(range: ( 1e24,  null), factor: 1e-24, unit: "si-y")
        );

        // N.B. We use kPa internally, so this is shifted one order of magnitude down.
        public static readonly TypeTable Pressure = new TypeTable
        (
            new TypeTable.Entry(range: (null, 1e-6), factor:  1e9, unit: "u--pascal"),
            new TypeTable.Entry(range: (1e-6, 1e-3), factor:  1e6, unit: "m--pascal"),
            new TypeTable.Entry(range: (1e-3,    1), factor:  1e3, unit: "pascal"),
            new TypeTable.Entry(range: (   1, 1000), factor:    1, unit: "k-pascal"),
            new TypeTable.Entry(range: (1000,  1e6), factor: 1e-4, unit: "m-pascal"),
            new TypeTable.Entry(range: ( 1e6, null), factor: 1e-6, unit: "g-pascal")
        );

        public static readonly TypeTable Power = new TypeTable
        (
            new TypeTable.Entry(range: (null, 1e-3), factor:  1e6, unit: "u--watt"),
            new TypeTable.Entry(range: (1e-3,    1), factor:  1e3, unit: "m--watt"),
            new TypeTable.Entry(range: (   1, 1000), factor:    1, unit: "watt"),
            new TypeTable.Entry(range: (1000,  1e6), factor: 1e-4, unit: "k-watt"),
            new TypeTable.Entry(range: ( 1e6,  1e9), factor: 1e-6, unit: "m-watt"),
            new TypeTable.Entry(range: ( 1e9, null), factor: 1e-9, unit: "g-watt")
        );

        public static readonly TypeTable Energy = new TypeTable
        (
            new TypeTable.Entry(range: ( null, 1e-3), factor:  1e6, unit: "u--joule"),
            new TypeTable.Entry(range: ( 1e-3,    1), factor:  1e3, unit: "m--joule"),
            new TypeTable.Entry(range: (    1, 1000), factor:    1, unit: "joule"),
            new TypeTable.Entry(range: ( 1000,  1e6), factor: 1e-4, unit: "k-joule"),
            new TypeTable.Entry(range: (  1e6,  1e9), factor: 1e-6, unit: "m-joule"),
            new TypeTable.Entry(range: (  1e9, null), factor: 1e-9, unit: "g-joule")
        );

        public static readonly TypeTable Temperature = new TypeTable
        (
            new TypeTable.Entry(range: ( null, 1e-3), factor:  1e6, unit: "u--kelvin"),
            new TypeTable.Entry(range: ( 1e-3,    1), factor:  1e3, unit: "m--kelvin"),
            new TypeTable.Entry(range: (    1,  1e3), factor:    1, unit: "kelvin"),
            new TypeTable.Entry(range: (  1e3,  1e6), factor: 1e-3, unit: "k-kelvin"),
            new TypeTable.Entry(range: (  1e6,  1e9), factor: 1e-6, unit: "m-kelvin"),
            new TypeTable.Entry(range: (  1e9, null), factor: 1e-9, unit: "g-kelvin")
        );

        public readonly static Dictionary<string, TypeTable> Types = new Dictionary<string, TypeTable>
        {
            ["generic"] = Generic!,
            ["pressure"] = Pressure!,
            ["power"] = Power!,
            ["energy"] = Energy!,
            ["temperature"] = Temperature!
        };
    }
}
