package common

import (
	"encoding/json"
	"time"

	"github.com/pkg/errors"
)

type CarState struct {
	Online                bool                `json:"online"`
	OnlineHMI             bool                `json:"online_hmi"`
	Battery               *Battery            `json:"battery,omitempty"`
	MaxRange              *MaxRange           `json:"max_range,omitempty"`
	Doors                 *Doors              `json:"doors,omitempty"`
	Location              *Location           `json:"location,omitempty"`
	Locks                 *Locks              `json:"door_locks,omitempty"`
	Windows               *Windows            `json:"windows,omitempty"`
	MiscWindows           *MiscWindows        `json:"misc_windows,omitempty"`
	Sunroof               *Sunroof            `json:"sunroof,omitempty"`
	CabinClimate          *CabinClimate       `json:"cabin_climate,omitempty"`
	RearDefrost           *RearDefrost        `json:"rear_defrost,omitempty"`
	DriverSeatHeat        *DriverSeatHeat     `json:"driver_seat_heat,omitempty"`
	PassengerSeatHeat     *PassengerSeatHeat  `json:"passenger_seat_heat,omitempty"`
	SteeringWheelHeat     *SteeringWheelHeat  `json:"steering_wheel_heat,omitempty"`
	AmbientTemperature    *AmbientTemperature `json:"ambient_temperature,omitempty"`
	CellTemperature       *CellTemperature    `json:"cell_temp,omitempty"`
	VehicleSpeed          *VehicleSpeed       `json:"vehicle_speed,omitempty"`
	VCU0x260              *VCU0x260Descriptor `json:"vcu0x260,omitempty"`
	ChargingMetrics       *VCUChargingMetrics `json:"charging_metrics,omitempty"`
	Gear                  *Gear               `json:"gear,omitempty"`
	StateOfCharge         *StateOfCharge      `json:"state_of_charge,omitempty"`
	TRexVersion           string              `json:"trex_version,omitempty"`
	DBCVersion            string              `json:"dbc_version,omitempty"`
	IP                    string              `json:"ip,omitempty"`
	UpdatedAt             *time.Time          `json:"updated,omitempty"`
	SafeState             *SafeState          `json:"safe_state,omitempty"`
	DriverOccupySeatState *int                `json:"driver_occupy_seat_state,omitempty"`
	PowerMode             *int                `json:"power_mode,omitempty"`
	ChargingStatus        *int                `json:"charging_status,omitempty"`
	VehicleReadyState     *VehicleReadyState  `json:"vehicle_ready_state,omitempty"`
	Battery12V            *Battery12V         `json:"battery_12v,omitempty"` // maybe make it Battery12v
	ExpandedSignals       *ExpandedSignals    `json:"expanded_signals,omitempty"`
}

func (c *CarState) GetBattery() *Battery {
	if c.Battery == nil {
		c.Battery = &Battery{}
	}

	return c.Battery
}

func (c *CarState) GetMaxRange() *MaxRange {
	if c.MaxRange == nil {
		c.MaxRange = &MaxRange{}
	}

	return c.MaxRange
}

func (c *CarState) GetDoors() *Doors {
	if c.Doors == nil {
		c.Doors = &Doors{}
	}

	return c.Doors
}

func (c *CarState) UpdateLocation(value []byte) error {
	loc := Location{}
	err := loc.Unmarshal(value)
	if err != nil {
		return err
	}

	location := c.GetLocation()
	location.Latitude = loc.Latitude
	location.Longitude = loc.Longitude

	// Altitude could have already been set from GPS_ALTITUDE so do not overwrite unless it is a non-zero
	if loc.Altitude != 0 {
		location.Altitude = loc.Altitude
	}

	return nil
}

func (c *CarState) GetLocation() *Location {
	if c.Location == nil {
		c.Location = &Location{}
	}

	return c.Location
}

func (c *CarState) GetLocks() *Locks {
	if c.Locks == nil {
		c.Locks = &Locks{}
	}

	return c.Locks
}

func (c *CarState) GetWindows() *Windows {
	if c.Windows == nil {
		c.Windows = &Windows{}
	}

	return c.Windows
}

func (c *CarState) GetMiscWindows() *MiscWindows {
	if c.MiscWindows == nil {
		c.MiscWindows = &MiscWindows{}
	}

	return c.MiscWindows
}

func (c *CarState) GetSunroof() *Sunroof {
	if c.Sunroof == nil {
		c.Sunroof = &Sunroof{}
	}

	return c.Sunroof
}

func (c *CarState) GetCabinClimate() *CabinClimate {
	if c.CabinClimate == nil {
		c.CabinClimate = &CabinClimate{}
	}

	return c.CabinClimate
}

func (c *CarState) GetRearDefrost() *RearDefrost {
	if c.RearDefrost == nil {
		c.RearDefrost = &RearDefrost{}
	}

	return c.RearDefrost
}

func (c *CarState) GetDriverSeatHeat() *DriverSeatHeat {
	if c.DriverSeatHeat == nil {
		c.DriverSeatHeat = &DriverSeatHeat{}
	}

	return c.DriverSeatHeat
}

func (c *CarState) GetPassengerSeatHeat() *PassengerSeatHeat {
	if c.PassengerSeatHeat == nil {
		c.PassengerSeatHeat = &PassengerSeatHeat{}
	}

	return c.PassengerSeatHeat
}

func (c *CarState) GetSteeringWheelHeat() *SteeringWheelHeat {
	if c.SteeringWheelHeat == nil {
		c.SteeringWheelHeat = &SteeringWheelHeat{}
	}

	return c.SteeringWheelHeat
}

func (c *CarState) GetAmbientTemperature() *AmbientTemperature {
	if c.AmbientTemperature == nil {
		c.AmbientTemperature = &AmbientTemperature{}
	}

	return c.AmbientTemperature
}

func (c *CarState) GetCellTemperature() *CellTemperature {
	if c.CellTemperature == nil {
		c.CellTemperature = &CellTemperature{}
	}

	return c.CellTemperature
}

func (c *CarState) GetVCU0x260() *VCU0x260Descriptor {
	if c.VCU0x260 == nil {
		c.VCU0x260 = &VCU0x260Descriptor{}
	}

	return c.VCU0x260
}

func (c *CarState) GetChargingMetrics() *VCUChargingMetrics {
	if c.ChargingMetrics == nil {
		c.ChargingMetrics = &VCUChargingMetrics{}
	}

	return c.ChargingMetrics
}

func (c *CarState) GetGear() *Gear {
	if c.Gear == nil {
		c.Gear = &Gear{}
	}

	return c.Gear
}

func (c *CarState) GetStateOfCharge() *StateOfCharge {
	if c.StateOfCharge == nil {
		c.StateOfCharge = &StateOfCharge{}
	}

	return c.StateOfCharge
}

func (c *CarState) GetVehicleSpeed() *VehicleSpeed {
	if c.VehicleSpeed == nil {
		c.VehicleSpeed = &VehicleSpeed{}
	}

	return c.VehicleSpeed
}

// Battery represents the battery state of the car
type Battery struct {
	Percent              int     `json:"percent" redis:"percent"`
	TotalMileageOdometer int     `json:"total_mileage_odometer"`
	BatteryVoltage       float64 `json:"battery_voltage"` // The fact that this is called battery voltage is really dumb. Its the 12 volt battery, not the high voltage one
}

func (b *Battery) Marshal() ([]byte, error) {
	data, err := json.Marshal(*b)
	return data, errors.WithStack(err)
}

func (b *Battery) Unmarshal(data []byte) error {
	err := json.Unmarshal(data, b)
	return errors.WithStack(err)
}

// StateOfCharge represents the battery state of charge
type StateOfCharge struct {
	Usable int `json:"usable"`
	Health int `json:"health"`
}

type SafeState struct {
	VehicleSafeState      bool `json:"vehicle_safe_state" redis:"vehicle_safe_state"`
	VCUSafeState          bool `json:"vcu_safe_state" redis:"vcu_safe_state"`
	MCUFrontSafeState     bool `json:"mcu_front_safe_state" redis:"mcu_front_safe_state"`
	MCURearSafeState      bool `json:"mcu_rear_safe_state" redis:"mcu_rear_safe_state"`
	MCURearDecoupState    bool `json:"mcu_rear_decoup_state" redis:"mcu_rear_decoup_state"`
	MCUFrontInverterError bool `json:"mcu_front_inverter_error" redis:"mcu_front_inverter_error"`
	MCURearInverterError  bool `json:"mcu_rear_inverter_error" redis:"mcu_rear_inverter_error"`
}

func (s *SafeState) Marshal() ([]byte, error) {
	data, err := json.Marshal(*s)
	return data, errors.WithStack(err)
}

func (s *SafeState) Unmarshal(data []byte) error {
	err := json.Unmarshal(data, s)
	return errors.WithStack(err)
}

func (c *CarState) GetSafeState() *SafeState {
	if c.SafeState == nil {
		c.SafeState = &SafeState{}
	}

	return c.SafeState
}

// MaxRange represents the predicted max range of the car
type MaxRange struct {
	MaxMiles int `json:"max_miles" redis:"max_miles"`
}

type VehicleReadyState struct {
	IsVehicleReady bool `json:"is_vehicle_ready" redis:"is_vehicle_ready"`
}

func (c *CarState) GetVehicleReadyState() *VehicleReadyState {
	if c.VehicleReadyState == nil {
		c.VehicleReadyState = &VehicleReadyState{}
	}
	return c.VehicleReadyState
}

func (b *MaxRange) Marshal() ([]byte, error) {
	data, err := json.Marshal(*b)
	return data, errors.WithStack(err)
}

func (b *MaxRange) Unmarshal(data []byte) error {
	err := json.Unmarshal(data, b)
	return errors.WithStack(err)
}

// Doors represents the doors state of the car
// false means closed, true means open
type Doors struct {
	Hood       bool `json:"hood" redis:"hood"`
	LeftFront  bool `json:"left_front" redis:"left_front"`
	LeftRear   bool `json:"left_rear" redis:"left_rear"`
	RightFront bool `json:"right_front" redis:"right_front"`
	RightRear  bool `json:"right_rear" redis:"right_rear"`
	Trunk      bool `json:"trunk" redis:"trunk"`
}

func (d *Doors) Marshal() ([]byte, error) {
	data, err := json.Marshal(*d)
	return data, errors.WithStack(err)
}

func (d *Doors) Unmarshal(data []byte) error {
	err := json.Unmarshal(data, d)
	return errors.WithStack(err)
}

// Locks represents the lock state of the car
type Locks struct {
	Driver bool `json:"driver" redis:"driver"`
	All    bool `json:"all" redis:"all"`
}

func (l *Locks) Marshal() ([]byte, error) {
	data, err := json.Marshal(*l)
	return data, errors.WithStack(err)
}

func (l *Locks) Unmarshal(data []byte) error {
	err := json.Unmarshal(data, l)
	return errors.WithStack(err)
}

// Windows represents the windows state of the car
//
//	value is a percentage 0-100 in increments of 0.5
type Windows struct {
	LeftFront  int `json:"left_front" redis:"left_front"`
	LeftRear   int `json:"left_rear" redis:"left_rear"`
	RightFront int `json:"right_front" redis:"right_front"`
	RightRear  int `json:"right_rear" redis:"right_rear"`
}

func (w *Windows) Marshal() ([]byte, error) {
	data, err := json.Marshal(*w)
	return data, errors.WithStack(err)
}

func (w *Windows) Unmarshal(data []byte) error {
	err := json.Unmarshal(data, w)
	return errors.WithStack(err)
}

// MiscWindows represents the windows state of the car for misc windows (left / right rear quarter and rear windshield)
// value is a percentage 0-100 in increments of 0.5
type MiscWindows struct {
	LeftRearQuarter  int `json:"left_rear_quarter" redis:"left_rear_quarter"`
	RightRearQuarter int `json:"right_rear_quarter" redis:"right_rear_quarter"`
	RearWindshield   int `json:"rear_windshield" redis:"rear_windshield"`
}

func (w *MiscWindows) Marshal() ([]byte, error) {
	data, err := json.Marshal(*w)
	return data, errors.WithStack(err)
}

func (w *MiscWindows) Unmarshal(data []byte) error {
	err := json.Unmarshal(data, w)
	return errors.WithStack(err)
}

type Sunroof struct {
	Sunroof int `json:"sunroof" redis:"sunroof"`
}

func (s *Sunroof) Marshal() ([]byte, error) {
	data, err := json.Marshal(*s)
	return data, errors.WithStack(err)
}

func (s *Sunroof) Unmarshal(data []byte) error {
	err := json.Unmarshal(data, s)
	return errors.WithStack(err)
}

type CabinClimate struct {
	CabinTemperature    int `json:"cabin_temperature" redis:"cabin_temperature"`
	InternalTemperature int `json:"internal_temperature" redis:"internal_temperature"`
}

func (w *CabinClimate) Marshal() ([]byte, error) {
	data, err := json.Marshal(*w)
	return data, errors.WithStack(err)
}

func (w *CabinClimate) Unmarshal(data []byte) error {
	err := json.Unmarshal(data, w)
	return errors.WithStack(err)
}

type RearDefrost struct {
	On bool `json:"on" redis:"on"`
}

func (w *RearDefrost) Marshal() ([]byte, error) {
	data, err := json.Marshal(*w)
	return data, errors.WithStack(err)
}

func (w *RearDefrost) Unmarshal(data []byte) error {
	err := json.Unmarshal(data, w)
	return errors.WithStack(err)
}

type DriverSeatHeat struct {
	Level int `json:"level" redis:"level"`
}

func (w *DriverSeatHeat) Marshal() ([]byte, error) {
	data, err := json.Marshal(*w)
	return data, errors.WithStack(err)
}

func (w *DriverSeatHeat) Unmarshal(data []byte) error {
	err := json.Unmarshal(data, w)
	return errors.WithStack(err)
}

type PassengerSeatHeat struct {
	Level int `json:"level" redis:"level"`
}

func (w *PassengerSeatHeat) Marshal() ([]byte, error) {
	data, err := json.Marshal(*w)
	return data, errors.WithStack(err)
}

func (w *PassengerSeatHeat) Unmarshal(data []byte) error {
	err := json.Unmarshal(data, w)
	return errors.WithStack(err)
}

type SteeringWheelHeat struct {
	On bool `json:"on" redis:"on"`
}

func (w *SteeringWheelHeat) Marshal() ([]byte, error) {
	data, err := json.Marshal(*w)
	return data, errors.WithStack(err)
}

func (w *SteeringWheelHeat) Unmarshal(data []byte) error {
	err := json.Unmarshal(data, w)
	return errors.WithStack(err)
}

type AmbientTemperature struct {
	Temperature int `json:"temperature" redis:"temperature"`
}

func (w *AmbientTemperature) Marshal() ([]byte, error) {
	data, err := json.Marshal(*w)
	return data, errors.WithStack(err)
}

func (w *AmbientTemperature) Unmarshal(data []byte) error {
	err := json.Unmarshal(data, w)
	return errors.WithStack(err)
}

type VehicleSpeed struct {
	Speed float64 `json:"speed" redis:"speed"`
}

func (w *VehicleSpeed) Marshal() ([]byte, error) {
	data, err := json.Marshal(*w)
	return data, errors.WithStack(err)
}

func (w *VehicleSpeed) Unmarshal(data []byte) error {
	err := json.Unmarshal(data, w)
	return errors.WithStack(err)
}

type CellTemperature struct {
	AvgBatteryTemp int `json:"avg_battery_temp"`
}

type VCU0x260Descriptor struct {
	ChargeType string `json:"charge_type"`
}

type VCUChargingMetrics struct {
	RemainingChargingTime     int `json:"remaining_charging_time"`
	RemainingChargingTimeFull int `json:"remaining_charging_time_full"`
}

type Gear struct {
	InPark bool `json:"in_park"`
	Immobilizer string `json:"immobilizer,omitempty"`
}

type Battery12V struct {
	IBS_BatteryVoltage *float64 `json:"voltage,omitempty"`        // 12 Volt battery voltage
	IBS_StateOfCharge  *float64 `json:"percent_charge,omitempty"` // Percentages of the voltage out of about 15.5 Volts
	IBS_StateOfHealth  *int     `json:"health,omitempty"`         // estimated health of the 12v battery
}

func (c *CarState) GetBattery12V() *Battery12V {
	if c.Battery12V == nil {
		c.Battery12V = &Battery12V{}
	}

	return c.Battery12V
}

type ExpandedSignals struct {
	// IBS_SOCUpperTolerance *float64 //unconfirmed
	// IBS_SOCLowerTolerance *float64 //unconfirmed
	IBS_NominalCapacity   *int     `json:",omitempty"`
	IBS_AvailableCapacity *int     `json:",omitempty"`
	BCM_TotMilg_ODO       *float64 `json:",omitempty"`
	BMS_SwVersS           *int     `json:",omitempty"`
	BMS_SwVersM           *int     `json:",omitempty"`
	BMS_SwVers            *int     `json:",omitempty"`
	BMS_AccueDchaTotAh    *int     `json:",omitempty"`
	BMS_AccueChrgTotAh    *int     `json:",omitempty"`
}

func (c *CarState) GetExpandedSignals() *ExpandedSignals {
	if c.ExpandedSignals == nil {
		c.ExpandedSignals = &ExpandedSignals{}
	}

	return c.ExpandedSignals
}

// I am quite certain there is no reason to have these custom marshalers, but want to keep with the current form incase of unexpected side effects
func (w *ExpandedSignals) Marshal() ([]byte, error) {
	data, err := json.Marshal(*w)
	return data, errors.WithStack(err)
}

func (w *ExpandedSignals) Unmarshal(data []byte) error {
	err := json.Unmarshal(data, w)
	return errors.WithStack(err)
}
