# *****************************************************************************
#
# This program 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 2 of the License, or (at your option) any later
# version.
#
# This program 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
# this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# Module authors:
#   Markus Zolliker <markus.zolliker@psi.ch>
#
# *****************************************************************************

import time
from frappy.core import Attached, Readable, Writable, Parameter, Command, \
    IDLE, BUSY, DISABLED, ERROR
from frappy.datatypes import FloatRange, StatusType, TupleOf, EnumType
from frappy.states import HasStates, Retry, status_code


class AutoFill(HasStates, Readable):
    level = Attached(Readable)
    valve = Attached(Writable)
    status = Parameter(datatype=StatusType(Readable, 'BUSY'))
    mode = Parameter('auto mode', EnumType(disabled=0, auto=30), readonly=False)

    fill_level = Parameter('low threshold triggering start filling',
                           FloatRange(unit='%'), readonly=False)
    full_level = Parameter('high threshold triggering stop filling',
                           FloatRange(unit='%'), readonly=False)
    fill_minutes_range = Parameter('range of possible fill rate',
                                   TupleOf(FloatRange(unit='min'), FloatRange(unit='min')),
                                   readonly=False)
    hold_hours_range = Parameter('range of possible consumption rate',
                                 TupleOf(FloatRange(unit='h'), FloatRange(unit='h')),
                                 readonly=False)
    fill_delay = Parameter('delay for cooling the transfer line',
                           FloatRange(unit='min'), readonly=False)

    def read_status(self):
        if self.mode == 'DISABLED':
            return DISABLED, ''
        vstatus = self.valve.status
        if vstatus[0] // 100 != IDLE // 100:
            self.stop_machine(vstatus)
            return vstatus
        status = self.level.read_status(self)
        if status[0] // 100 == IDLE // 100:
            return HasStates.read_status(self)
        self.stop_machine(status)
        return status

    def write_mode(self, mode):
        if mode == 'DISABLED':
            self.stop_machine((DISABLED, ''))
        elif mode == 'AUTO':
            self.start_machine(self.watching)
        return mode

    @status_code(BUSY)
    def watching(self, state):
        if state.init:
            self.valve.write_target(0)
        delta = state.delta(10)
        raw = self.level.value
        if raw > self.value:
            self.value -= delta / (3600 * self.hold_hours_range[1])
        elif raw < self.value:
            self.value -= delta / (3600 * self.hold_hours_range[0])
        else:
            self.value = raw
        if self.value < self.fill_level:
            return self.precooling
        return Retry

    @status_code(BUSY)
    def precooling(self, state):
        if state.init:
            state.fillstart = state.now
            self.valve.write_target(1)
        delta = state.delta(1)
        raw = self.level.value
        if raw > self.value:
            self.value += delta / (60 * self.fill_minutes_range[0])
        elif raw < self.value:
            self.value -= delta / (60 * self.fill_minutes_range[0])
        else:
            self.value = raw
        if self.value > self.full_level:
            return self.watching
        if state.now > state.fillstart + self.fill_delay * 60:
            return self.filling
        return Retry

    @status_code(BUSY)
    def filling(self, state):
        delta = state.delta(1)
        raw = self.level.value
        if raw > self.value:
            self.value += delta / (60 * self.fill_minutes_range[0])
        elif raw < self.value:
            self.value += delta / (60 * self.fill_minutes_range[1])
        else:
            self.value = raw
        if self.value > self.full_level:
            return self.watching
        return Retry

    def on_cleanup(self, state):
        try:
            self.valve.write_target(0)
        except Exception:
            pass
        super().on_cleanup()

    @Command()
    def fill(self):
        self.mode = 'AUTO'
        self.start_machine(self.precooling, fillstart=time.time())

    @Command()
    def stop(self):
        self.start_machine(self.watching)
