#
# Sirael Metabolic Simulator Example of Implementing Custom Insulin-Pump Controller
# Copyright (c) since 2020 Tomas Koutny.
#
# Contact:
# rawos@rawos.com
# Tomas Koutny
#
#
# Purpose of this software:
# This software is a personal, research project of Tomas Koutny. It is strictly
# prohibited to use this software for diagnosis or treatment of any medical condition,
# without proving a safety for the intended use.
#
# By using this software, you are responsible for:
#  a) implementing all safety requirements,
#  b) proving the safety for a particular purpose,
#  c) obtaining all regulatory approvals,
#  d) any arising harm.
#
# Especially, a diabetic patient is warned that improper use of this software
# may result into severe injure, including death.
#
#
#
# Licensing terms:
# This software is granted under the Apache 2.0 license for a non-commercial use.
# This software can be granted for a commercial use under a written agreement only.
# Always, you must include and display this header-comment when redistributing the software in any form.
#
# When referring to this work, use the following citation:
# Koutny, Tomas, Siraela - In-Silico Metabolic Simulator with Application for Diabetes (May 18, 2023).
# Available at
#	SSRN: https://ssrn.com/abstract=4452634
#  or http://dx.doi.org/10.2139/ssrn.4452634
#  or git: https://gitlab.com/sirael_metabolic_simulator/
#

import os
import subprocess
import sys
import threading
import matplotlib.pyplot as plt


class Sirael_Terminal(object):    

    controller_level_threshold = 5.0
    controller_multiplier = 0.2
    controller_suspend_period = 20*60	#20 minutes in seconds

    controller_suspend_until = -1
    controller_is_suspended = False
    
    recent_level = 0.0
    recent_time = 0.0

    controller_condition = threading.Condition()

    glucose_time=[]
    glucose_levels=[]
    bolus_time=[]
    bolus_values=[]
    cho_val = []
    cho_time = []

    def __init__(self):
        pass
    def run(self):
        env = os.environ.copy()
        p = subprocess.Popen('sirael.exe', stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True, env=env)

        sys.stdout.write("Started Sirael terminal...\n")
        sys.stdout.write("Wall Clock [hour]; Series ID; Level; Name\n")

        def writeall(p):
            
            

            while True:                
                data = p.stdout.readline().decode()

                
                if not data:
                    break                

                if data.startswith("Emit:"):                    
                    data = data[6:]
                    chunks = data.split(';')

                    time = chunks[0]
                    series = chunks[1]
                    level = chunks[2]
                    name = chunks[3]                    

                    if series.endswith("{B44F2278-F592-491E-BE20-6DF6999AC2D8}"):                        
                        sys.stdout.write(data)
                        sys.stdout.flush()

                        self.glucose_time.append(float(time)/(60*60*24))
                        self.glucose_levels.append(float(level))
                        self.recent_level = level
                        self.recent_time = time                        
                            
                        with self.controller_condition:
                            self.controller_condition.notify()
                            

            
                

        writer = threading.Thread(target=writeall, args=(p,))
        writer.start()

        try:
            # Load real-world scenario, when the user eats, exercise, etc.            
            # Conviniently, it already contains commands for advancing the simulationa and sampling its state.
            # scenario = open('scenario.txt', 'r')
            scenario = open(sys.argv[3], 'r')

            # Open a first metabolic state of a particular patient/metabolism.
            # Note that it is possible to reset the state multiple times without closing the simulator/Sirael Terminal.
            #self._write (p, bytes('reset 0 metabolism.sir\n', 'utf-8'))
            reset_cmd = 'reset ' + sys.argv[2] + ' '  + sys.argv[1]+'\n'
            self._write (p, bytes(reset_cmd, 'utf-8'))
            while True:
                line = scenario.readline()
                if not line:
                    break

                self._write (p, bytes(line, 'utf-8'))

                if line.startswith("emit"):
                    with self.controller_condition:
                        self.controller_condition.wait()
                    self.exert_controller_strategy(p, self.recent_level, self.recent_time)

                if line.startswith("cho"):
                    self.cho_time.append(float(self.recent_time)/(60*60*24))
                    val = float(line[4:])*0.1
                    self.cho_val.append(val)
                    print(str(self.recent_time) + '; {37AA6AC1-6984-4A06-92CC-A660110D0DC7}; ' +str(val)+'; consumed carbohydrates')

            # Close the simulation
            self._write (p, bytes('exit\n', 'utf-8'))
            scenario.close()         

            plt.rcParams["figure.figsize"] = [16, 9]
            fig, ax = plt.subplots()
            
            glucose_color = 'tab:blue'
            ax.set_xlabel('Wall Clock [days]')
            ax.set_ylabel('CGM Sensor [mmol/L] (solid line)\nCHO [10*g] (diamond marks)', )
            ax.tick_params(axis='y', labelcolor=glucose_color)
            ax.plot(self.glucose_time, self.glucose_levels, color=glucose_color, linestyle='solid')
            ax.plot(self.cho_time, self.cho_val, color='tab:brown', linestyle='None', marker='D')

            bolus_color = 'tab:red'
            ax2 = ax.twinx()            
            ax2.set_ylabel('Insulin boluses [U]', color=bolus_color)            
            ax2.tick_params(axis='y', labelcolor=bolus_color)
            ax2.plot(self.bolus_time, self.bolus_values, color=bolus_color, linestyle='None', marker='o')

            plt.title("A simple Sirael VM demonstration with custom insulin-pump controller\nPlease, be aware that this is very naive controller so that the results will not be optimal.")
            # plt.savefig("metabolism.controller.png")
            plt.savefig(sys.argv[4], format='svg', transparent=True)

        except EOFError:
            pass

    def _write(self, process, message):
        process.stdin.write(message)
        process.stdin.flush()


    def exert_controller_strategy(self, process, level, current_time):
        # This method implements a custom insulin-pump control algorithm.
        if self.controller_suspend_until < int(current_time):
            if float(level)>self.controller_level_threshold:
                bolus = self.controller_multiplier*(float(level) - self.controller_level_threshold)
                self.bolus_time.append(float(current_time)/(24*60*60))
                self.bolus_values.append(bolus)
                bolus_str = str(bolus)
                self._write (process, bytes('bolus ' + bolus_str + '\n', 'utf-8'))
                self.controller_suspend_until = int(current_time) + self.controller_suspend_period

                sys.stdout.write(str(current_time)+"; {09B16B4A-54C2-4C6A-948A-3DEF8533059B}; " + bolus_str + '; insulin bolus\n')
                sys.stdout.flush()
                

if os.path.isfile('sirael.exe'):
    sirael = Sirael_Terminal()
    sirael.run()
else:
    print('Please, copy sirael.exe file to the same directory as this .py file\n')