// battery
// Copyright (C) 2016-2017,2023 Karol 'Kenji Takahashi' Woźniak
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
// OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

package battery

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"math"
	"os/exec"
	"strconv"
)

var errValueNotFound = fmt.Errorf("Value not found")

func readFloat(val string) (float64, error) {
	num, err := strconv.ParseFloat(val, 64)
	if err != nil {
		return 0, err
	}
	if num == math.MaxUint32 {
		return 0, fmt.Errorf("Unknown value received")
	}
	return num, nil
}

func readVoltage(val string) (float64, error) {
	voltage, err := readFloat(val)
	if err != nil {
		return 0, err
	}
	return voltage / 1000, nil
}

func readState(val string) (AgnosticState, error) {
	state, err := strconv.Atoi(val)
	if err != nil {
		return Unknown, err
	}

	switch {
	case state&1 != 0:
		return Discharging, nil
	case state&2 != 0:
		return Charging, nil
	case state&4 != 0:
		return Empty, nil
	default:
		return Undefined, nil
	}
}

type errParse int

func (p errParse) Error() string {
	return fmt.Sprintf("Parse error: `%d`", p)
}

type batteryReader struct {
	cmdout *bufio.Scanner
	li     int
	lline  []byte
	e      ErrPartial
}

func (r *batteryReader) setErrParse(n int) {
	if r.e.Design == errValueNotFound {
		r.e.Design = errParse(n)
	}
	if r.e.Full == errValueNotFound {
		r.e.Full = errParse(n)
	}
	if r.e.Current == errValueNotFound {
		r.e.Current = errParse(n)
	}
	if r.e.ChargeRate == errValueNotFound {
		r.e.ChargeRate = errParse(n)
	}
	if r.e.State == errValueNotFound {
		r.e.State = errParse(n)
	}
	if r.e.Voltage == errValueNotFound {
		r.e.Voltage = errParse(n)
	}
	if r.e.DesignVoltage == errValueNotFound {
		r.e.DesignVoltage = errParse(n)
	}
}

func (r *batteryReader) readValue() (string, string, int) {
	var piece []byte
	if r.lline != nil {
		piece = r.lline
		r.lline = nil
	} else {
		pieces := bytes.Split(r.cmdout.Bytes(), []byte{':'})
		if len(pieces) < 4 {
			return "", "", 4
		}

		i, err := strconv.Atoi(string(pieces[1]))
		if err != nil {
			return "", "", 1
		}

		if i != r.li {
			r.li = i
			r.lline = pieces[3]
			return "", "", 666
		}

		piece = pieces[3]
	}

	values := bytes.Split(piece, []byte{'\t'})
	if len(values) < 2 {
		return "", "", 2
	}
	return string(values[0]), string(values[1]), 0
}

func (r *batteryReader) readBattery() (*Battery, bool, bool) {
	b := &Battery{}
	var exists, amps bool

	for r.cmdout.Scan() {
		exists = true

		name, value, errno := r.readValue()
		if errno == 666 {
			break
		}
		if errno != 0 {
			r.setErrParse(errno)
			continue
		}

		switch name {
		case "bif_design_cap":
			b.Design, r.e.Design = readFloat(value)
		case "bif_last_cap":
			b.Full, r.e.Full = readFloat(value)
		case "bif_unit":
			amps = value != "0"
		case "bif_voltage":
			b.DesignVoltage, r.e.DesignVoltage = readVoltage(value)
		case "bst_voltage":
			b.Voltage, r.e.Voltage = readVoltage(value)
		case "bst_rem_cap":
			b.Current, r.e.Current = readFloat(value)
		case "bst_rate":
			b.ChargeRate, r.e.ChargeRate = readFloat(value)
		case "bst_state":
			b.State.Raw, r.e.State = readState(value)
			b.State.specific = value
		}
	}

	return b, amps, exists
}

func (r *batteryReader) next() (*Battery, error) {
	r.e = ErrPartial{
		Design:        errValueNotFound,
		Full:          errValueNotFound,
		Current:       errValueNotFound,
		ChargeRate:    errValueNotFound,
		State:         errValueNotFound,
		Voltage:       errValueNotFound,
		DesignVoltage: errValueNotFound,
	}

	b, amps, exists := r.readBattery()

	if !exists {
		return nil, io.EOF
	}

	if r.e.DesignVoltage != nil && r.e.Voltage == nil {
		b.DesignVoltage, r.e.DesignVoltage = b.Voltage, nil
	}

	if amps {
		if r.e.DesignVoltage == nil {
			b.Design *= b.DesignVoltage
		} else {
			r.e.Design = r.e.DesignVoltage
		}
		if r.e.Voltage == nil {
			b.Full *= b.Voltage
			b.Current *= b.Voltage
			b.ChargeRate *= b.Voltage
		} else {
			r.e.Full = r.e.Voltage
			r.e.Current = r.e.Voltage
			r.e.ChargeRate = r.e.Voltage
		}
	}

	if b.State.Raw == Unknown && r.e.Current == nil && r.e.Full == nil && b.Current >= b.Full {
		b.State.Raw = Full
		b.State.specific = fmt.Sprintf("current >= full")
	}

	return b, r.e
}

func newBatteryReader() (*batteryReader, error) {
	out, err := exec.Command("kstat", "-p", "-m", "acpi_drv", "-n", "battery B*").Output()
	if err != nil {
		return nil, err
	}

	return &batteryReader{cmdout: bufio.NewScanner(bytes.NewReader(out))}, nil
}

func systemGet(idx int) (*Battery, error) {
	br, err := newBatteryReader()
	if err != nil {
		return nil, err
	}

	return br.next()
}

func systemGetAll() ([]*Battery, error) {
	br, err := newBatteryReader()
	if err != nil {
		return nil, err
	}

	var batteries []*Battery
	var errors Errors
	for b, e := br.next(); e != io.EOF; b, e = br.next() {
		batteries = append(batteries, b)
		errors = append(errors, e)
	}

	return batteries, errors
}
