from pathlib import Path
import math

try:
    import orjson as json

except ImportError:
    import json


__all__ = ["UnitHandler"]

JSON_DIR = Path(__file__).parents[0] / "json/"


def load(system, overrides=(), abbrev=True):
    abbrev_ext = "-w_abbrev" if abbrev else ""

    def_file = JSON_DIR / f"{system}{abbrev_ext}.json"
    try:
        with open(def_file, "r") as f:
            defs = json.load(f)

    except FileNotFoundError:
        raise ValueError(f"Cannot find unit definition file {def_file}")

    return defs


class SpacedDimensions:
    def __init__(self, step, justification):
        self.justification = justification
        self.step = step

    def __matmul__(self, num):
        assert isinstance(num, int)
        if self.justification in ["c", "centered"]:
            begin = -sum([self.step] * num) / 2

        elif self.justification in ["l", "left"]:
            begin = 0.0

        return [begin + self.step * i for i in range(num + 1)]

    def __rmatmul__(self, num):
        return self.__matmul__(num)


class Dimension(float):
    def __mul__(self, other):
        try:
            out = float.__mul__(self, float(other))
        except:
            out = type(other)(
                [
                    # float mult if scalar, else recurse
                    float.__mul__(self, i) if isinstance(i, (float, int)) else other * i
                    for i in other
                ]
            )
        return out

    def __rmul__(self, other):
        return self * other


class UnitHandler:
    def __init__(self, base, abbrev=True):
        system, *overrides = base.split(", ")
        self.base_dims = load(system, overrides)

    def __getattr__(self, name):
        return Dimension(self.base_dims[name])

    def __repr__(self):
        return f"""<{', '.join(self.base_dims.keys())}>"""

    def dim(self, dim):
        feet, inches = map(float, dim.split("-"))
        return feet * self.foot + inches * self.inch

    def spacing(self, dim, justification="l"):
        if isinstance(dim, str):
            dim = self.dim(dim)
        return SpacedDimensions(dim, justification)

def spacing(dim, just):
    return SpacedDimensions(dim, just)


_Aliases = {
    "mm": "millimeter",
    "cm": "centimeter",
    "m": "meter",
    "km": "kilometer",
    "ft": "foot",
    "yd": "yard",
    "mi": "mile",

    "N": "newton",
    "dyn": "dyne",
    "pdl": "poundal",
    "kip": "kilopoundf",
    "lbf": "poundf",

    "lbm": "poundmass",
    "gm": "gram",
    "kg": "kilogram",
    "oz": "ounce",

    "Pa": "pascal",
    "torr": "torr",
    "kPa": "kilopascal",
    "bar": "bar",
    "atm": "atmosphere",
    "MPa": "megapascal",
    "GPa": "gigapascal",

    "Hz": "hertz",

    "rad": "radian",
    "deg": "degree",

    "sec": "second",
    "min": "minute",
    "hr": "hour",
    "wk": "week",
    "yr": "year",
}

class UnitSystem:
    #
    #  IPS 
    #
    Length: str
    Force : str
    Mass  : str

    _length = {
        "inch", "foot", "yard", "mile",
        "millimeter", "centimeter", "meter", "kilometer"
    }
    _force = {
        "poundf", "kilopoundf",
        "newton", "dyne", "poundal"
    }
    _mass = {
        "poundmass", "slug", ""
        "gram", "kilogram", "ounce"
    }


    #
    #  Length 
    #
    millimeter   : float         # Milimeter
    centimeter   : float         # Centimeter
    meter        : float         # Meter
    kilometer    : float         # Kilometer
    inch         : float
    foot         : float         # International foot
    yard         : float         # International yard
    mile         : float         # Mile

    #
    #  Angle 
    #
    radian       : float                 # Radian
    arcsec                      : float         # Arcsecond
    arcmin                      : float         # Arcminute
    degree       : float         # Degrees

    #
    #  Constants 
    #
    pi           : float   = math.pi      # Pi

    #
    #  Time 
    #
    second       : float
    minute       : float
    hour         : float
    day          : float
    week         : float
    year         : float

    #
    #  Force 
    #
    newton       : float         # Newton (force)
    dyne         : float         # Dyne
    poundal      : float         # Poundal
    poundf       : float
    kilopoundf   : float

    #
    #  Mass 
    #
    slug         : float
    poundmass    : float         # International avoirdupois pound
    gram         : float
    kilogram     : float         # Kilogram
    tonne        : float         # Metric tonne
    ounce        : float         # International avoirdupois ounce

    #
    #  Pressure 
    #
    pascal       : float         # Pascal
    torr         : float         
    kilopascal   : float         
    bar          : float         
    atmosphere   : float         # Standard atmosphere
    megapascal   : float         
    gigapascal   : float         
    psi          : float         # Pound-square-inch
    ksi          : float

    #
    #  Frequency
    #
    hertz        : float                 

    #
    #  Area 
    #
    mm2          : float         # Square millimeter
    m2           : float          # Square meter
    cm2          : float           # Square centimeter
    km2          : float        # Square kilometer
    in2          : float                 # Square inch
    ft2          : float               # Square foot
    yd2          : float              # Square yard
    mi2          : float        # Square mile

    #
    #  Volume 
    #
    mm3          : float         
    m3           : float         
    cm3          : float         
    km3          : float    
    in3          : float                 
    ft3          : float              
    cyd          : float
    mi3          : float

    #
    #  Velocity 
    #
    mmps         : float         # Millimeter per second
    cps          : float         # Centimeter per second
    mps          : float         # Meter per second
    kps          : float         # Kilometer per second
    ips          : float                # Inch per second
    fps          : float                # Foot per second
    yps          : float                # Yard per second
    mph          : float                # mile per hour

    #
    #  Acceleration 
    #
    mmps2      : float
    cps2       : float         
    mps2       : float         
    kps2       : float         
    ips2       : float                 
    fps2       : float                
    yps2       : float                
    mph2       : float         
    gravity    : float         # Standard gravity

    #
    #  Angular Velocity 
    #
    rpm        : float         # Revolution per minute
    radps      : float         # Radian per second

