#  Copyright 2017 Carlos Pascual-Izarra <cpascual@users.sourceforge.net>
#
#  This file is part of PeakEvo.
#
#  PeakEvo is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  PeakEvo is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Foobar.  If not, see <http://www.gnu.org/licenses/>.


import logging
import time
from collections import deque
from threading import RLock
from unittest.mock import MagicMock

import numpy
import serial
from serial.threaded import LineReader, ReaderThread
from serial.tools.list_ports import comports


class PressureReader(LineReader):
    def __init__(self, n=50):
        LineReader.__init__(self)
        self._buffer = deque([0.0] * n)  # store the last n readings (normalized)
        self._raw = 0
        self._time = 0
        self._lock = RLock()
        self._n = n

    def handle_line(self, line):
        """Reimplemented from serial.threaded.LineReader to handle the read
        data
        """
        with self._lock:
            try:
                # calculate a moving average
                new = float(line) / self._n
                self._buffer.append(new)
                old = self._buffer.popleft()
                self._raw += new - old
            except Exception:
                logging.debug('PressureReader: cannot convert "%r"', line)
            self._time = time.time()

    @property
    def raw(self):
        with self._lock:
            ret = self._raw
        return ret

    def read(self):
        with self._lock:
            p, t = self._raw, self._time
        return p, t


class PressureSensor(object):
    def __init__(self, port=None, calib=None, buffer=50):
        if port is None:
            port = self.get_arduino_port()
        if port is None:
            raise Exception("Unable to connect to pressure sensor")

        if calib is None:
            calib = numpy.array([1.0, 0.0], dtype="float64")
        self._calib = calib
        self.serial = serial.Serial(port=port)
        self.reader = PressureReader(n=buffer)
        self.thread = ReaderThread(self.serial, lambda: self.reader)
        self.thread.start()

    @property
    def calib(self):
        return self._calib

    @calib.setter
    def calib(self, value):
        self._calib = numpy.array(value, dtype="float64")

    def read(self):
        """returns (pressure, timestamp)"""
        raw, t = self.reader.read()
        pressure = numpy.polyval(self._calib, raw)
        return pressure, t

    def raw(self):
        return self.reader.raw

    @staticmethod
    def get_arduino_port():
        port = None
        for p in comports():
            if "Arduino" in p.description:
                port = p.device
                break
        return port


class DummyPressureSensor(MagicMock):
    calib = numpy.array([1.0, 0.0], dtype="float64")

    def raw(self):
        t = time.time()
        return int(t * 1000) % 1024

    def read(self):
        t = time.time()
        raw = self.raw()
        return numpy.polyval(self.calib, raw), t


if __name__ == "__main__":
    s = DummyPressureSensor()
    for i in range(30):
        print(s.read())
        time.sleep(0.01)
    s.thread.stop()
