#!/usr/bin/env python

# BSD 3-Clause License; see https://github.com/scikit-hep/uproot-methods/blob/master/LICENSE

import math
import numbers
import operator

import awkward.array.chunked
import awkward.array.jagged
import awkward.array.objects
import awkward.util

import uproot_methods.base
import uproot_methods.common.TVector
import uproot_methods.classes.TVector3

class Common(object):
    @property
    def E(self):
        return self.t

    def dot(self, other):
        out = self.t*other.t
        out = out - self.x*other.x
        out = out - self.y*other.y
        out = out - self.z*other.z
        return out

    @property
    def energy(self):
        return self.t

    @property
    def p(self):
        return self.p3.mag

    @property
    def p2(self):
        return self.p3.mag2

    @property
    def perp2(self):
        return self.p3.rho2

    @property
    def perp(self):
        return self.p3.rho

    @property
    def pt2(self):
        return self.p3.rho2

    @property
    def Et(self):
        return self.energy * self.pt / self.p

    @property
    def mag2(self):
        return self.dot(self)

    @property
    def mass2(self):
        return self.mag2

    @property
    def mt2(self):
        return self.energy**2 - self.z**2

    @property
    def theta(self):
        return self.p3.theta

    @property
    def cottheta(self):
        return self.p3.cottheta

    @property
    def beta(self):
        return self.p / self.energy

    def delta_phi(self, other):
        return (self.phi - other.phi + math.pi) % (2*math.pi) - math.pi

    def delta_r2(self, other):
        return (self.eta - other.eta)**2 + self.delta_phi(other)**2

    def _rotate_axis(self, axis, angle):
        if not isinstance(axis, uproot_methods.classes.TVector3.Common):
            raise TypeError("axis must be an (array of) TVector3")
        p3 = self.p3._rotate_axis(axis, angle)
        return p3, self.t

    def _rotate_euler(self, phi, theta, psi):
        return self.p3._rotate_euler(phi, theta, psi), self.t

    def rotatex(self, angle):
        return self.rotate_axis(uproot_methods.classes.TVector3.TVector3(1.0, 0.0, 0.0), angle)

    def rotatey(self, angle):
        return self.rotate_axis(uproot_methods.classes.TVector3.TVector3(0.0, 1.0, 0.0), angle)

    def rotatez(self, angle):
        return self.rotate_axis(uproot_methods.classes.TVector3.TVector3(0.0, 0.0, 1.0), angle)

    def isspacelike(self, tolerance=1e-10):
        return self.mag2 < -tolerance

    def istimelike(self, tolerance=1e-10):
        return self.mag2 > tolerance

    def __lt__(self, other):
        raise TypeError("Lorentz vectors have no natural ordering")

    def __gt__(self, other):
        raise TypeError("Lorentz vectors have no natural ordering")

    def __le__(self, other):
        raise TypeError("Lorentz vectors have no natural ordering")

    def __ge__(self, other):
        raise TypeError("Lorentz vectors have no natural ordering")

class ArrayMethods(Common, uproot_methods.base.ROOTMethods):
    def _initObjectArray(self, table):
        self.awkward.ObjectArray.__init__(self, table, lambda row: TLorentzVector(row["fX"], row["fY"], row["fZ"], row["fE"]))

    def __awkward_serialize__(self, serializer):
        self._valid()
        x, y, z, t = self.x, self.y, self.z, self.t
        return serializer.encode_call(
            ["uproot_methods.classes.TLorentzVector", "TLorentzVectorArray", "from_cartesian"],
            serializer(x, "TLorentzVectorArray.x"),
            serializer(y, "TLorentzVectorArray.y"),
            serializer(z, "TLorentzVectorArray.z"),
            serializer(t, "TLorentzVectorArray.t"))

    @staticmethod
    def _wrapmethods(node, awkwardlib):
        if isinstance(node, awkward.array.chunked.ChunkedArray):
            node.__class__ = type("ChunkedArrayMethods", (awkwardlib.ChunkedArray, uproot_methods.classes.TVector3.ArrayMethods), {})
            for chunk in node.chunks:
                ArrayMethods._wrapmethods(chunk, awkwardlib)
        elif isinstance(node, awkward.array.jagged.JaggedArray):
            node.__class__ = type("JaggedArrayMethods", (awkwardlib.JaggedArray, uproot_methods.classes.TVector3.ArrayMethods), {})
            ArrayMethods._wrapmethods(node.content, awkwardlib)
        elif isinstance(node, awkward.array.objects.ObjectArray):
            node.__class__ = type("ObjectArrayMethods", (awkwardlib.ObjectArray, uproot_methods.classes.TVector3.ArrayMethods), {})
        
    @property
    def p3(self):
        out = self.empty_like(generator=lambda row: uproot_methods.classes.TVector3.TVector3(row["fX"], row["fY"], row["fZ"]))
        ArrayMethods._wrapmethods(out, self.awkward)
        out["fX"] = self.x
        out["fY"] = self.y
        out["fZ"] = self.z
        return out

    @property
    def x(self):
        return self["fX"]

    @property
    def y(self):
        return self["fY"]

    @property
    def z(self):
        return self["fZ"]

    @property
    def t(self):
        return self["fE"]

    @property
    def pt(self):
        return self._trymemo("pt", lambda self: self.awkward.numpy.sqrt(self.pt2))

    @property
    def eta(self):
        return self._trymemo("eta", lambda self: self.awkward.numpy.arcsinh(self.z / self.awkward.numpy.sqrt(self.x**2 + self.y**2)))

    @property
    def phi(self):
        return self._trymemo("phi", lambda self: self.p3.phi)

    @property
    def mass(self):
        return self._trymemo("mass", lambda self: self.awkward.numpy.sqrt(self.mag2))

    @property
    def mag(self):
        return self.awkward.numpy.sqrt(self.mag2)

    @property
    def mt(self):
        mt2 = self.mt2
        sign = self.awkward.numpy.sign(mt2)
        return self.awkward.numpy.sqrt(self.awkward.numpy.absolute(mt2)) * sign

    @property
    def rapidity(self):
        return 0.5 * self.awkward.numpy.log((self.t + self.z) / (self.t - self.z))

    @property
    def unit(self):
        return self / self.awkward.numpy.sqrt(self.mag)

    @property
    def boostp3(self):
        out = self.empty_like(generator=lambda row: uproot_methods.classes.TVector3.TVector3(row["fX"], row["fY"], row["fZ"]))
        if isinstance(self, self.awkward.JaggedArray):
            out.__class__ = type("JaggedArrayMethods", (self.awkward.JaggedArray, uproot_methods.classes.TVector3.ArrayMethods), {})
        else:
            out.__class__ = type("ObjectArrayMethods", (self.awkward.ObjectArray, uproot_methods.classes.TVector3.ArrayMethods), {})
        out["fX"] = self.x / self.t
        out["fY"] = self.y / self.t
        out["fZ"] = self.z / self.t
        return out

    def boost(self, p3):
        if not isinstance(p3, (uproot_methods.classes.TVector3.ArrayMethods, uproot_methods.classes.TVector3.Methods)):
            raise TypeError("boost p3 must be an (array of) TVector3")

        b2 = p3.mag2
        gamma = (1 - b2)**(-0.5)
        gamma2 = self.awkward.numpy.zeros(b2.shape, dtype=self.awkward.numpy.float64)
        mask = (b2 != 0)
        gamma2[mask] = (gamma[mask] - 1) / b2[mask]
        del mask

        bp = self.p3.dot(p3)

        v = self.p3 + gamma2*bp*p3 + self.t*gamma*p3
        out = self.empty_like()
        out["fX"] = v.x
        out["fY"] = v.y
        out["fZ"] = v.z
        out["fE"] = gamma*(self.t + bp)
        return out

    @property
    def gamma(self):
        out = self.beta
        mask = (out < 1) & (out > -1)
        out[mask] = (1 - out[mask]**2)**(-0.5)
        out[~mask] = self.awkward.numpy.inf
        return out

    def delta_r(self, other):
        return self.awkward.numpy.sqrt(self.delta_r2(other))

    def rotate_axis(self, axis, angle):
        p3, t = self._rotate_axis(axis, angle)
        x, y, z = p3
        out = self.empty_like()
        out["fX"] = x
        out["fY"] = y
        out["fZ"] = z
        out["fE"] = t
        return out

    def rotate_euler(self, phi=0, theta=0, psi=0):
        p3, t = self._rotate_euler(phi, theta, psi)
        x, y, z = p3
        out = self.empty_like()
        out["fX"] = x
        out["fY"] = y
        out["fZ"] = z
        out["fE"] = t
        return out

    def islightlike(self, tolerance=1e-10):
        return self.awkward.numpy.absolute(self.mag2) < tolerance

    def sum(self):
        if isinstance(self, awkward.AwkwardArray) and self._util_hasjagged(self):
            return TLorentzVectorArray.from_cartesian(self.x.sum(), self.y.sum(), self.z.sum(), self.t.sum())
        else:
            return TLorentzVector(self.x.sum(), self.y.sum(), self.z.sum(), self.t.sum())

    def _to_cartesian(self):
        return TLorentzVectorArray.from_cartesian(self.x,self.y,self.z,self.t)

    def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
        if "out" in kwargs:
            raise NotImplementedError("in-place operations not supported")

        if method != "__call__":
            return NotImplemented

        inputs = list(inputs)
        for i in range(len(inputs)):
            if isinstance(inputs[i], self.awkward.numpy.ndarray) and inputs[i].dtype == self.awkward.numpy.dtype(object) and len(inputs[i]) > 0:
                idarray = self.awkward.numpy.frombuffer(inputs[i], dtype=self.awkward.numpy.uintp)
                if (idarray == idarray[0]).all():
                    inputs[i] = inputs[i][0]

        if ufunc is self.awkward.numpy.add or ufunc is self.awkward.numpy.subtract:
            if not all(isinstance(x, (ArrayMethods, Methods)) for x in inputs):
                raise TypeError("(arrays of) TLorentzVector can only be added to/subtracted from other (arrays of) TLorentzVector")
            cart_inputs = [x._to_cartesian() for x in inputs]
            out = cart_inputs[0].empty_like()
            out["fX"] = getattr(ufunc, method)(*[x.x for x in cart_inputs], **kwargs)
            out["fY"] = getattr(ufunc, method)(*[x.y for x in cart_inputs], **kwargs)
            out["fZ"] = getattr(ufunc, method)(*[x.z for x in cart_inputs], **kwargs)
            out["fE"] = getattr(ufunc, method)(*[x.t for x in cart_inputs], **kwargs)
            return out

        elif ufunc is self.awkward.numpy.power and len(inputs) >= 2 and isinstance(inputs[1], (numbers.Number, self.awkward.numpy.number)):
            if inputs[1] == 2:
                return self.mag2
            else:
                return self.mag2**(0.5*inputs[1])

        elif ufunc is self.awkward.numpy.absolute:
            return self.mag

        else:
            return super(ArrayMethods, self).__array_ufunc__(ufunc, method, *inputs, **kwargs)

JaggedArrayMethods = ArrayMethods.mixin(ArrayMethods, awkward.JaggedArray)

class PtEtaPhiMassArrayMethods(ArrayMethods):
    def _initObjectArray(self, table):
        self.awkward.ObjectArray.__init__(self, table, lambda row: PtEtaPhiMassLorentzVector(row["fPt"], row["fEta"], row["fPhi"], row["fMass"]))

    def __awkward_serialize__(self, serializer):
        self._valid()
        pt, eta, phi, mass = self.pt, self.eta, self.phi, self.mass
        return serializer.encode_call(
            ["uproot_methods.classes.TLorentzVector", "TLorentzVectorArray", "from_ptetaphim"],
            serializer(pt, "TLorentzVectorArray.pt"),
            serializer(eta, "TLorentzVectorArray.eta"),
            serializer(phi, "TLorentzVectorArray.phi"),
            serializer(mass, "TLorentzVectorArray.mass"))
    
    @property
    def x(self):
        return self._trymemo("x",lambda self: self.pt * self.awkward.numpy.cos(self.phi))
    
    @property
    def y(self):
        return self._trymemo("y",lambda self: self.pt * self.awkward.numpy.sin(self.phi))
    
    @property
    def z(self):
        return self._trymemo("z",lambda self: self.pt * self.awkward.numpy.sinh(self.eta))
    
    @property
    def t(self):
        return self._trymemo("t",lambda self: self.awkward.numpy.hypot(self.mass, self.p))

    @property
    def pt(self):
        return self["fPt"]
    
    @property
    def pt2(self):
        return self["fPt"]**2
    
    @property
    def perp(self):
        return self["fPt"]
    
    @property
    def perp2(self):
        return self["fPt"]**2
    
    @property
    def eta(self):
        return self["fEta"]
    
    @property
    def phi(self):
        return self["fPhi"]
    
    @property
    def mass(self):
        return self["fMass"]

    @property
    def mass2(self):
        return self["fMass"]**2
    
    @property
    def mag(self):
        return self["fMass"]

    @property
    def mag2(self):
        return self["fMass"]**2

    @property
    def mt(self):
        return self.awkward.numpy.sqrt(self.mt2)

    @property
    def mt2(self):
        return self["fMass"]**2 + self["fPt"]**2

    @property
    def p(self):
        return self._trymemo("p",lambda self: self["fPt"]*self.awkward.numpy.cosh(self["fEta"]))
    
    @property
    def p2(self):
        return self.p**2

    def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
        if "out" in kwargs:
            raise NotImplementedError("in-place operations not supported")

        if method != "__call__":
            return NotImplemented

        inputs = list(inputs)
        for i in range(len(inputs)):
            if isinstance(inputs[i], self.awkward.numpy.ndarray) and inputs[i].dtype == self.awkward.numpy.dtype(object) and len(inputs[i]) > 0:
                idarray = self.awkward.numpy.frombuffer(inputs[i], dtype=self.awkward.numpy.uintp)
                if (idarray == idarray[0]).all():
                    inputs[i] = inputs[i][0]

        if ufunc is self.awkward.numpy.multiply or ufunc is self.awkward.numpy.divide:
            if sum(isinstance(x, PtEtaPhiMassArrayMethods) for x in inputs) > 1:
                raise ValueError("cannot multiply or divide two PtEtaPhiMassArrayMethods")
            this_input = None
            for i in range(len(inputs)):
                if isinstance(inputs[i], PtEtaPhiMassArrayMethods) and not isinstance(inputs[i], self.awkward.JaggedArray) and this_input is None:
                    this_input = inputs[i]
                    inputs[i] = self.awkward.Table(fPt=inputs[i]['fPt'], fMass=inputs[i]['fMass'])

            out = super(PtEtaPhiMassArrayMethods, self).__array_ufunc__(ufunc, method, *inputs, **kwargs)
            if this_input is not None:
                out['fEta'] = this_input['fEta']
                out['fPhi'] = this_input['fPhi']
                out.__class__ = this_input.__class__
            return out

        else:
            return super(PtEtaPhiMassArrayMethods, self).__array_ufunc__(ufunc, method, *inputs, **kwargs)

PtEtaPhiMassJaggedArrayMethods = PtEtaPhiMassArrayMethods.mixin(PtEtaPhiMassArrayMethods, awkward.JaggedArray)

class Methods(Common, uproot_methods.base.ROOTMethods):
    _arraymethods = ArrayMethods
    
    @property
    def p3(self):
        return self._fP

    @property
    def x(self):
        return self._fP._fX

    @property
    def y(self):
        return self._fP._fY

    @property
    def z(self):
        return self._fP._fZ

    @property
    def t(self):
        return self._fE

    def _to_cartesian(self):
        return TLorentzVector(self.x,self.y,self.z,self.t)
    
    def __repr__(self):
        return "TLorentzVector(x={0:.5g}, y={1:.5g}, z={2:.5g}, t={3:.5g})".format(self._fP._fX, self._fP._fY, self._fP._fZ, self._fE)

    def __str__(self):
        return repr(self)

    def __eq__(self, other):
        return isinstance(other, Methods) and self.x == other.x and self.y == other.y and self.z == other.z and self.t == other.t

    def _scalar(self, operator, scalar, reverse=False):
        cart = self._to_cartesian()
        if not isinstance(scalar, (numbers.Number, self.awkward.numpy.number)):
            raise TypeError("cannot {0} a TLorentzVector with a {1}".format(operator.__name__, type(scalar).__name__))
        if reverse:
            return TLorentzVector(operator(scalar, cart.x), operator(scalar, cart.y), operator(scalar, cart.z), operator(scalar, cart.t))
        else:
            return TLorentzVector(operator(cart.x, scalar), operator(cart.y, scalar), operator(cart.z, scalar), operator(cart.t, scalar))

    def _vector(self, operator, vector, reverse=False):
        cart = self._to_cartesian()
        if not isinstance(vector, Methods):
            raise TypeError("cannot {0} a TLorentzVector with a {1}".format(operator.__name__, type(vector).__name__))
        if reverse:
            return TLorentzVector(operator(vector.x, cart.x), operator(vector.y, cart.y), operator(vector.z, cart.z), operator(vector.t, cart.t))
        else:
            return TLorentzVector(operator(cart.x, vector.x), operator(cart.y, vector.y), operator(cart.z, vector.z), operator(cart.t, vector.t))

    def _unary(self, operator):
        cart = self._to_cartesian()
        return TLorentzVector(operator(cart.x), operator(cart.y), operator(cart.z), operator(cart.t))

    @property
    def pt(self):
        return math.sqrt(self.pt2)

    @property
    def eta(self):
        return math.asinh(self.z / math.sqrt(self.x**2 + self.y**2))

    @property
    def phi(self):
        return self.p3.phi

    @property
    def mass(self):
        return math.sqrt(self.mag2)

    @property
    def mag(self):
        return math.sqrt(self.mag2)

    @property
    def mt(self):
        out = self.mt2
        if out >= 0:
            return math.sqrt(out)
        else:
            return -math.sqrt(out)

    @property
    def rapidity(self):
        return math.log((self.t + self.z) / (self.t - self.z)) / 2.0

    @property
    def unit(self):
        return self / math.sqrt(self.mag)

    @property
    def boostp3(self):
        return uproot_methods.classes.TVector3.TVector3(self.x/self.t, self.y/self.t, self.z/self.t)

    def boost(self, p3):
        if not isinstance(p3, uproot_methods.classes.TVector3.Methods):
            raise TypeError("boost p3 must be a TVector3")

        b2 = p3.mag2
        gamma = (1.0 - b2)**(-0.5)
        if b2 != 0:
            gamma2 = (gamma - 1.0) / b2
        else:
            gamma2 = 0.0

        bp = self.p3.dot(p3)
        v = self.p3 + gamma2*bp*p3 + gamma*p3*self.t
        return self.__class__(v.x, v.y, v.z, gamma*(self.t + bp))

    @property
    def gamma(self):
        out = self.beta
        if -1 < out < 1:
            return (1 - out**2)**(-0.5)
        else:
            return float("inf")

    def delta_r(self, other):
        return math.sqrt(self.delta_r2(other))

    def rotate_axis(self, axis, angle):
        p3, t = self._rotate_axis(axis, angle)
        x, y, z = p3
        return self.__class__(x, y, z, t)

    def rotate_euler(self, phi=0, theta=0, psi=0):
        p3, t = self._rotate_euler(phi, theta, psi)
        x, y, z = p3
        return self.__class__(x, y, z, t)

    def islightlike(self, tolerance=1e-10):
        return abs(self.mag2) < tolerance

    def __add__(self, other):
        return self._vector(operator.add, other)

    def __radd__(self, other):
        return self._vector(operator.add, other, True)

    def __sub__(self, other):
        return self._vector(operator.sub, other)

    def __rsub__(self, other):
        return self._vector(operator.sub, other, True)

    def __mul__(self, other):
        return self._scalar(operator.mul, other)

    def __rmul__(self, other):
        return self._scalar(operator.mul, other, True)

    def __div__(self, other):
        return self._scalar(operator.div, other)

    def __rdiv__(self, other):
        return self._scalar(operator.div, other, True)

    def __truediv__(self, other):
        return self._scalar(operator.truediv, other)

    def __rtruediv__(self, other):
        return self._scalar(operator.truediv, other, True)

    def __floordiv__(self, other):
        return self._scalar(operator.floordiv, other)

    def __rfloordiv__(self, other):
        return self._scalar(operator.floordiv, other, True)

    def __mod__(self, other):
        return self._scalar(operator.mod, other)

    def __rmod__(self, other):
        return self._scalar(operator.mod, other, True)

    def __divmod__(self, other):
        return self._scalar(operator.divmod, other)

    def __rdivmod__(self, other):
        return self._scalar(operator.divmod, other, True)

    def __pow__(self, other):
        if isinstance(other, (numbers.Number, self.awkward.numpy.number)):
            if other == 2:
                return self.mag2
            else:
                return self.mag2**(0.5*other)
        else:
            self._scalar(operator.pow, other)

    # no __rpow__

    def __lshift__(self, other):
        return self._scalar(operator.lshift, other)

    def __rlshift__(self, other):
        return self._scalar(operator.lshift, other, True)

    def __rshift__(self, other):
        return self._scalar(operator.rshift, other)

    def __rrshift__(self, other):
        return self._scalar(operator.rshift, other, True)

    def __and__(self, other):
        return self._scalar(operator.and_, other)

    def __rand__(self, other):
        return self._scalar(operator.and_, other, True)

    def __or__(self, other):
        return self._scalar(operator.or_, other)

    def __ror__(self, other):
        return self._scalar(operator.or_, other, True)

    def __xor__(self, other):
        return self._scalar(operator.xor, other)

    def __rxor__(self, other):
        return self._scalar(operator.xor, other, True)

    def __neg__(self):
        return self._unary(operator.neg)

    def __pos__(self):
        return self._unary(operator.pos)

    def __abs__(self):
        return self.mag

    def __invert__(self):
        return self._unary(operator.invert)

class PtEtaPhiMassMethods(Methods):
    _arraymethods = PtEtaPhiMassArrayMethods
    
    @property
    def pt(self):
        return self._fPt
    
    @property
    def eta(self):
        return self._fEta
    
    @property
    def phi(self):
        return self._fPhi
    
    @property
    def mass(self):
        return self._fMass
    
    @property
    def mag(self):
        return self._fMass
    
    @property
    def mag2(self):
        return self._fMass**2
    
    @property
    def mt(self):
        out = self.mt2
        if out >= 0:
            return math.sqrt(out)
        else:
            return -math.sqrt(out)
    
    @property
    def mt2(self):
        return self._fMass**2 + self._fPt**2
    
    @property
    def p3(self):
        return uproot_methods.classes.TVector3.TVector3(self.x, self.y, self.z)
    
    @property
    def x(self):
        return self.pt * self.awkward.numpy.cos(self.phi)
    
    @property
    def y(self):
        return self.pt * self.awkward.numpy.sin(self.phi)
    
    @property
    def z(self):
        return self.pt * self.awkward.numpy.sinh(self.eta)
    
    @property
    def t(self):
        x = self.x
        y = self.y
        z = self.z
        mass = self.mass
        return self.awkward.numpy.sqrt(x*x + y*y + z*z + mass*mass*self.awkward.numpy.sign(mass))
    
    def __repr__(self):
        return "PtEtaPhiMassLorentzVector(pt={0:.5g}, eta={1:.5g}, phi={2:.5g}, mass={3:.5g})".format(self._fPt, self._fEta, self._fPhi, self._fMass)

class PtEtaPhiMassLorentzVectorArray(PtEtaPhiMassArrayMethods, uproot_methods.base.ROOTMethods.awkward.ObjectArray):
    def __init__(self, pt, eta, phi, mass):
        if isinstance(pt, awkward.array.jagged.JaggedArray) or isinstance(eta, awkward.array.jagged.JaggedArray) or isinstance(phi, awkward.array.jagged.JaggedArray) or isinstance(mass, awkward.array.jagged.JaggedArray):
            raise TypeError("PtEtaPhiMassLorentzVectorArray constructor arguments must not be jagged; use TLorentzVectorArray.from_ptetaphim for jaggedness-handling")
        self._initObjectArray(self.awkward.Table())
        self["fPt"]   = pt
        self["fEta"]  = eta
        self["fPhi"]  = phi
        self["fMass"] = mass

    @property
    def pt(self):
        return self["fPt"]
    
    @pt.setter
    def pt(self, value):
        self["fPt"] = value

    @property
    def eta(self):
        return self["fEta"]
    
    @eta.setter
    def eta(self, value):
        self["fEta"] = value

    @property
    def phi(self):
        return self["fPhi"]
    
    @phi.setter
    def phi(self, value):
        self["fPhi"] = value

    @property
    def mass(self):
        return self["fMass"]
    
    @mass.setter
    def mass(self, value):
        self["fMass"] = value

class TLorentzVectorArray(ArrayMethods, uproot_methods.base.ROOTMethods.awkward.ObjectArray):

    def __init__(self, x, y, z, t):
        if isinstance(x, awkward.array.jagged.JaggedArray) or isinstance(y, awkward.array.jagged.JaggedArray) or isinstance(z, awkward.array.jagged.JaggedArray) or isinstance(t, awkward.array.jagged.JaggedArray):
            raise TypeError("TLorentzVectorArray constructor arguments must not be jagged; use TLorentzVectorArray.from_cartesian for jaggedness-handling")
        self._initObjectArray(self.awkward.Table())
        self["fX"] = x
        self["fY"] = y
        self["fZ"] = z
        self["fE"] = t

    @classmethod
    def origin(cls, shape, dtype=None):
        if dtype is None:
            dtype = cls.awkward.numpy.float64
        return cls(cls.awkward.numpy.zeros(shape, dtype=dtype),
                   cls.awkward.numpy.zeros(shape, dtype=dtype),
                   cls.awkward.numpy.zeros(shape, dtype=dtype),
                   cls.awkward.numpy.zeros(shape, dtype=dtype))

    @classmethod
    def origin_like(cls, array):
        return array * 0.0

    @classmethod
    def from_p3(cls, p3, t):
        return cls.from_cartesian(p3.x, p3.y, p3.z, t)

    @classmethod
    @awkward.util.wrapjaggedmethod(JaggedArrayMethods)
    def from_cartesian(cls, x, y, z, t):
        return cls(x, y, z, t)

    @classmethod
    @awkward.util.wrapjaggedmethod(JaggedArrayMethods)
    def from_spherical(cls, r, theta, phi, t):
        return cls.from_p3(uproot_methods.classes.TVector3.TVector3Array.from_spherical(r, theta, phi), t)

    @classmethod
    @awkward.util.wrapjaggedmethod(JaggedArrayMethods)
    def from_cylindrical(cls, rho, phi, z, t):
        return cls.from_p3(uproot_methods.classes.TVector3.TVector3Array.from_cylindrical(rho, phi, z), t)

    @classmethod
    @awkward.util.wrapjaggedmethod(JaggedArrayMethods)
    def from_xyzm(cls, x, y, z, m):
        return cls(x, y, z, cls.awkward.numpy.sqrt(x*x + y*y + z*z + m*m*cls.awkward.numpy.sign(m)))

    @classmethod
    @awkward.util.wrapjaggedmethod(JaggedArrayMethods)
    def from_ptetaphi(cls, pt, eta, phi, energy):
        out = cls(pt * cls.awkward.numpy.cos(phi),
                  pt * cls.awkward.numpy.sin(phi),
                  pt * cls.awkward.numpy.sinh(eta),
                  energy)
        out._memo_pt = pt
        out._memo_eta = eta
        out._memo_phi = phi
        return out

    @classmethod
    def from_ptetaphie(cls, pt, eta, phi, energy):
        return cls.from_ptetaphi(pt, eta, phi, energy)

    @classmethod
    @awkward.util.wrapjaggedmethod(PtEtaPhiMassJaggedArrayMethods)
    def from_ptetaphim(cls, pt, eta, phi, mass):
        return PtEtaPhiMassLorentzVectorArray(pt,eta,phi,mass)

    @property
    def x(self):
        return self["fX"]

    @x.setter
    def x(self, value):
        self["fX"] = value

    @property
    def y(self):
        return self["fY"]

    @y.setter
    def y(self, value):
        self["fY"] = value

    @property
    def z(self):
        return self["fZ"]

    @z.setter
    def z(self, value):
        self["fZ"] = value

    @property
    def t(self):
        return self["fE"]

    @t.setter
    def t(self, value):
        self["fE"] = value

    @property
    def E(self):
        return self["fE"]

    @E.setter
    def E(self, value):
        self["fE"] = value

class PtEtaPhiMassLorentzVector(PtEtaPhiMassMethods):
    def __init__(self, pt, eta, phi, mass):
        self._fPt   = float(pt)
        self._fEta  = float(eta)
        self._fPhi  = float(phi)
        self._fMass = float(mass)
    
    @property
    def pt(self):
        return self._fPt
    
    @pt.setter
    def pt(self,value):
        self._fPt = value
                                           
    @property
    def eta(self):
        return self._fEta
                                       
    @eta.setter
    def eta(self, value):
        self._fEta = value
                                       
    @property
    def phi(self):
        return self._fPhi
                                       
    @phi.setter
    def phi(self, value):
        self._fPhi = value
                                       
    @property
    def mass(self):
        return self._fMass
                                       
    @mass.setter
    def mass(self, value):
        self._fMass = value
                                       
class TLorentzVector(Methods):
    def __init__(self, x, y, z, t):
        self._fP = uproot_methods.classes.TVector3.TVector3(float(x), float(y), float(z))
        self._fE = float(t)

    @classmethod
    def origin(cls):
        return cls(0.0, 0.0, 0.0, 0.0)

    @classmethod
    def from_p3(cls, p3, t):
        out = cls.__new__(cls)
        out._fP = p3
        out._fE = t
        return out

    @classmethod
    def from_spherical(cls, r, theta, phi, t):
        return cls.from_p3(uproot_methods.classes.TVector3.Methods.from_spherical(r, theta, phi), t)

    @classmethod
    def from_cylindrical(cls, rho, phi, z, t):
        return cls.from_p3(uproot_methods.classes.TVector3.Methods.from_cylindrical(rho, phi, z), t)

    @classmethod
    def from_xyzm(cls, x, y, z, m):
        return cls(x, y, z, math.sqrt(x*x + y*y + z*z + m*m*(1 if m >= 0 else -1)))

    @classmethod
    def from_ptetaphi(cls, pt, eta, phi, energy):
        return cls(pt * math.cos(phi),
                   pt * math.sin(phi),
                   pt * math.sinh(eta),
                   energy)

    @classmethod
    def from_ptetaphie(cls, pt, eta, phi, energy):
        return cls.from_ptetaphi(pt, eta, phi, energy)
    
    @classmethod
    def from_ptetaphim(cls, pt, eta, phi, mass):
        return PtEtaPhiMassLorentzVector(pt,eta,phi,mass)

    @property
    def x(self):
        return self._fP._fX

    @x.setter
    def x(self, value):
        self._fP._fX = value

    @property
    def y(self):
        return self._fP._fY

    @y.setter
    def y(self, value):
        self._fP._fY = value

    @property
    def z(self):
        return self._fP._fZ

    @z.setter
    def z(self, value):
        self._fP._fZ = value

    @property
    def t(self):
        return self._fE

    @t.setter
    def t(self, value):
        self._fE = value

    @property
    def E(self):
        return self._fE

    @E.setter
    def E(self, value):
        self._fE = value