// Copyright 2016 The Periph Authors. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.

// Package i2c defines the API to communicate with devices over the I²C
// protocol.
//
// As described in https://periph.io/x/conn/v3#hdr-Concepts, periph.io uses
// the concepts of Bus, Port and Conn.
//
// In the package i2c, 'Port' is not exposed, since once you know the I²C
// device address, there's no unconfigured Port to configure.
//
// Instead, the package includes the adapter 'Dev' to directly convert an I²C
// bus 'i2c.Bus' into a connection 'conn.Conn' by only specifying the device
// I²C address.
//
// See https://en.wikipedia.org/wiki/I%C2%B2C for more information.
package i2c

import (
	"errors"
	"io"
	"strconv"

	"periph.io/x/conn/v3"
	"periph.io/x/conn/v3/gpio"
	"periph.io/x/conn/v3/physic"
)

// Bus defines the interface a concrete I²C driver must implement.
//
// This interface is consummed by a device driver for a device sitting on a bus.
//
// This interface doesn't implement conn.Conn since a device address must be
// specified. Use i2cdev.Dev as an adapter to get a conn.Conn compatible
// object.
type Bus interface {
	String() string
	// Tx does a transaction at the specified device address.
	//
	// Write is done first, then read. One of 'w' or 'r' can be omitted for a
	// unidirectional operation.
	Tx(addr uint16, w, r []byte) error
	// SetSpeed changes the bus speed, if supported.
	//
	// On linux due to the way the I²C sysfs driver is exposed in userland,
	// calling this function will likely affect *all* I²C buses on the host.
	SetSpeed(f physic.Frequency) error
}

// BusCloser is an I²C bus that can be closed.
//
// This interface is meant to be handled by the application and not the device
// driver. A device driver doesn't "own" a bus, hence it must operate on a Bus,
// not a BusCloser.
type BusCloser interface {
	io.Closer
	Bus
}

// Pins defines the pins that an I²C bus interconnect is using on the host.
//
// It is expected that a implementer of Bus also implement Pins but this is not
// a requirement.
type Pins interface {
	// SCL returns the CLK (clock) pin.
	SCL() gpio.PinIO
	// SDA returns the DATA pin.
	SDA() gpio.PinIO
}

// Dev is a device on a I²C bus.
//
// It implements conn.Conn.
//
// It saves from repeatedly specifying the device address.
type Dev struct {
	Bus  Bus
	Addr uint16
}

func (d *Dev) String() string {
	s := "<nil>"
	if d.Bus != nil {
		s = d.Bus.String()
	}
	return s + "(" + strconv.Itoa(int(d.Addr)) + ")"
}

// Tx does a transaction by adding the device's address to each command.
//
// It's a wrapper for Bus.Tx().
func (d *Dev) Tx(w, r []byte) error {
	return d.Bus.Tx(d.Addr, w, r)
}

// Write writes to the I²C bus without reading, implementing io.Writer.
//
// It's a wrapper for Tx()
func (d *Dev) Write(b []byte) (int, error) {
	if err := d.Tx(b, nil); err != nil {
		return 0, err
	}
	return len(b), nil
}

// Duplex always return conn.Half for I²C.
func (d *Dev) Duplex() conn.Duplex {
	return conn.Half
}

// Addr is an I²C slave address.
type Addr uint16

// Set sets the Addr to a value represented by the string s. Values maybe in
// decimal or hexadecimal form. Set implements the flag.Value interface.
func (a *Addr) Set(s string) error {
	// Allow for only maximum of 10 bits for i2c addresses.
	u, err := strconv.ParseUint(s, 0, 10)
	if err != nil {
		return errI2CSetError
	}
	*a = Addr(u)
	return nil
}

// String returns an i2c.Addr as a string formated in hexadecimal.
func (a Addr) String() string {
	return "0x" + strconv.FormatInt(int64(a), 16)
}

var errI2CSetError = errors.New("invalid i2c address")

var _ conn.Conn = &Dev{}
