# The MIT License (MIT)
#
# Copyright (c) 2017 Scott Shawcroft for Adafruit Industries
# Copyright (c) 2018 Kattni Rembor for Adafruit Industries
#
# 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.
"""
`adafruit_motorkit`
====================================================

CircuitPython helper library for DC & Stepper Motor FeatherWing, Shield, and Pi Hat kits.

* Author(s): Scott Shawcroft, Kattni Rembor

Implementation Notes
--------------------

**Hardware:**

   "* `DC Motor + Stepper FeatherWing <https://www.adafruit.com/product/2927>`_"
   "* `Adafruit Motor/Stepper/Servo Shield for Arduino v2 Kit
   <https://www.adafruit.com/product/1438>`_"
   "* `Adafruit DC & Stepper Motor HAT for Raspberry Pi - Mini Kit
   <https://www.adafruit.com/product/2348>`_"

**Software and Dependencies:**

* Adafruit CircuitPython firmware for the supported boards:
  https://github.com/adafruit/circuitpython/releases

 * Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice
 * Adafruit's Register library: https://github.com/adafruit/Adafruit_CircuitPython_Register
 * Adafruit's PCA9685 library: https://github.com/adafruit/Adafruit_CircuitPython_PCA9685
 * Adafruit's Motor library: https://github.com/adafruit/Adafruit_CircuitPython_Motor

"""


import board
from adafruit_pca9685 import PCA9685

__version__ = "0.0.0-auto.0"
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_MotorKit.git"


class MotorKit:
    """Class representing an Adafruit DC & Stepper Motor FeatherWing, Shield or Pi Hat kit.

       Automatically uses the I2C bus on a Feather, Metro or Raspberry Pi."""

    def __init__(self, address=0x60, i2c=None, steppers_microsteps=16):
        self._motor1 = None
        self._motor2 = None
        self._motor3 = None
        self._motor4 = None
        self._stepper1 = None
        self._stepper2 = None
        if i2c is None:
            i2c = board.I2C()
        self._pca = PCA9685(i2c, address=address)
        self._pca.frequency = 1600
        self._steppers_microsteps = steppers_microsteps

    # We can save memory usage (~300 bytes) by deduplicating the construction of the objects for
    # each motor. This saves both code size and the number of raw strings (the error message)
    # stored. The same technique is a net loss for stepper because there is less duplication.
    def _motor(self, motor_name, channels, stepper_name):
        from adafruit_motor import motor  # pylint: disable=import-outside-toplevel

        motor_name = "_motor" + str(motor_name)
        stepper_name = "_stepper" + str(stepper_name)
        if not getattr(self, motor_name):
            if getattr(self, stepper_name):
                raise RuntimeError(
                    "Cannot use {} at the same time as {}.".format(
                        motor_name[1:], stepper_name[1:]
                    )
                )
            self._pca.channels[channels[0]].duty_cycle = 0xFFFF
            setattr(
                self,
                motor_name,
                motor.DCMotor(
                    self._pca.channels[channels[1]], self._pca.channels[channels[2]]
                ),
            )
        return getattr(self, motor_name)

    @property
    def motor1(self):
        """:py:class:``~adafruit_motor.motor.DCMotor`` controls for motor 1.

            The following image shows the location of the M1 terminal on the DC/Stepper FeatherWing.
            The label on the FeatherWing is found on the bottom of the board.
            The terminal is labeled on the top of the Shield and Pi Hat.

            .. image :: ../docs/_static/motor_featherwing/m1.jpg
              :alt: Motor 1 location

            This example moves the motor forwards for one fifth of a second at full speed.

            .. code-block:: python

                import time
                from adafruit_motorkit import motorkit

                kit = MotorKit()

                kit.motor1.throttle = 1.0
                time.sleep(0.2)

                kit.motor1.throttle = 0
        """
        return self._motor(1, (8, 9, 10), 1)

    @property
    def motor2(self):
        """:py:class:``~adafruit_motor.motor.DCMotor`` controls for motor 2.

            The following image shows the location of the M2 terminal on the DC/Stepper FeatherWing.
            The label on the FeatherWing is found on the bottom of the board.
            The terminal is labeled on the top of the Shield and Pi Hat.

            .. image :: ../docs/_static/motor_featherwing/m2.jpg
              :alt: Motor 2 location

            This example moves the motor forwards for one fifth of a second at full speed.

            .. code-block:: python

                import time
                from adafruit_motorkit import motorkit

                kit = MotorKit()

                kit.motor2.throttle = 1.0
                time.sleep(0.2)

                kit.motor1.throttle = 0
        """
        return self._motor(2, (13, 11, 12), 1)

    @property
    def motor3(self):
        """:py:class:``~adafruit_motor.motor.DCMotor`` controls for motor 3.

            The following image shows the location of the M2 terminal on the DC/Stepper FeatherWing.
            The label on the FeatherWing is found on the bottom of the board.
            The terminal is labeled on the top of the Shield and Pi Hat.

            .. image :: ../docs/_static/motor_featherwing/m3.jpg
              :alt: Motor 3 location

            This example moves the motor forwards for one fifth of a second at full speed.

            .. code-block:: python

                import time
                from adafruit_motorkit import motorkit

                kit = MotorKit()

                kit.motor3.throttle = 1.0
                time.sleep(0.2)

                kit.motor1.throttle = 0
        """
        return self._motor(3, (2, 3, 4), 2)

    @property
    def motor4(self):
        """:py:class:``~adafruit_motor.motor.DCMotor`` controls for motor 4.

            .. image :: ../docs/_static/motor_featherwing/m4.jpg
              :alt: Motor 4 location

            This example moves the motor forwards for one fifth of a second at full speed.

            .. code-block:: python

                import time
                from adafruit_motorkit import motorkit

                kit = MotorKit()

                kit.motor4.throttle = 1.0
                time.sleep(0.2)

                kit.motor1.throttle = 0
        """
        return self._motor(4, (7, 5, 6), 2)

    @property
    def stepper1(self):
        """:py:class:``~adafruit_motor.stepper.StepperMotor`` controls for one connected to stepper
           1 (also labeled motor 1 and motor 2).

            The following image shows the location of the stepper1 terminals on the DC/Stepper
            FeatherWing. stepper1 is made up of the M1 and M2 terminals.
            The labels on the FeatherWing are found on the bottom of the board.
            The terminals are labeled on the top of the Shield and Pi Hat.

            .. image :: ../docs/_static/motor_featherwing/stepper1.jpg
              :alt: Stepper 1 location

            This example moves the stepper motor 100 steps forwards.

            .. code-block:: python

                from adafruit_motorkit import MotorKit

                kit = MotorKit()

                for i in range(100):
                    kit.stepper1.onestep()
        """
        if not self._stepper1:
            from adafruit_motor import (  # pylint: disable=import-outside-toplevel
                stepper,
            )

            if self._motor1 or self._motor2:
                raise RuntimeError(
                    "Cannot use stepper1 at the same time as motor1 or motor2."
                )
            self._pca.channels[8].duty_cycle = 0xFFFF
            self._pca.channels[13].duty_cycle = 0xFFFF
            self._stepper1 = stepper.StepperMotor(
                self._pca.channels[10],
                self._pca.channels[9],
                self._pca.channels[11],
                self._pca.channels[12],
                microsteps=self._steppers_microsteps,
            )
        return self._stepper1

    @property
    def stepper2(self):
        """:py:class:``~adafruit_motor.stepper.StepperMotor`` controls for one connected to stepper
           2 (also labeled motor 3 and motor 4).

            The following image shows the location of the stepper2 terminals on the DC/Stepper
            FeatherWing. stepper2 is made up of the M3 and M4 terminals.
            The labels on the FeatherWing are found on the bottom of the board.
            The terminals are labeled on the top of the Shield and Pi Hat.

            .. image :: ../docs/_static/motor_featherwing/stepper2.jpg
              :alt: Stepper 2 location

            This example moves the stepper motor 100 steps forwards.

            .. code-block:: python

                from adafruit_motorkit import MotorKit

                kit = MotorKit()

                for i in range(100):
                    kit.stepper2.onestep()
        """
        if not self._stepper2:
            from adafruit_motor import (  # pylint: disable=import-outside-toplevel
                stepper,
            )

            if self._motor3 or self._motor4:
                raise RuntimeError(
                    "Cannot use stepper2 at the same time as motor3 or motor4."
                )
            self._pca.channels[7].duty_cycle = 0xFFFF
            self._pca.channels[2].duty_cycle = 0xFFFF
            self._stepper2 = stepper.StepperMotor(
                self._pca.channels[4],
                self._pca.channels[3],
                self._pca.channels[5],
                self._pca.channels[6],
                microsteps=self._steppers_microsteps,
            )
        return self._stepper2
