# flask_simple_server
import atexit
import glob
import logging
import os
import subprocess
import socket
import sys
import threading
import time
import json
import requests
from multiprocessing import Process
from os import path
from threading import Timer

from flask import Flask, request
from requests import exceptions, get, post

import backendManager as bm
import check_if_port_open
import obs_websocket as obs
import steamVRManager as svrm
import systemSettings as sys
import udp_client_listener as ucl
# -----------------------------
import checkInstalledGame as ch

app = Flask(__name__)
logging.basicConfig(level=logging.DEBUG)
# app_id
TEAMVR_GAMEID = '250820'
HELLBLADE = '747350'
STEAMVR_ID = '250820'
# GAME_NAME
SteamVR = 'vrserver'

# IP
CLOUDXR_SERVER_MANAGER_IP = ''
CLOUDXR_CLIENT_IP = ''
BACKEND_SERVER_IP = ''
# FLAG
is_available = True
app_id = ''
app_title = ''
player_ip = ''
platform = ''
applist = list()
# GlOBAL VARIABLES
launch_time = time.time()
g_CurrentGameID = ''
settings = ''

@app.route('/connection-timeout', methods=['GET'])
def timeoutCloudXR():
    svrm.timeoutOpenGame(BACKEND_SERVER_IP)
    global is_available
    is_available = True
    print("timeoutCloudXR")

# check if steamvr is ok and return ready for a user to connect
@app.route('/game-connection', methods=['POST', 'GET'])
def launchCloudXR():
    if request.method == 'POST':
        print('============launchCloudXR=============')
        global player_ip, is_available, app_title, platform
        global app_id
        global BACKEND_SERVER_IP
        # if not is_available:
        #     return 'Game server not available now'
        app_title = request.form.get('app_title', type=str)
        app_id = request.form.get('app_id', type=str)
        player_ip = request.form.get('player_ip', type=str)
        platform = request.form.get('platform', type = str)
        print('platform: ', platform)

        svrm.setAppID(app_id)
        svrm.setAppTitle(app_title)
        svrm.setAppPlatform(platform)

        # logging.info("request: {0} {1} {2}".format(app_title,  app_id, player_ip))

        if svrm.CheckGameStatus(SteamVR) >= 1:
            if platform == "steam" or platform == "compal":
                if is_available == True:
                    svrm.startGame(BACKEND_SERVER_IP, player_ip,
                                app_id, app_title, platform)
                is_available = False
                return {'launch success': True}
            else:
                return {'launch success': False}
        else:
            svrm.startSteamvr(STEAMVR_ID)
            while svrm.CheckGameStatus(SteamVR) == 0:
                continue
            # TODO: open steamvr again and startGame
            # TODO: return false if error happen
            return {'launch success': False}
    # for launching steamVR process
    else:
        #should return exception when packet is not POST
        if time.time() > launch_time+10:
            return {'status': True}
        else:
            return {'status': False}

@app.route('/game-disconnection', methods=['POST'])
def closeCloudXR():
    global player_ip, is_available, app_id, app_title,platform
    # print(player_ip, request.remote_addr)
    # app_title = request.form.get('app_title', type=str)
    # app_id = request.form.get('app_id', type=str)
    # if request.remote_addr != player_ip and player_ip != '127.0.0.1':
    #     return 'Invalid request'

    # Close game app and steamVR, and reset game server status
    print('closeCloudXR : ', svrm.getAppTitle())
    logging.info(svrm.getAppTitle())
    # if g_CurrentGameTitle == '':
    #     return {'close fail': False, 'app_title' : app_title}
    if svrm.CheckGameStatus(svrm.getAppTitle()) >= 1:
        #close the current game
        cur_platform = svrm.getAppPlatform()
        # print('cur_platform: ', cur_platform)
        if cur_platform == 'steam':
            svrm.closeGame(svrm.getAppTitle())
        elif cur_platform =='compal':
            print('svrm.getApplication: ', svrm.getApplication())
            svrm.closeApplication(svrm.getAppTitle(), svrm.getApplication())
        #close the steamvr
        time.sleep(5)
        svrm.closeSteamvr()
        #close obs streaming
        stream_server_ip = settings['STREAM_SERVER_IP']
        obs.stop(BACKEND_SERVER_IP,stream_server_ip)
        #set game server to be available again
        is_available = True
        #print(player_ip, is_available)
        time.sleep(5)
        #restart steamvr
        svrm.startSteamvr(STEAMVR_ID)
        while svrm.CheckGameStatus(SteamVR) == 0:
            continue
        #send status to backend server
        bm.SendGameConnection(BACKEND_SERVER_IP, player_ip, app_id, "closed",platform)
        svrm.setAppID('')
        player_ip = ''

        # RegisterToBackednServer()
        return {'close success': True, 'app_title': app_title}
    elif svrm.CheckGameStatus(SteamVR) >= 1:
        svrm.closeSteamvr()
        bm.SendGameConnection(BACKEND_SERVER_IP, player_ip, app_id, "closed",platform)
        svrm.setAppID('')
        player_ip = ''
        #add delay
        time.sleep(5)
        #restart steamvr
        svrm.startSteamvr(STEAMVR_ID)
        while svrm.CheckGameStatus(SteamVR) == 0:
            continue
        return {'close success': True, 'app_title': app_title}
    else:
        return {'close success': False, 'app_title': app_title}

@app.route('/steamvr-status', methods=['POST', 'GET'])
def getSteamVRStatus():
    if svrm.CheckGameStatus(SteamVR) >= 1:
        return {'SteamVR Status': True}
    else:
        return {'SteamVR Status': False}

@app.route('/game-status', methods=['POST', 'GET'])
def getGameStatus():
    # app_id = request.form.get('app_id', type=str)
    app_title = request.form.get('app_title', type=str)
    if svrm.CheckGameStatus(app_title) >= 1:
        return {'app_id Status': True, 'app_title': app_title}
    else:
        return {'app_id Status': False, 'app_title': app_title}

@app.route('/reconnect_to_backend_server', methods=['POST', 'GET'])
def reconnect_to_backend_server():
    bm.RegisterToBackendServer(BACKEND_SERVER_IP, applist)
    return {'app_id Status': True}

@app.route('/start_droid_cam', methods=['POST', 'GET'])
def start_droid_cam():
    app_title = request.form.get('app_title', type=str)
    app_type = request.form.get('app_type', type = str)
    print('start_droid_cam app_title: ', app_title)
    print('start_droid_cam app_type: ', app_type)
    with open(os.path.relpath('GamingServer/appdict.json')) as f:
        data = json.load(f)
    for json_dict in data:
        if json_dict['App'] == app_title:
            svrm.setApplication(json_dict['Application'])
            #request the ip
            my_params = {
                    'protocol': app_type,
                    'app_name': app_title
                    }
            url = 'http://' + BACKEND_SERVER_IP + '/dataflow'
            r = requests.get(url, params=my_params)
            print("status_code: ", r.status_code)
            json_data = json.loads(r.text)
            # start camera
            if json_data['status'] == True:
                path = json_dict['DroidCam_Path']
                subprocess.Popen([ path, '-c', json_data['source_url'], '4747', '-video'])
    return json_data

@app.route('/obs_start_streaming', methods=['POST', 'GET'])
def obs_start_streaming():
    global player_ip
    print("obs_start_streaming")
    stream_server_ip = settings['STREAM_SERVER_IP']
    obs.start(BACKEND_SERVER_IP,stream_server_ip ,player_ip)
    dict = {
        'status' : True,
        'msg': 'TODO'
    }
    return dict

@app.route('/obs_stop_streaming', methods=['POST', 'GET'])
def obs_stop_streaming():
    print("obs_stop_streaming")
    stream_server_ip = settings['STREAM_SERVER_IP']
    obs.stop(BACKEND_SERVER_IP,stream_server_ip)
    dict = {
        'status' : True,
        'msg': 'TODO'
    }
    return dict

def main():
    global BACKEND_SERVER_IP
    global applist
    global settings
    # check installed steamvr games in pc
    ch.CheckInstalledSteamGame(applist)
    ch.CheckInstalledNonSteamApplication(applist)
    print(applist)
    if len(applist) == 0:
        print('applist is empty')
    # ucl.start_udp_server()
    settings = sys.loadConfig()
    # if sys not true return error
    BACKEND_SERVER_IP = settings['BACKEND_SERVER_IP']
    # register to backed server
    bm.RegisterToBackendServer(BACKEND_SERVER_IP, applist)
    # if fail to RegisterToBackednServer

    # then throw exceptions

    # if steamVR not open
    if svrm.CheckGameStatus(SteamVR) == 0:
        print("SteamVR is not running, open now!")
        svrm.startSteamvr(STEAMVR_ID)
        while svrm.CheckGameStatus(SteamVR) == 0:
            continue
    else:
        print("SteamVR is running!")

    atexit.register(bm.UnregisterToBackendServer, BACKEND_SERVER_IP)
    # atexit.register(ucl.stop_udp_server)
    # create a while-while to listen to steamvr messages
    # CheckSteamVRLogs
    # if logs updated
    # send status to backed server
    # loop

if __name__ == '__main__':
    # set host='0.0.0.0', port=80 to enable external access in local network
    # app.debug = True
    # app.use_reloader=False
    main()
    # check_if_port_open.check(
    #     settings['GAMESERVER_IP'], settings['GAMESERVER_PORT'])

    # send Heartbeat to backend
    if False:
        dict = {'BACKEND_SERVER_IP': settings['GAMESERVER_IP'], 'client_ip': 'client_ip',
                'app_id': 'app_id', 'app_title': 'app_title'}
        p = Process(target=bm.SendSystemHeartbeat, args=(dict,))
        p.start()

    # add thread to send heartbeat to backend
    app.run(host=settings['GAMESERVER_IP'], port=settings['GAMESERVER_PORT'])


'''
Given an array of integers in which two elements appear exactly once and all other elements appear exactly twice, find the two elements that appear only once.

For example, given the array [2, 4, 6, 8, 10, 2, 6, 10], return 4 and 8. The order does not matter.

Follow-up: Can you do this in linear time and constant space?
'''

from typing import List

# Method 1: Using Set. Add a number if it's not in the set, and remove it if it is.
def findSingleElements1(int_arr: List[int]):
    out_set = set()

    for num in int_arr:
        if num in out_set:
            out_set.remove(num)
        else:
            out_set.add(num)

    # Convert to list or tuple if desired. Leaving as a set here.
    return out_set

print(findSingleElements1([2, 4, 6, 8, 10, 2, 6, 10]))

# Method 2: Using bitwise XOR. This works on the axiom that for any value n, n ^ n = 0.
def findSingleElements2(int_arr: List[int]):
    # Do a XOR across all elements.
    arr_xor = 0
    for num in int_arr:
        arr_xor ^= num
    # arr_xor now equals the two single elements XOR'd. We now need to know the first bit where they differ in order to determine the elements' values.
    # This is accomplished by a bitwise AND between itself and the inverse of itself minus 1.
    first_different_bit = arr_xor & ~(arr_xor - 1)
    # We can then split the difference in the same manner as above. One element will ultimately land in s1, and the other in s2, with all others filtered out.
    s1, s2 = 0, 0
    for num in int_arr:
        if num & first_different_bit:
            s1 ^= num
        else:
            s2 ^= num

    return s1, s2

print(findSingleElements2([2, 4, 6, 8, 10, 2, 6, 10]))


from south.db import db
from django.db import models
from todo.models import *

class Migration:

    def forwards(self, orm):

        # Adding model 'Item'
        db.create_table('todo_item', (
            ('id', models.AutoField(primary_key=True)),
            ('goal', models.CharField(max_length=255)),
            ('when', models.DateTimeField()),
            ('list', models.ForeignKey(orm.List)),
            ('order', models.PositiveIntegerField(default=0)),
        ))
        db.send_create_signal('todo', ['Item'])

        # Adding model 'List'
        db.create_table('todo_list', (
            ('id', models.AutoField(primary_key=True)),
            ('name', models.CharField(max_length=255)),
            ('slug', models.SlugField(unique=True)),
        ))
        db.send_create_signal('todo', ['List'])

    def backwards(self, orm):

        # Deleting model 'Item'
        db.delete_table('todo_item')

        # Deleting model 'List'
        db.delete_table('todo_list')

    models = {
        'todo.item': {
            'goal': ('models.CharField', [], {'max_length': '255'}),
            'id': ('models.AutoField', [], {'primary_key': 'True'}),
            'list': ('models.ForeignKey', ["orm['todo.List']"], {}),
            'order': ('models.PositiveIntegerField', [], {'default': '0'}),
            'when': ('models.DateTimeField', [], {})
        },
        'todo.list': {
            'id': ('models.AutoField', [], {'primary_key': 'True'}),
            'name': ('models.CharField', [], {'max_length': '255'}),
            'slug': ('models.SlugField', [], {'unique': 'True'})
        }
    }

    complete_apps = ['todo']


from kwippyproject.kwippy.models.quip import Quip
from django import template
from django.shortcuts import get_object_or_404
from django.template import Node, NodeList, Template, Context, Variable
from django.core.cache import cache
from django.conf import settings
import pdb, re

register = template.Library()

regex = re.compile('^.*(https?:\/\/[^\ \s]*).*$')

def do_formaturl(parser, token):
    bits = list(token.split_contents())
    nodelist = NodeList()
    return FormatURLNode(bits[1])

# this can be removed/optimized
def formaturl(parser, token):
    return do_formaturl(parser, token)

class FormatURLNode(Node):
    def __init__(self, var1):
        self.var1 = Variable(var1)

    def __repr__(self):
        return "<FormatTimeNode>"

    def render(self, context):
        try:
            val1 = self.var1.resolve(context)
        except VariableDoesNotExist:
            val1 = None
        cache_key = '%s_quiptext%d' % (settings.CACHE_MIDDLEWARE_KEY_PREFIX,val1,)
        kwip = cache.get(cache_key)
        if kwip is None:
            quip = get_object_or_404(Quip, id=val1)
            text = quip.original.strip()
            match = re.match(regex,text)
            if match:
                url = match.group(1)
                replacement = ''+url+''
                kwip = text.replace(url,replacement)
            else:
                kwip = quip.original
            cache.set(cache_key,kwip,86400)
        return kwip

register.tag('formaturl',do_formaturl)


import numpy as np
from PIL import Image
import ica_algorithm

class ICA_Adapter:
    ica_alg = ica_algorithm.ICA_Algorithm()

class ICA_Image(ICA_Adapter):
    def runICA(self,mixed_images):
        d = len(mixed_images)
        n1,n2  = mixed_images[0].size
        n = n1*n2

        #put the mixed images in a form for ica
        x = np.zeros((d,n))
        for k in range(d):
            xarr = np.asarray(mixed_images[k])
            x[k,:] = np.reshape(xarr,(1,n))

        #call ica
        y = self.ica_alg.ica(x)

        #create output list of component
        component_images = []
        for k in range(d):
            #ica centers and normalizes, so scale output to make into an image
            yarr = np.reshape(y[k,:],(n1,n2))*64.0 + 127.0
            component_images.append(Image.fromarray(yarr))

        return component_images

class ICA_Signal(ICA_Adapter):
    def runICA(self,mixed_signals):
         d = len(mixed_signals)
         n = len(mixed_signals[0])

         #crate array to hold all 1-d signals
         x = np.zeros((d,n))
         for k in range(d):
             x[k,:] = mixed_signals[k]

         #call ica
         y = self.ica_alg.ica(x)

         #put component signals back into a list
         component_signals =  []
         for k in range(d):
             component_signals.append(y[k,:])

         return component_signals


from itertools import permutations

def solution(numbers):
    answer = 0
    already_check = []
    numbers = list(numbers)
    for i in range(len(numbers)):
        for j in permutations(numbers, i+1):
            temp = int("".join(j))
            if prime_check(temp) and temp not in already_check:
                already_check.append(temp)
                answer += 1

    return answer

import math
def prime_check(n):
    if n < 2:
        return False
    for i in range(2, int(math.sqrt(n))+1):
        if n % i == 0:
            return False
    return True

print(solution("011"))


from django.contrib.auth import get_user_model

from rest_framework import views, generics, permissions, status
from rest_framework.response import Response

from mlhaiti.accounts.api.serializers import UserSerializer

User = get_user_model()

class UserCreateView(generics.CreateAPIView):
    """
        User Registration
    """
    permission_classes = []
    serializer_class = UserSerializer
    queryset = User.objects.all()

    def create(self, request, *args, **kwargs):
        serializer = self.serializer_class(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(
                {
                    'status':201,
                    'error':False,
                    'data':serializer.data,
                    'message':'user registred successfully'
                },
                status=status.HTTP_201_CREATED
            )

        return Response(
            {
                'status':400,
                'error':True,
                'data':[],
                'message':serializer.errors
            },
            status=status.HTTP_400_BAD_REQUEST
        )


"""Functions dealing with the local database of publications."""

from pathlib import Path
import glob
import json
import appdirs

data_dir_base = appdirs.user_data_dir("dblptobib")
data_dir = data_dir_base + "/automatic/"
mod_dir = data_dir_base + "/manual/"
subdirs = ["authors", "venues", "papers"]

def setup_directories():
    """Make sure that all necessary directories exits."""
    for subdir in subdirs:
        for dir in [data_dir, mod_dir]:
            Path(dir + subdir).mkdir(parents=True, exist_ok=True)

def file_name(subdir, name):
    """File name for data point `name` of type `subdir`."""
    return data_dir + subdir + "/" + name + ".json"

def paper_exists(dblp_key):
    """Check whether the paper with the given id exists in the local database."""
    path = Path(file_name("papers", dblp_key))
    return path.exists()

def load_data(subdir):
    """Helper function for loading all data from a specific subdir.

    Loads the data from the `data_dir` and applies manual
    modifications from the `mod_dir`.  The type of data is specified
    by `subdir`.

    For external use, use wrapper functions such as `autors()` or
    'papers()`.

    """
    # load data
    files = glob.glob(data_dir + subdir + "/*.json")
    results = {Path(file).stem: json.load(open(file)) for file in files}

    # load manual modifications
    mod_files = glob.glob(mod_dir + subdir + "/*.json")
    mod_results = {Path(file).stem: json.load(open(file)) for file in mod_files}

    # update papers according to modifications
    for result, props in mod_results.items():
        if result not in results:
            results[result] = {}
        for prop, value in props.items():
            results[result][prop] = value

    return results

def authors():
    """Return dictionary of authors in the database."""
    return load_data("authors")

def papers():
    """Return dictionary of papers in the database (augmented with venue information)."""
    all_papers = load_data("papers")
    venues = load_data("venues")
    for paper in all_papers.values():
        if "venue" not in paper or paper["venue"] not in venues:
            continue
        venue = venues[paper["venue"]]
        if paper["type"] == "article":
            paper["journal_full"] = venue["name"]
            paper["journal_acronym"] = venue["acronym"]
        elif paper["type"] == "inproceedings":
            paper["booktitle_full"] = venue["name"]
            paper["booktitle_acronym"] = venue["acronym"]

    return all_papers


from dateutil.relativedelta import relativedelta
from django.apps import apps as django_apps
from django.test import TestCase, tag
from edc_base.utils import get_utcnow
from edc_constants.constants import NOT_APPLICABLE, YES
from edc_facility.import_holidays import import_holidays
from model_mommy import mommy
import pytz

from edc_visit_schedule.models import SubjectScheduleHistory

from ..models import OnScheduleCohortAAntenatal
from ..models import OnScheduleCohortAQuarterly
from ..models import OnScheduleCohortBEnrollment, OnScheduleCohortBQuarterly

@tag('ca')
class TestCohortAssignmentSetup(TestCase):

    databases = '__all__'
    utc = pytz.UTC

    def setUp(self):
        import_holidays()
        self.subject_identifier = '12345678'
        self.study_maternal_identifier = '89721'

        self.options = {
            'consent_datetime': get_utcnow(),
            'version': '1'}

        self.maternal_dataset_options = {
            'delivdt': get_utcnow() - relativedelta(years=2, months=5),
            'mom_enrolldate': get_utcnow(),
            'mom_hivstatus': 'HIV-infected',
            'study_maternal_identifier': self.study_maternal_identifier,
            'mom_pregarv_strat': '3-drug ART',
            'protocol': 'Tshilo Dikotla'}

        self.child_dataset_options = {
            'infant_hiv_exposed': 'Exposed',
            'infant_enrolldate': get_utcnow(),
            'study_maternal_identifier': self.study_maternal_identifier,
            'study_child_identifier': '1234'}

        self.child_dummy_consent_cls = django_apps.get_model(
            'flourish_child.childdummysubjectconsent')

    def year_3_age(self, year_3_years, year_3_months):
        """Returns the age at year 3.
        """
        app_config = django_apps.get_app_config('flourish_caregiver')
        start_date_year_3 = app_config.start_date_year_3

        child_dob = start_date_year_3 - relativedelta(years=year_3_years,
                                                      months=year_3_months)
        return child_dob

    def test_cohort_a_onschedule_antenatal_valid(self):
        """Assert that a pregnant woman is put on cohort a schedule.
        """
        screening_preg = mommy.make_recipe(
            'flourish_caregiver.screeningpregwomen',)

        subject_consent = mommy.make_recipe(
            'flourish_caregiver.subjectconsent',
            screening_identifier=screening_preg.screening_identifier,
            subject_identifier=self.subject_identifier,
            breastfeed_intent=YES,
            **self.options)

        child_consent = mommy.make_recipe(
            'flourish_caregiver.caregiverchildconsent',
            subject_consent=subject_consent,
            study_child_identifier=None,
            child_dob=None,
            first_name=None,
            last_name=None)

        self.assertEqual(child_consent.cohort, 'cohort_a')

        self.assertEqual(OnScheduleCohortAAntenatal.objects.filter(
            subject_identifier=subject_consent.subject_identifier,
            schedule_name='a_antenatal1_schedule1').count(), 0)

        self.assertEqual(OnScheduleCohortAQuarterly.objects.filter(
            subject_identifier=subject_consent.subject_identifier,
            schedule_name='a_quarterly1_schedule1').count(), 0)

    def test_cohort_b_onschedule_valid(self):
        """Assert that a 5.1 year old by year 3 participant's mother who is on
         efv regimen from Mpepu study is put on cohort b schedule.
        """

        self.subject_identifier = self.subject_identifier[:-1] + '2'
        self.study_maternal_identifier = '981232'
        self.maternal_dataset_options['protocol'] = 'Mpepu'
        self.maternal_dataset_options['delivdt'] = self.year_3_age(5, 1)
        maternal_dataset_obj = mommy.make_recipe(
            'flourish_caregiver.maternaldataset',
            subject_identifier=self.subject_identifier,
            preg_efv=1,
            **self.maternal_dataset_options)

        mommy.make_recipe(
            'flourish_child.childdataset',
            dob=self.year_3_age(5, 1),
            **self.child_dataset_options)

        self.options = {
            'consent_datetime': get_utcnow(),
            'version': '1'}

        mommy.make_recipe(
            'flourish_caregiver.screeningpriorbhpparticipants',
            screening_identifier=maternal_dataset_obj.screening_identifier,
            study_maternal_identifier=maternal_dataset_obj.study_maternal_identifier)

        subject_consent = mommy.make_recipe(
            'flourish_caregiver.subjectconsent',
            screening_identifier=maternal_dataset_obj.screening_identifier,
            breastfeed_intent=NOT_APPLICABLE,
            biological_caregiver=YES,
            **self.options)

        child_consent = mommy.make_recipe(
            'flourish_caregiver.caregiverchildconsent',
            subject_consent=subject_consent,
            study_child_identifier=self.child_dataset_options.get(
                'study_child_identifier'),
            child_dob=maternal_dataset_obj.delivdt,)

        child_dummy_consent = self.child_dummy_consent_cls.objects.get(
            subject_identifier=child_consent.subject_identifier)

        self.assertEqual(child_consent.cohort, 'cohort_b')

        self.assertEqual(child_dummy_consent.cohort, 'cohort_b')

        self.assertEqual(OnScheduleCohortBEnrollment.objects.filter(
            subject_identifier=subject_consent.subject_identifier,
            schedule_name='b_enrol1_schedule1').count(), 0)

        self.assertEqual(OnScheduleCohortBQuarterly.objects.filter(
            subject_identifier=subject_consent.subject_identifier,
            schedule_name='b_quarterly1_schedule1').count(), 0)

    @tag('cblt10')
    def test_cohort_b_lt10(self):
        """ Assert that a participant with a child who is less than
            10 years old at beginning of year 3 goes into cohort b schedule.
        """
        dob_dt = (get_utcnow() - relativedelta(years=9, months=9)).date()
        self.subject_identifier = self.subject_identifier[:-1] + '2'
        self.study_maternal_identifier = '981232'
        self.maternal_dataset_options['protocol'] = 'Mpepu'
        self.maternal_dataset_options['delivdt'] = dob_dt

        maternal_dataset_obj = mommy.make_recipe(
            'flourish_caregiver.maternaldataset',
            subject_identifier=self.subject_identifier,
            preg_efv=1,
            **self.maternal_dataset_options)

        mommy.make_recipe(
            'flourish_child.childdataset',
            dob=dob_dt,
            **self.child_dataset_options)

        self.options = {
            'consent_datetime': get_utcnow(),
            'version': '1'}

        mommy.make_recipe(
            'flourish_caregiver.flourishconsentversion',
            screening_identifier=maternal_dataset_obj.screening_identifier,
            version='1',
            child_version='1')

        mommy.make_recipe(
            'flourish_caregiver.screeningpriorbhpparticipants',
            screening_identifier=maternal_dataset_obj.screening_identifier,
            study_maternal_identifier=maternal_dataset_obj.study_maternal_identifier)

        subject_consent = mommy.make_recipe(
            'flourish_caregiver.subjectconsent',
            screening_identifier=maternal_dataset_obj.screening_identifier,
            breastfeed_intent=NOT_APPLICABLE,
            **self.options)

        caregiver_child_consent_obj = mommy.make_recipe(
            'flourish_caregiver.caregiverchildconsent',
            subject_consent=subject_consent,
            study_child_identifier=self.child_dataset_options['study_child_identifier'],
            child_dob=maternal_dataset_obj.delivdt,)

        mommy.make_recipe(
            'flourish_child.childassent',
            subject_identifier=caregiver_child_consent_obj.subject_identifier,
            first_name=caregiver_child_consent_obj.first_name,
            last_name=caregiver_child_consent_obj.last_name,
            dob=caregiver_child_consent_obj.child_dob,
            identity=caregiver_child_consent_obj.identity,
            confirm_identity=caregiver_child_consent_obj.identity,
            remain_in_study=YES,
            version=subject_consent.version)

        child_dummy_consent = self.child_dummy_consent_cls.objects.get(
            subject_identifier=caregiver_child_consent_obj.subject_identifier)

        self.assertEqual(caregiver_child_consent_obj.cohort, 'cohort_b')

        self.assertEqual(child_dummy_consent.cohort, 'cohort_b')

        self.assertEqual(OnScheduleCohortBEnrollment.objects.filter(
            subject_identifier=subject_consent.subject_identifier,
            schedule_name='b_enrol1_schedule1').count(), 0)

        self.assertEqual(OnScheduleCohortBQuarterly.objects.filter(
            subject_identifier=subject_consent.subject_identifier,
            schedule_name='b_quarterly1_schedule1').count(), 0)


# Create toggle switch model
from scipy.optimize import fsolve
from scipy.integrate import odeint, quad
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
# ODEs
def f_ode(x,t, *args):
    K,b_t,b_l,d_t,d_l,del_t,del_l,beta_t,beta_l, Kt0, Kl0 = args
    m_t = x[0]
    m_l = x[1]
    p_t = x[2]
    p_l = x[3]
    md_t = x[4]
    md_l = x[5]
    pd_t = x[6]
    pd_l = x[7]
    y = np.zeros(8)
    y[0] = K * b_t**2/(b_t**2 + p_l**2) - d_t * m_t
    y[1] = K * b_l**2/(b_l**2 + p_t**2) - d_l * m_l
    y[2] = beta_t * m_t - del_t*p_t
    y[3] = beta_l * m_l - del_l * p_l

    y[4] = K * b_t**2/(b_t**2 + pd_l**2) - d_t * md_t + Kt0
    y[5] = K * b_l**2/(b_l**2 + pd_t**2) - d_l * md_l + Kl0
    y[6] = beta_t * md_t - del_t*pd_t
    y[7] = beta_l * md_l - del_l * pd_l
    return y

x0 = np.zeros(8)
params_nom = [100, 1000, 10, 5, 5, 0.01, 0.01, 0.01, 0.01] # default params
params_nom.append(10) # leak params
params_nom.append(20) # leak params

timepoints = np.linspace(0,450,100)
params = tuple(params_nom)
# Solve ODEs, numerically
y = odeint(f_ode, x0, timepoints, args = params)

# Plot solutions from numerical simulation
plt.subplot(2,2,1)
plt.plot(timepoints,y[:,0], label = 'Original model')
plt.plot(timepoints,y[:,4], label = 'Perturbed model')
plt.legend()
plt.xlabel('Time')
plt.ylabel('Species amount')
plt.title('mRNA TetR response')

plt.subplot(2,2,2)
plt.plot(timepoints,y[:,1], label = 'Original model')
plt.plot(timepoints,y[:,5], label = 'Perturbed model')
plt.legend()
plt.xlabel('Time')
plt.ylabel('Species amount')
plt.title('mRNA LacI response')

plt.subplot(2,2,3)
plt.plot(timepoints,y[:,2], label = 'Original model')
plt.plot(timepoints,y[:,6], label = 'Perturbed model')
plt.legend()
plt.xlabel('Time')
plt.ylabel('Species amount')
plt.title('Protein TetR response')

plt.subplot(2,2,4)
plt.plot(timepoints,y[:,3], label = 'Original model')
plt.plot(timepoints,y[:,7], label = 'Perturbed model')
plt.title('Protein LacI response')
plt.xlabel('Time')
plt.ylabel('Species amount')
plt.legend()

plt.show()


from torchvision import models as models
import torch.nn as nn
import torch

def create_model(is_pretrained=True, num_outputs=14, model_name='resnet50'):

    model = None
    if model_name.lower() == 'resnet50':

        model = models.resnet50(progress=True, pretrained=is_pretrained)
#         model.fc = nn.Sequential(
#                 nn.Linear(2048, 1024),
#                 nn.ReLU(),
#                 nn.Linear(1024, 512),
#                 nn.ReLU(),
#                 nn.Linear(512, num_outputs),
#                 nn.Sigmoid())
        model.fc = nn.Sequential(
                    nn.Linear(2048, 1411),
                    nn.ReLU(),
                    nn.Linear(1411, num_outputs),
                    nn.Sigmoid())

    elif model_name.lower() == 'resnet34':

        model = models.resnet34(progress=True, pretrained=is_pretrained)
        model.fc = nn.Sequential(
                nn.Linear(512, 512),
                nn.ReLU(),
                nn.Linear(512, num_outputs),
                nn.Sigmoid())

    elif model_name.lower() == 'densenet':

        model = models.densenet121(progress=True, pretrained=is_pretrained)
        model.classifier = nn.Sequential(
                nn.Linear(1024, 512),
                nn.ReLU(),
                nn.Linear(512, num_outputs),
                nn.Sigmoid())

    return model

def freeze_model_parameters(model):

    for params in model.parameters():
        params.requires_grad = False

    model_name = model.__class__.__name__

    if model_name.lower() == 'resnet':
        for params in model.fc.parameters():
            params.requires_grad = True           # Unfreezing the last fully connected layers

    elif model_name.lower() == 'densenet':
        for params in model.classifier.parameters():
            params.requires_grad = True           # Unfreezing the last fully connected layers

def unfreeze_model_parameters(model):

    for params in model.parameters():
        params.requires_grad = True

def load_model(model, path):

    model_checkpoint = torch.load(path)
    model.load_state_dict(model_checkpoint['model_state_dict'], strict = False)

    return model

def load_model_for_prediction(model, path):

    model_checkpoint = torch.load(path, map_location=torch.device('cpu'))
    model.load_state_dict(model_checkpoint['model_state_dict'], strict = False)

    return model

def build_model_for_uptuna(trial, model, num_outputs=14, model_name='resnet50'):

    # Suggest the number of layers of neural network model
    n_layers = trial.suggest_int("n_layers", 1, 4)

    if model_name.lower() == 'resnet50':

        layers = generate_model_layers(trial=trial, in_features=2048, n_layers=n_layers, num_outputs=num_outputs)
        model.fc = nn.Sequential(*layers)

    elif model_name.lower() == 'resnet34':

        layers = generate_model_layers(trial=trial, in_features=512, n_layers=n_layers, num_outputs=num_outputs)
        model.fc = nn.Sequential(*layers)

    elif model_name.lower() == 'densenet':

        layers = generate_model_layers(trial=trial, in_features=1024, n_layers=n_layers, num_outputs=num_outputs)
        model.classifier = nn.Sequential(*layers)

    return model

def generate_model_layers(trial, in_features, n_layers, num_outputs):

    layers = []

    for i in range(n_layers):

        # Suggest the number of units in each layer
        out_features = trial.suggest_int("n_units_l{}".format(i), 64, 2048)

        layers.append(nn.Linear(in_features, out_features))
        layers.append(nn.ReLU())

        in_features = out_features

    layers.append(nn.Linear(in_features, num_outputs))
    layers.append(nn.Sigmoid())

    return layers

def apply_optuna_learned_fully_connected_layers(model, num_outputs):

        model.fc = nn.Sequential(
            nn.Linear(2048, 1411),
            nn.ReLU(),
            nn.Linear(1411, num_outputs),
            nn.Sigmoid())

        return model


import requests
import json
from BookingCar import startQuery
import configparser
import os
from tqdm import tqdm
class queryStationList:
	def __init__(self):
		if "isHeroku" in os.environ:
			IDNumber = os.environ["IDNO"]
			DeviceID = os.environ["DeviceID"]
			print (IDNumber)
			print (DeviceID)
		else:
			config = configparser.ConfigParser()
			config.read('Config.ini')

			#讀取身分證字號
			IDNumber =str(config.get(
				'identifyInformation', 'IDNO'))
			#讀取手機ID
			DeviceID =str(config.get(
				'identifyInformation', 'DeviceID'))

		self.my_data = {
			"para": {
				"app": "1",
				"ProjID": "ALL",
				"ProjMode": 0,
				"appVersion": "3.0.29",
				"DeviceID": DeviceID
			}
		}

	def searchPark(self, CarType,CityName):
		#租賃站的名稱
		stationNameLi = []
		#租賃站ID
		stationIDLi = []

		#station
		stationGISLi = []

		stationAddr = []
		# 將資料加入 POST 請求中
		r = requests.post(
			'https://irent.irentcar.com.tw/iMotoAPI/api/Preferential', json=self.my_data)
		stations = json.loads(r.text)
		if "ErrorCode" in stations:
			if stations["ErrorCode"] != "000000":
				return stations["ErrMsg"]
		#要查詢租車一定要專案ID
		projID = stations["data"][1]["ProjID"]
		#選擇不同縣市的同站租還專案
		for station in stations["data"][1]["Station"]:
			if station["Area"] == CityName:
				#租車站地址
				addr = station["ADDR"]
				#租車站ID
				siteID = station["Site_ID"]

				#租賃站點的中文名稱
				SiteName = station["SiteName"]
				lat, lng = station["Lat"], station["Lng"]
				ADDR = station["ADDR"]
				#可以輸出站點資訊
				# print("SiteName:{0} Addr:{1} siteID:{2}".format(SiteName , addr, siteID))

				#增加篩選車型功能，如果該站點沒有該車型，那就不要去詢問有沒有車
				for car in station["CarType"]:
					if car["TypeID"] == CarType:
						carID = car["TypeID"]
						typeName = car["TypeName"]
						#有該車型，那就加入搜尋的停車場列表
						stationNameLi.append(SiteName)
						stationIDLi.append(siteID)
						stationGISLi.append([lat, lng])
						stationAddr.append(ADDR)
						#依照各車型輸出詳情
						# print("CarID:{0} TypeName:{1}".format(carID,typeName))
		return stationNameLi, stationIDLi, stationGISLi,stationAddr

	def start(self,startQuery,Starttime,EndTime,CarType,CityName):
		try:
			#將所有的租賃站ID跟名稱列出來
			stationNameLi, stationIDLi, stationGISLi, stationAddr = self.searchPark(
				CarType, CityName)
		except ValueError:
			#返回錯誤，可能是請先登入等驗證問題
			return self.searchPark(CarType, CityName)
		index = 0
		hasCarStation = []
		for stationName in tqdm(stationNameLi):
			StationName = stationName
			StationID = stationIDLi[index]
			#座標位置
			stationGIS = stationGISLi[index]

			Addr = stationAddr[index]
			result = startQuery.getCar(
				Starttime, EndTime, CarType, StationName, StationID, stationGIS,Addr)
			if result != None:
				if "ErrMsg" in result:
					yield result["ErrMsg"]
				hasCarStation.append(result)
			index += 1
			yield len(stationNameLi),hasCarStation
		#回傳有車的站點
		yield len(stationNameLi),hasCarStation
if __name__ == "__main__":
	queryStationList = queryStationList()
	startQuery = startQuery()
	queryStationList.start(startQuery)


import copy
import functools
import logging

import matplotlib.pyplot as plt
import numpy as np
import pyqtgraph as pg
from PyQt4.QtCore import Qt, QSize, QEvent, QMetaObject
from PyQt4.QtCore import pyqtSignal
from PyQt4.QtGui import QTableView, QWidget, QVBoxLayout, QHBoxLayout, \
    QSizePolicy, QPushButton, QSpacerItem, QApplication, QDockWidget,\
    QComboBox, QColor, QMessageBox
from qgis.core import QgsGeometry, QgsCoordinateTransform
from qgis.core import QgsFeatureRequest

from zDeltaresTdiToolbox.config.waterbalance.sum_configs import serie_settings
from zDeltaresTdiToolbox.models.wb_item import WaterbalanceItemModel
from zDeltaresTdiToolbox.utils.maptools.polygon_draw import PolygonDrawTool

log = logging.getLogger('DeltaresTdi.' + __name__)

try:
    _encoding = QApplication.UnicodeUTF8

    def _translate(context, text, disambig):
        return QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
    def _translate(context, text, disambig):
        return QApplication.translate(context, text, disambig)

serie_settings = {s['name']: s for s in serie_settings}

INPUT_SERIES = [
    ('2d_in', 0, '2d'),
    ('2d_out', 1, '2d'),
    ('1d_in', 2, '1d'),
    ('1d_out', 3, '1d'),
    ('2d_bound_in', 4, '2d'),
    ('2d_bound_out', 5, '2d'),
    ('1d_bound_in', 6, '1d'),
    ('1d_bound_out', 7, '1d'),
    ('1d_2d_in', 8, '1d'),
    ('1d_2d_out', 9, '2d'),
    ('2d_to_1d_pos', 10, '1d_2d'),
    ('2d_to_1d_neg', 11, '1d_2d'),
    ('pump_in', 12, '1d'),
    ('pump_out', 13, '1d'),
    ('rain', 14, '2d'),
    ('infiltration_rate_simple', 15, '2d'),
    ('lat_2d', 16, '2d'),
    ('lat_1d', 17, '1d'),
    ('d_2d_vol', 18, '2d'),
    ('d_1d_vol', 19, '1d'),
    ('error_2d', 20, 'error_2d'),
    ('error_1d', 21, 'error_1d'),
    ('error_1d_2d', 22, 'error_1d_2d'),
    ('2d_groundwater_in', 23, '2d'),
    ('2d_groundwater_out', 24, '2d'),
    ('d_2d_groundwater_vol', 25, '2d'),
    ('leak', 26, '2d'),
    ('inflow', 27, '1d'),
    ('2d_vertical_infiltration_pos', 28, '2d_vert'),
    ('2d_vertical_infiltration_neg', 29, '2d_vert'),
]

# some helper functions
#######################

def _get_request_filter(ids):
    ids_flat = list(set([i for j in ids.values() for i in j]))
    return QgsFeatureRequest().setFilterFids(ids_flat)

def _get_feature_iterator(layer, request_filter):
    # mainly pumps are often not present
    if layer:
        return layer.getFeatures(request_filter)
    else:
        return []

#######################

@functools.total_ordering
class Bar(object):
    """Bar for waterbalance barchart with positive and negative components.
    """
    SERIES_NAME_TO_INDEX = {name: idx for (name, idx, _) in INPUT_SERIES}

    def __init__(self, label_name, in_series, out_series, type):
        self.label_name = label_name
        self.in_series = in_series
        self.out_series = out_series
        self.type = type
        self._balance_in = None
        self._balance_out = None

    @staticmethod
    def _get_time_indices(ts, t1, t2):
        """Time series indices in range t1-t2."""
        idx_x1 = np.searchsorted(ts, t1)
        if not t2:
            idx_x2 = len(ts)
        else:
            idx_x2 = np.searchsorted(ts, t2)
        return np.arange(idx_x1, idx_x2)

    @property
    def end_balance_in(self):
        return self._balance_in

    def set_end_balance_in(self, ts, ts_series, t1=0, t2=None):
        idxs = [self.SERIES_NAME_TO_INDEX[name] for name in self.in_series]
        ts_indices_sliced = self._get_time_indices(ts, t1, t2)
        # NOTE: we're using np.clip to determine in/out for dvol (for flows
        # /discharges this shouldn't matter I THINK)
        ts_deltas = np.concatenate(([0], np.diff(ts)))
        # shape = (N_idxs, len(ts))
        balance_tmp = (ts_deltas * ts_series[:, idxs].T).clip(min=0)
        self._balance_in = balance_tmp[:, ts_indices_sliced].sum()

    @property
    def end_balance_out(self):
        return self._balance_out

    def set_end_balance_out(self, ts, ts_series, t1=0, t2=None):
        idxs = [self.SERIES_NAME_TO_INDEX[name] for name in self.out_series]
        ts_indices_sliced = self._get_time_indices(ts, t1, t2)
        # NOTE: we're using np.clip to determine in/out for dvol (for flows
        # /discharges this shouldn't matter I THINK)
        ts_deltas = np.concatenate(([0], np.diff(ts)))
        # shape = (N_idxs, len(ts))
        balance_tmp = (ts_deltas * ts_series[:, idxs].T).clip(max=0)
        self._balance_out = balance_tmp[:, ts_indices_sliced].sum()

    def calc_balance(self, ts, ts_series, t1=0, t2=None):
        """Calculate balance values."""
        self.set_end_balance_in(ts, ts_series, t1, t2)
        self.set_end_balance_out(ts, ts_series, t1, t2)
        if self.is_storage_like:
            self.convert_to_net()

    def convert_to_net(self):
        """Make a bar that contains the net value (positive or negative).
        """
        # NOTE: use addition because out is negative
        net_val = self._balance_in + self._balance_out
        if net_val > 0:
            self._balance_in = net_val
            self._balance_out = 0
        else:
            self._balance_in = 0
            self._balance_out = net_val

    def invert(self):
        """Flip positive to negative and vice versa."""
        self._balance_in, self._balance_out = \
            -1 * self._balance_out, -1 * self._balance_in

    @property
    def is_storage_like(self):
        return 'storage' in self.label_name

    # add sorting
    def __lt__(self, other):
        # TODO: label_names are not unique, should add 'type' to make a
        # primary key
        if not self.is_storage_like and other.is_storage_like:
            return True
        elif self.is_storage_like and not other.is_storage_like:
            return False
        return self.label_name < other.label_name

class BarManager(object):
    def __init__(self, series):
        self.series = series
        self.bars = sorted([
            Bar(
                label_name=x['label_name'],
                in_series=x['in'],
                out_series=x['out'],
                type=x['type'],
            ) for x in series
        ])

    def calc_balance(
            self, ts, ts_series, t1, t2, net=False, invert=[]):
        for b in self.bars:
            b.calc_balance(ts, ts_series, t1=t1, t2=t2)
            if net:
                b.convert_to_net()
            if b.label_name in invert:
                b.invert()

    @property
    def x(self):
        return np.arange(len(self.bars))

    @property
    def xlabels(self):
        return [b.label_name for b in self.bars]

    @property
    def end_balance_in(self):
        return [b.end_balance_in for b in self.bars]

    @property
    def end_balance_out(self):
        return [b.end_balance_out for b in self.bars]

class WaterbalanceItemTable(QTableView):
    hoverExitRow = pyqtSignal(int)
    hoverExitAllRows = pyqtSignal()  # exit the whole widget
    hoverEnterRow = pyqtSignal(str)

    def __init__(self, parent=None):
        super(WaterbalanceItemTable, self).__init__(parent)
        self.setStyleSheet("QTreeView::item:hover{background-color:#FFFF00;}")
        self.setMouseTracking(True)
        self.model = None

        self._last_hovered_row = None
        self.viewport().installEventFilter(self)

    def on_close(self):
        """
        unloading widget and remove all required stuff
        :return:
        """
        self.setMouseTracking(False)
        self.viewport().removeEventFilter(self)

    def closeEvent(self, event):
        """
        overwrite of QDockWidget class to emit signal
        :param event: QEvent
        """
        self.on_close()
        event.accept()

    def eventFilter(self, widget, event):
        if widget is self.viewport():

            if event.type() == QEvent.MouseMove:
                row = self.indexAt(event.pos()).row()
                if row == 0 and self.model and row > self.model.rowCount():
                    row = None

            elif event.type() == QEvent.Leave:
                row = None
                self.hoverExitAllRows.emit()
            else:
                row = self._last_hovered_row

            if row != self._last_hovered_row:
                if self._last_hovered_row is not None:
                    try:
                        self.hover_exit(self._last_hovered_row)
                    except IndexError:
                        log.warning(
                            "Hover row index %s out of range",
                            self._last_hovered_row)
                        # self.hoverExitRow.emit(self._last_hovered_row)
                # self.hoverEnterRow.emit(row)
                if row is not None:
                    try:
                        self.hover_enter(row)
                    except IndexError:
                        log.warning("Hover row index %s out of range", row),
                self._last_hovered_row = row
                pass
        return QTableView.eventFilter(self, widget, event)

    def hover_exit(self, row_nr):
        if row_nr >= 0:
            item = self.model.rows[row_nr]
            item.color.value = item.color.value[:3] + [150]
            item.hover.value = False

    def hover_enter(self, row_nr):
        if row_nr >= 0:
            item = self.model.rows[row_nr]
            name = item.name.value
            self.hoverEnterRow.emit(name)
            item.color.value = item.color.value[:3] + [220]
            item.hover.value = True

    def setModel(self, model):
        super(WaterbalanceItemTable, self).setModel(model)

        self.model = model

        self.resizeColumnsToContents()
        self.model.set_column_sizes_on_view(self)

class WaterBalancePlotWidget(pg.PlotWidget):
    def __init__(self, parent=None, name=""):

        super(WaterBalancePlotWidget, self).__init__(parent)
        self.name = name
        self.showGrid(True, True, 0.5)
        self.setLabel("bottom", "tijd", "s")
        self.setLabel("left", "Debiet", "m3/s")
        # Auto SI prefix scaling doesn't work properly with m3, m2 etc.
        self.getAxis("left").enableAutoSIPrefix(False)
        self.series = {}

    def setModel(self, model):
        self.model = model
        self.model.dataChanged.connect(self.data_changed)
        self.model.rowsInserted.connect(self.on_insert)
        self.model.rowsAboutToBeRemoved.connect(
            self.on_remove)

    def on_remove(self):
        self.draw_timeseries()

    def on_insert(self):
        self.draw_timeseries()

    def draw_timeseries(self):
        self.clear()

        ts = self.model.ts

        zeros = np.zeros(shape=(np.size(ts, 0),))
        zero_serie = pg.PlotDataItem(
            x=ts,
            y=zeros,
            connect='finite',
            pen=pg.mkPen(color=QColor(0, 0, 0, 220), width=1))
        self.addItem(zero_serie)

        for dir in ['in', 'out']:
            prev_serie = zeros
            prev_pldi = zero_serie
            for item in self.model.rows:
                if item.active.value:
                    cum_serie = prev_serie + item.ts_series.value[dir]
                    plot_item = pg.PlotDataItem(
                        x=ts,
                        y=cum_serie,
                        connect='finite',
                        pen=pg.mkPen(color=QColor(*item.color.value), width=1))

                    color = item.color.value
                    fill = pg.FillBetweenItem(prev_pldi,
                                              plot_item,
                                              pg.mkBrush(*color))

                    # keep reference
                    item._plots[dir] = plot_item
                    item._plots[dir + 'fill'] = fill
                    prev_serie = cum_serie
                    prev_pldi = plot_item

        for dir in ['in', 'out']:
            for item in reversed(self.model.rows):
                if item.active.value:
                    self.addItem(item._plots[dir])
                    self.addItem(item._plots[dir + 'fill'])

        self.autoRange()

    def data_changed(self, index):
        """
        change graphs based on changes in locations
        :param index: index of changed field
        """
        if self.model.columns[index.column()].name == 'active':
            self.draw_timeseries()

        elif self.model.columns[index.column()].name == 'hover':
            item = self.model.rows[index.row()]
            if item.hover.value:
                if item.active.value:
                    if 'in' in item._plots:
                        item._plots['in'].setPen(color=item.color.value,
                                                 width=1)
                        item._plots['infill'].setBrush(
                            pg.mkBrush(item.color.value))
                    if 'out' in item._plots:
                        item._plots['out'].setPen(color=item.color.value,
                                                  width=1)
                        item._plots['outfill'].setBrush(
                            pg.mkBrush(item.color.value))
            else:
                if item.active.value:
                    if 'in' in item._plots:
                        item._plots['in'].setPen(color=item.color.value,
                                                 width=1)
                        item._plots['infill'].setBrush(
                            pg.mkBrush(item.color.value))
                    if 'out' in item._plots:
                        item._plots['out'].setPen(color=item.color.value,
                                                 width=1)
                    item._plots['outfill'].setBrush(
                        pg.mkBrush(item.color.value))

class WaterBalanceWidget(QDockWidget):
    closingWidget = pyqtSignal()

    INPUT_SERIES = INPUT_SERIES

    IN_OUT_SERIES = [
        {
            'label_name': '1D-2D flow',
            'in': ['1d_2d_in'],
            'out': ['1d_2d_in'],
            'type': '1d',
        }, {
            'label_name': '1D-2D flow',
            'in': ['1d_2d_out'],
            'out': ['1d_2d_out'],
            'type': '2d',
        }, {
            'label_name': '1D-2D flow (all domains)',
            # does this make sense?
            'in': ['1d_2d_in', '1d_2d_out'],
            'out': ['1d_2d_in', '1d_2d_out'],
            'type': 'NETVOL',
        }, {
            'label_name': 'net change in storage',
            'in': ['d_2d_vol'],
            'out': ['d_2d_vol'],
            'type': '2d',
        }, {
            'label_name': 'net change in storage',
            'in': ['d_1d_vol'],
            'out': ['d_1d_vol'],
            'type': '1d',
        }, {
            'label_name': 'net change in storage',
            'in': ['d_2d_groundwater_vol'],
            'out': ['d_2d_groundwater_vol'],
            'type': '2d_groundwater',
        }, {
            'label_name': 'leakage',
            'in': ['leak'],
            'out': ['leak'],
            'type': '2d_groundwater',
        }, {
            'label_name': 'simple infiltration',
            'in': ['infiltration_rate_simple'],
            'out': ['infiltration_rate_simple'],
            'type': '2d',
        }, {
            'label_name': '2D flow',
            'in': ['2d_in'],
            'out': ['2d_out'],
            'type': '2d',
        }, {
            'label_name': '1D flow',
            'in': ['1d_in'],
            'out': ['1d_out'],
            'type': '1d',
        }, {
            'label_name': '2D groundwater flow',
            'in': ['2d_groundwater_in'],
            'out': ['2d_groundwater_out'],
            'type': '2d_groundwater',
        }, {
            'label_name': '2D laterals',
            'in': ['lat_2d'],
            'out': ['lat_2d'],
            'type': '2d',
        }, {
            'label_name': '1D laterals',
            'in': ['lat_1d'],
            'out': ['lat_1d'],
            'type': '1d',
        }, {
            'label_name': '2D boundaries',
            'in': ['2d_bound_in'],
            'out': ['2d_bound_out'],
            'type': '2d',
        }, {
            'label_name': '1D boundaries',
            'in': ['1d_bound_in'],
            'out': ['1d_bound_out'],
            'type': '1d',
        }, {
            'label_name': '1D-2D exchange',
            'in': ['2d_to_1d_pos'],
            'out': ['2d_to_1d_neg'],
            'type': '1d_2d',
        }, {
            'label_name': '1D inflow from rain',
            'in': ['inflow'],
            'out': ['inflow'],
            'type': '1d',
        }, {
            'label_name': 'infiltration/exfiltration (domain exchange)',
            # NOTE: for the argument why pos is out and neg is in, see the
            # comment in ``WaterBalanceCalculation.get_aggregated_flows``
            'in': ['2d_vertical_infiltration_neg'],
            'out': ['2d_vertical_infiltration_pos'],
            'type': '2d_vert',
        }, {
            'label_name': 'change in storage',
            'in': ['d_2d_vol', 'd_2d_groundwater_vol', 'd_1d_vol'],
            'out': ['d_2d_vol', 'd_2d_groundwater_vol', 'd_1d_vol'],
            'type': 'NETVOL',
        }, {
            'label_name': 'pump',
            'in': ['pump_in'],
            'out': ['pump_out'],
            'type': '1d',
        }, {
            'label_name': 'rain',
            'in': ['rain'],
            'out': ['rain'],
            'type': '2d',
        }
    ]

    def __init__(
            self, parent=None, iface=None, ts_datasource=None, wb_calc=None):
        """Constructor."""
        super(WaterBalanceWidget, self).__init__(parent)

        self.aggregation_warning_issued_on_start = False
        self.iface = iface
        self.ts_datasource = ts_datasource
        self.calc = wb_calc

        # setup ui
        self.setup_ui(self)

        self.model = WaterbalanceItemModel()
        self.wb_item_table.setModel(self.model)
        self.plot_widget.setModel(self.model)

        # link tool
        self.polygon_tool = PolygonDrawTool(self.iface.mapCanvas(),
                                            self.select_polygon_button,
                                            self.on_polygon_ready)

        # fill comboboxes with selections
        self.modelpart_combo_box.insertItems(
            0,
            ['1d 2d', '1d', '2d'])
        #  self.modelpart_combo_box.setCurrentIndex(0)
        self.source_nc_combo_box.insertItems(
            0,
            ['normal', 'aggregation'])
        self.sum_type_combo_box.insertItems(
            0,
            serie_settings.keys())

        self.agg_combo_box.insertItems(
            0,
            ['m3/s natural', 'm3 cumulative natural', 'm3/s',
             'm3 cumulative'])

        # add listeners
        self.select_polygon_button.toggled.connect(self.toggle_polygon_button)
        self.reset_waterbalans_button.clicked.connect(self.reset_waterbalans)
        self.chart_button.clicked.connect(self.show_chart)
        # self.polygon_tool.deactivated.connect(self.update_wb)
        self.modelpart_combo_box.currentIndexChanged.connect(self.update_wb)
        self.source_nc_combo_box.currentIndexChanged.connect(self.update_wb)
        self.source_nc_combo_box.currentIndexChanged.connect(
            self.issue_warning)
        self.sum_type_combo_box.currentIndexChanged.connect(self.update_wb)
        self.agg_combo_box.currentIndexChanged.connect(self.update_wb)
        self.wb_item_table.hoverEnterRow.connect(
            self.hover_enter_map_visualization)
        self.wb_item_table.hoverExitAllRows.connect(
            self.hover_exit_map_visualization)

        # TODO: is this a good default?
        # initially turn on tool
        self.select_polygon_button.toggle()

        self.__current_calc = None  # cache the results of calculation

    def show_chart(self):
        if not self._current_calc:
            return
        ts, ts_series = self._current_calc

        io_series_net = [
            x for x in self.IN_OUT_SERIES if (
                x['type'] in [
                    '2d', '1d_2d', '2d_vert', '2d_groundwater', '1d'] and
                'storage' not in x['label_name'] and
                'exchange' not in x['label_name'] and
                x['label_name'] != '1D-2D flow') or
            x['type'] == 'NETVOL'
        ]

        io_series_2d = [
            x for x in self.IN_OUT_SERIES if x['type'] in
            ['2d', '1d_2d', '2d_vert']
        ]

        io_series_2d_groundwater = [
            x for x in self.IN_OUT_SERIES if x['type'] in [
                '2d_groundwater', '2d_vert']
        ]

        io_series_1d = [
            x for x in self.IN_OUT_SERIES if x['type'] in [
                '1d', '1d_2d']
        ]

        # get timeseries x range in plot widget
        viewbox_state = self.plot_widget.getPlotItem().getViewBox().getState()
        view_range = viewbox_state['viewRange']
        t1, t2 = view_range[0]

        bm_net = BarManager(io_series_net)
        bm_2d = BarManager(io_series_2d)
        bm_2d_groundwater = BarManager(io_series_2d_groundwater)
        bm_1d = BarManager(io_series_1d)

        bm_net.calc_balance(ts, ts_series, t1, t2, net=True)
        bm_2d.calc_balance(ts, ts_series, t1, t2)
        bm_2d_groundwater.calc_balance(
            ts, ts_series, t1, t2,
            invert=['infiltration/exfiltration (domain exchange)']
        )
        bm_1d.calc_balance(ts, ts_series, t1, t2, invert=['1D-2D exchange'])

        # init figure
        plt.close()
        plt.figure(1)  # TODO: what does this do?
        plt.suptitle("Water balance from t=%.2f to t=%.2f" % (t1, t2))

        # prevent clipping of tick-labels, among others
        plt.subplots_adjust(
            bottom=.3, top=.9, left=.125, right=.9, hspace=1, wspace=.4)

        pattern = '//'

        # #####
        # Net #
        # #####

        plt.subplot(221)
        plt.axhline(color='black', lw=.5)
        bar_in = plt.bar(bm_net.x, bm_net.end_balance_in, label='In')
        bar_out = plt.bar(bm_net.x, bm_net.end_balance_out, label='Out')
        bar_in[-1].set_hatch(pattern)
        bar_out[-1].set_hatch(pattern)
        plt.xticks(bm_net.x, bm_net.xlabels, rotation=45, ha='right')
        plt.title('Net water balance')
        plt.ylabel(r'volume ($m^3$)')
        plt.legend()

        # ####
        # 2D #
        # ####

        # this axes object will be shared by the other subplots to give them
        # the same y alignment
        ax1 = plt.subplot(234)

        plt.axhline(color='black', lw=.5)
        bar_in = plt.bar(bm_2d.x, bm_2d.end_balance_in, label='In')
        bar_out = plt.bar(bm_2d.x, bm_2d.end_balance_out, label='Out')
        bar_in[-1].set_hatch(pattern)
        bar_out[-1].set_hatch(pattern)
        plt.xticks(bm_2d.x, bm_2d.xlabels, rotation=45, ha='right')
        plt.title('2D surface water domain')
        plt.ylabel(r'volume ($m^3$)')
        plt.legend()

        # ################
        # 2D groundwater #
        # ################

        plt.subplot(235, sharey=ax1)
        plt.axhline(color='black', lw=.5)
        bar_in = plt.bar(
            bm_2d_groundwater.x, bm_2d_groundwater.end_balance_in, label='In')
        bar_out = plt.bar(
            bm_2d_groundwater.x, bm_2d_groundwater.end_balance_out,
            label='Out')
        bar_in[-1].set_hatch(pattern)
        bar_out[-1].set_hatch(pattern)
        plt.xticks(
            bm_2d_groundwater.x, bm_2d_groundwater.xlabels, rotation=45,
            ha='right')
        plt.title('2D groundwater domain')
        plt.ylabel(r'volume ($m^3$)')
        plt.legend()

        # ####
        # 1D #
        # ####

        plt.subplot(236, sharey=ax1)
        plt.axhline(color='black', lw=.5)
        bar_in = plt.bar(bm_1d.x, bm_1d.end_balance_in, label='In')
        bar_out = plt.bar(bm_1d.x, bm_1d.end_balance_out, label='Out')
        bar_in[-1].set_hatch(pattern)
        bar_out[-1].set_hatch(pattern)
        plt.xticks(bm_1d.x, bm_1d.xlabels, rotation=45, ha='right')
        plt.title('1D network domain')
        plt.ylabel(r'volume ($m^3$)')
        plt.legend()

        plt.show()

    def hover_enter_map_visualization(self, name):
        """On hover rubberband visualisation using the table item name.

        Uses the cached self.qgs_lines/self.qgs_points.
        """
        if self.select_polygon_button.isChecked():
            # highlighting when drawing the polygon doesn't look right.
            # this is the best solution I can think of atm...
            return
        types_2d_line = [
            '2d flow',
        ]
        types_2d_node = [
            'volumeverandering 2d',
        ]
        # TODO 1: generate this dict

        # TODO 2: 1d-2d flow isn't completely correct for 2d
        # and 1d flow because those only only take either 1d_2d_in or
        # 1d_2d_out and we're using the aggregated 1d_2d_intersected

        # TODO 3: using the name as key is INCREDIBLY error prone: one
        # spelling mistake or a change in sum_configs and it doesn't work
        # anymore, and because we also catch the KeyErrors you won't even
        # notice. NEEDS TO BE FIXED

        NAME_TO_LINE_TYPES_ALLES = {
            '2d flow': ['2d'],
            '2d boundaries': ['2d_bound'],
            '1d flow': ['1d'],
            '1d boundaries': ['1d_bound'],
            '1d-2d uitwisseling': ['1d_2d'],
            # TODO: '1d_2d_intersected' and 'pump_or_whatever' are magic
            # strings that we ad-hoc created in the
            # prepare_and_visualize_selection function.
            # A better solution would be nice...
            '1d-2d flow': ['1d_2d_intersected'],
            'pompen': ['pump_or_whatever'],
            '2d groundwater flow': ['2d_groundwater'],
        }
        NAME_TO_LINE_TYPES_HOOFDSTROMEN = {
            '2d flow': ['2d', '2d_bound', '1d_2d_intersected'],
            '1d flow': [
                '1d', 'pump_or_whatever', '1d_bound', '1d_2d_intersected'],
            '1d-2d uitwisseling': ['1d_2d'],
            '2d groundwater flow': ['2d_groundwater'],
        }
        NAME_TO_NODE_TYPES = {
            'volumeverandering': ['1d', '2d', '2d_groundwater'],
            'volumeverandering 2d': ['2d'],
            'volumeverandering 1d': ['1d'],
            'volumeverandering 2d grondwater': ['2d_groundwater'],
            'neerslag': ['2d'],
            'inflow 1d from rain': ['1d'],
            'lateraal 1d': ['1d'],
            'lateraal 2d': ['2d'],
            'leakage': ['2d'],
            'infiltratie': ['2d'],
            'belasting (regen en lateralen)': ['1d', '2d'],
        }

        # more hackery to fix keys defined in both 'hoofdstromen'
        # and 'alles'.
        sum_type = self.sum_type_combo_box.currentText()
        assert sum_type in ['hoofdstromen', 'alles']
        if sum_type == 'hoofdstromen':
            name_to_line_type = NAME_TO_LINE_TYPES_HOOFDSTROMEN
        elif sum_type == 'alles':
            name_to_line_type = NAME_TO_LINE_TYPES_ALLES
        else:
            raise ValueError("Unknown type %s" % sum_type)

        try:
            types_line = name_to_line_type[name]
        except KeyError:
            line_geoms = []
        else:
            line_geoms = []
            for t in types_line:
                try:
                    geoms = self.qgs_lines[t]
                except KeyError:
                    continue
                line_geoms.extend(geoms)
        try:
            types_node = NAME_TO_NODE_TYPES[name]
        except KeyError:
            point_geoms = []
        else:
            point_geoms = []
            for t in types_node:
                try:
                    geoms = self.qgs_points[t]
                except KeyError:
                    continue
                point_geoms.extend(geoms)
        self.polygon_tool.selection_vis.update(line_geoms, point_geoms)

    def hover_exit_map_visualization(self, *args):
        self.polygon_tool.selection_vis.reset()

    def on_polygon_ready(self, points):
        self.iface.mapCanvas().unsetMapTool(self.polygon_tool)

    def reset_waterbalans(self):
        self.polygon_tool.reset()

    def toggle_polygon_button(self):

        if self.select_polygon_button.isChecked():
            self.reset_waterbalans()

            self.iface.mapCanvas().setMapTool(self.polygon_tool)

            self.select_polygon_button.setText(_translate(
                "DockWidget", "Gebied tekenen afronden", None))
        else:
            self.iface.mapCanvas().unsetMapTool(self.polygon_tool)
            self.update_wb()
            self.select_polygon_button.setText(_translate(
                "DockWidget", "Teken nieuw gebied", None))

            if not self.aggregation_warning_issued_on_start:
                self.issue_warning()
                self.aggregation_warning_issued_on_start = True

    def redraw_wb(self):
        pass

    def update_wb(self):

        try:
            ts, graph_series = self.calc_wb(
                self.modelpart_combo_box.currentText(),
                self.source_nc_combo_box.currentText(),
                self.agg_combo_box.currentText(),
                serie_settings[self.sum_type_combo_box.currentText()])
        except self.calc.AggregationFileNotFoundError:
            QMessageBox.warning(
                None,
                "No aggregation file found",
                "The 'aggregation' option requires an aggregation NetCDF "
                "file with the following variables:"
                "\n\ncumulative:\n- rain\n- infiltration\n- laterals"
                "\n- leakage\n- discharge\n- pump discharge"
                "\n\npositive cumulative:\n- discharge"
                "\n\nnegative cumulative:\n- discharge"
            )
            return

        self.model.removeRows(0, len(self.model.rows))

        self.model.ts = ts
        self.model.insertRows(graph_series['items'])

        if self.agg_combo_box.currentText() == 'm3/s':
            self.plot_widget.setLabel("left", "Debiet", "m3/s")
        elif self.agg_combo_box.currentText() == 'm3 cumulative':
            self.plot_widget.setLabel("left", "Cumulatieve debiet", "m3")
        elif self.agg_combo_box.currentText() == 'm3/s natural':
            self.plot_widget.setLabel("left", "Debiet", "m3/s")
        elif self.agg_combo_box.currentText() == 'm3 cumulative natural':
            self.plot_widget.setLabel("left", "Cumulatieve debiet", "m3")
        else:
            self.plot_widget.setLabel("left", "-", "-")

        # set labels for in and out fluxes
        text_upper = pg.TextItem(text="in", anchor=(0, 1), angle=-90)
        text_upper.setPos(0, 0)
        text_lower = pg.TextItem(text="uit", anchor=(1, 1), angle=-90)
        text_lower.setPos(0, 0)
        self.plot_widget.addItem(text_upper)
        self.plot_widget.addItem(text_lower)

    @property
    def _current_calc(self):
        return self.__current_calc

    @_current_calc.setter
    def _current_calc(self, ts_total_time_tuple):
        # NOTE: flips the sign on dvol to make things more intuitive for
        # barcharts
        ts, ts_series = ts_total_time_tuple
        ts_series = ts_series.copy()
        # indices for dvol of 2d, 1d, and groundwater
        if self.reverse_dvol_sign:
            ts_series[:, [18, 19, 25]] = -1 * ts_series[:, [18, 19, 25]]
        self.__current_calc = (ts, ts_series)

    @property
    def reverse_dvol_sign(self):
        aggregation_type = self.agg_combo_box.currentText()
        return aggregation_type in ['m3/s', 'm3 cumulative']

    def calc_wb(self, model_part, source_nc, aggregation_type, settings):
        poly_points = self.polygon_tool.points
        wb_polygon = QgsGeometry.fromPolygon([poly_points])

        lines, points, pumps = \
            self.ts_datasource.rows[0].get_result_layers()
        tr = QgsCoordinateTransform(
            self.iface.mapCanvas().mapRenderer().destinationCrs(), lines.crs())
        wb_polygon.transform(tr)

        link_ids, pump_ids = self.calc.get_incoming_and_outcoming_link_ids(
            wb_polygon, model_part)
        node_ids = self.calc.get_nodes(wb_polygon, model_part)

        ts, total_time = self.calc.get_aggregated_flows(
            link_ids, pump_ids, node_ids, model_part, source_nc,
            reverse_dvol_sign=self.reverse_dvol_sign)

        # cache data for barchart
        self._current_calc = (ts, total_time)

        graph_series = self.make_graph_series(
            ts, total_time, model_part, aggregation_type, settings)

        self.prepare_and_visualize_selection(
            link_ids, pump_ids, node_ids, lines, pumps, points)

        return ts, graph_series

    def prepare_and_visualize_selection(
            self, link_ids, pump_ids, node_ids, lines, pumps, points,
            draw_it=False):
        """Prepare dictionaries with geometries categorized by type and
        save it on self.qgs_lines and self.qgs_points.
        """
        req_filter_links = _get_request_filter(link_ids)
        req_filter_pumps = _get_request_filter(pump_ids)
        req_filter_nodes = _get_request_filter(node_ids)

        line_id_to_type = {}
        for _type, id_list in link_ids.items():
            for i in id_list:
                # we're not interested in in or out types, but for 1d_2d_in
                # and 1d_2d_out we need to employ this hack because these
                # types end in '_in'/'_out'
                if _type == '1d_2d_in' or _type == '1d_2d_out':
                    t = '1d_2d_intersected'
                else:
                    t = _type.rsplit('_out')[0].rsplit('_in')[0]

                if i not in line_id_to_type:
                    # business as usual
                    line_id_to_type[i] = t
                else:
                    # NOTE: awful hack for links that have multiple types
                    val = line_id_to_type[i]
                    if isinstance(val, list):
                        val.append(t)
                    else:
                        line_id_to_type[i] = [val, t]

        # pump_id_to_type = {}
        # for _, id_list in pump_ids.items():
        #     for i in id_list:
        #         pump_id_to_type[i] = 'all'
        node_id_to_type = {}
        for _type, id_list in node_ids.items():
            for i in id_list:
                node_id_to_type[i] = _type

        qgs_lines = {}
        qgs_points = {}
        tr_reverse = QgsCoordinateTransform(
            lines.crs(),
            self.iface.mapCanvas().mapRenderer().destinationCrs(),
        )

        # NOTE: getting all features again isn't efficient because they're
        # already calculated in WaterBalanceCalculation, but w/e
        for feat in _get_feature_iterator(lines, req_filter_links):
            geom = feat.geometry()
            geom.transform(tr_reverse)
            _type = line_id_to_type[feat['id']]

            if isinstance(_type, list):
                # NOTE: this means there are multiple types for one link
                for t in _type:
                    qgs_lines.setdefault(t, []).append(geom.asPolyline())
            else:
                # one type only, business as usual
                qgs_lines.setdefault(_type, []).append(geom.asPolyline())
        for feat in _get_feature_iterator(pumps, req_filter_pumps):
            geom = feat.geometry()
            geom.transform(tr_reverse)
            qgs_lines.setdefault('pump_or_whatever', []).append(
                geom.asPolyline())
        for feat in _get_feature_iterator(points, req_filter_nodes):
            geom = feat.geometry()
            geom.transform(tr_reverse)
            _type = node_id_to_type[feat['id']]
            qgs_points.setdefault(_type, []).append(geom.asPoint())

        self.qgs_lines = qgs_lines
        self.qgs_points = qgs_points

        # draw the lines/points immediately
        # TODO: probably need to throw this code away since we won't use it
        if draw_it:
            qgs_lines_all = [j for i in qgs_lines.values() for j in i]
            qgs_points_all = [j for i in qgs_points.values() for j in i]

            self.polygon_tool.update_line_point_selection(
                qgs_lines_all, qgs_points_all)

    def make_graph_series(
            self, ts, total_time, model_part, aggregation_type, settings):
        settings = copy.deepcopy(settings)

        if model_part == '1d 2d':
            input_series = dict([
                (x, y) for (x, y, z) in self.INPUT_SERIES
                if z in ['2d', '1d', 'error_1d_2d']])
        elif model_part == '2d':
            input_series = dict([
                (x, y) for (x, y, z) in self.INPUT_SERIES
                if z in ['2d', '1d_2d', 'error_2d']])
        elif model_part == '1d':
            input_series = dict([
                (x, y) for (x, y, z) in self.INPUT_SERIES
                if z in ['1d', '1d_2d', 'error_1d']])
            idx_2d_to_1d_pos = input_series['2d_to_1d_pos']
            idx_2d_to_1d_neg = input_series['2d_to_1d_neg']
            idx_2d_to_1d = (idx_2d_to_1d_pos, idx_2d_to_1d_neg)
            total_time[:, idx_2d_to_1d] = total_time[:, idx_2d_to_1d] * -1

        # TODO: figure out why the hell np.clip is needed.
        for serie_setting in settings.get('items', []):
            serie_setting['active'] = True
            serie_setting['method'] = serie_setting['default_method']
            serie_setting['color'] = [int(c) for c in serie_setting['def_color'].split(',')] + [150]
            serie_setting['ts_series'] = {}
            nrs_input_series = []
            for serie in serie_setting['series']:
                if serie not in input_series:
                    # throw good error message
                    log.warning('serie config error: %s is an unknown serie or is doubled in the config.', serie)
                else:
                    nrs_input_series.append(input_series[serie])
                    del input_series[serie]

            if serie_setting['default_method'] == 'net':
                sum = total_time[:, nrs_input_series].sum(axis=1)
                serie_setting['ts_series']['in'] = sum.clip(min=0)
                serie_setting['ts_series']['out'] = sum.clip(max=0)
            elif serie_setting['default_method'] == 'gross':
                sum_pos = np.zeros(shape=(np.size(ts, 0),))
                sum_neg = np.zeros(shape=(np.size(ts, 0),))
                for nr in nrs_input_series:
                    sum_pos += total_time[:, nr].clip(min=0)
                    sum_neg += total_time[:, nr].clip(max=0)
                serie_setting['ts_series']['in'] = sum_pos
                serie_setting['ts_series']['out'] = sum_neg
            else:
                # throw config error
                log.warning('aggregation %s method unknown.', serie_setting['default_method'])

            if aggregation_type == 'm3 cumulative' or \
                    aggregation_type == 'm3 cumulative natural':
                log.debug('aggregate')
                diff = np.append([0], np.diff(ts))

                serie_setting['ts_series']['in'] = serie_setting['ts_series']['in'] * diff
                serie_setting['ts_series']['in'] = np.cumsum(serie_setting['ts_series']['in'], axis=0)

                serie_setting['ts_series']['out'] = serie_setting['ts_series']['out'] * diff
                serie_setting['ts_series']['out'] = np.cumsum(serie_setting['ts_series']['out'], axis=0)

        if len(input_series) > 0:

            serie_setting = {
                'name': 'Overige',
                'default_method': settings['remnant_method'],
                'order': 100,
                'color': [int(c) for c in settings['remnant_def_color'].split(',')] + [150],
                'def_color': settings['remnant_def_color'], #TODO: fix + [150],
                'series': [key for key in input_series],
                'ts_series': {}
            }
            for serie in input_series:
                nrs_input_series.append(input_series[serie])

            if serie_setting['default_method'] == 'net':
                sum = total_time[:, nrs_input_series].sum(axis=1)
                serie_setting['ts_series']['in'] = sum.clip(min=0)
                serie_setting['ts_series']['out'] = sum.clip(max=0)
            elif serie_setting['default_method'] == 'gross':
                sum_pos = np.zeros(shape=(np.size(ts, 0),))
                sum_neg = np.zeros(shape=(np.size(ts, 0),))
                for nr in nrs_input_series:
                    sum_pos += total_time[:, nr].clip(min=0)
                    sum_neg += total_time[:, nr].clip(max=0)
                serie_setting['ts_series']['in'] = sum_pos
                serie_setting['ts_series']['out'] = sum_neg
            else:
                # throw config error
                log.warning('aggregation %s method unknown.', serie_setting['default_method'])

            settings['items'].append(serie_setting)

        if model_part == '1d':
            total_time[:, (10, 11)] = total_time[:, (10, 11)] * -1

        settings['items'] = sorted(settings['items'], key=lambda item: item['order'])

        return settings

    def unset_tool(self):
        pass

    def accept(self):
        pass

    def reject(self):
        self.close()

    def closeEvent(self, event):
        self.select_polygon_button.toggled.disconnect(
            self.toggle_polygon_button)
        self.reset_waterbalans_button.clicked.disconnect(
            self.reset_waterbalans)
        self.chart_button.clicked.disconnect(self.show_chart)
        # self.polygon_tool.deactivated.disconnect(self.update_wb)
        self.iface.mapCanvas().unsetMapTool(self.polygon_tool)
        self.polygon_tool.close()

        self.modelpart_combo_box.currentIndexChanged.disconnect(self.update_wb)
        self.source_nc_combo_box.currentIndexChanged.disconnect(self.update_wb)
        self.sum_type_combo_box.currentIndexChanged.disconnect(self.update_wb)
        self.wb_item_table.hoverEnterRow.disconnect(
            self.hover_enter_map_visualization)
        self.wb_item_table.hoverExitAllRows.disconnect(
            self.hover_exit_map_visualization)

        self.closingWidget.emit()
        event.accept()

    def issue_warning(self):
        mode = self.source_nc_combo_box.currentText()
        if mode == 'normal':
            QMessageBox.information(
                None,
                "Information",
                "You're currently using the 'normal' NetCDF result file which "
                "may result in a less accurate overall water balance "
                "compared to the 'aggregation' NetCDF file, depending on the "
                "output time step that was chosen. Proceed at your own "
                "discretion. Please select the 'aggregation' option "
                "for the most accurate results."
            )

    def setup_ui(self, dock_widget):
        """
        initiate main Qt building blocks of interface
        :param dock_widget: QDockWidget instance
        """

        dock_widget.setObjectName("dock_widget")
        dock_widget.setAttribute(Qt.WA_DeleteOnClose)

        self.dock_widget_content = QWidget(self)
        self.dock_widget_content.setObjectName("dockWidgetContent")

        self.main_vlayout = QVBoxLayout(self)
        self.dock_widget_content.setLayout(self.main_vlayout)

        # add button to add objects to graphs
        self.button_bar_hlayout = QHBoxLayout(self)
        self.select_polygon_button = QPushButton(self)
        self.select_polygon_button.setCheckable(True)
        self.select_polygon_button.setObjectName("SelectedSideview")
        self.button_bar_hlayout.addWidget(self.select_polygon_button)
        self.reset_waterbalans_button = QPushButton(self)
        self.reset_waterbalans_button.setObjectName("ResetSideview")
        self.button_bar_hlayout.addWidget(self.reset_waterbalans_button)
        self.chart_button = QPushButton(self)
        self.button_bar_hlayout.addWidget(self.chart_button)

        self.modelpart_combo_box = QComboBox(self)
        self.button_bar_hlayout.addWidget(self.modelpart_combo_box)
        self.source_nc_combo_box = QComboBox(self)
        self.button_bar_hlayout.addWidget(self.source_nc_combo_box)
        self.sum_type_combo_box = QComboBox(self)
        self.button_bar_hlayout.addWidget(self.sum_type_combo_box)

        self.agg_combo_box = QComboBox(self)
        self.button_bar_hlayout.addWidget(self.agg_combo_box)

        spacer_item = QSpacerItem(40,
                                  20,
                                  QSizePolicy.Expanding,
                                  QSizePolicy.Minimum)
        self.button_bar_hlayout.addItem(spacer_item)
        self.main_vlayout.addLayout(self.button_bar_hlayout)

        # add tabWidget for graphWidgets
        self.contentLayout = QHBoxLayout(self)

        # Graph
        self.plot_widget = WaterBalancePlotWidget(self)
        sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(1)
        sizePolicy.setVerticalStretch(1)
        sizePolicy.setHeightForWidth(
            self.plot_widget.sizePolicy().hasHeightForWidth())
        self.plot_widget.setSizePolicy(sizePolicy)
        self.plot_widget.setMinimumSize(QSize(250, 250))

        self.contentLayout.addWidget(self.plot_widget)

        # table
        self.wb_item_table = WaterbalanceItemTable(self)
        sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(
            self.wb_item_table.sizePolicy().hasHeightForWidth())
        self.wb_item_table.setSizePolicy(sizePolicy)
        self.wb_item_table.setMinimumSize(QSize(300, 0))

        self.contentLayout.addWidget(self.wb_item_table)

        self.main_vlayout.addLayout(self.contentLayout)

        # add dockwidget
        dock_widget.setWidget(self.dock_widget_content)
        self.retranslate_ui(dock_widget)
        QMetaObject.connectSlotsByName(dock_widget)

    def retranslate_ui(self, dock_widget):
        pass
        dock_widget.setWindowTitle(_translate(
            "DockWidget", "3Di waterbalans", None))
        self.select_polygon_button.setText(_translate(
            "DockWidget", "Teken nieuw gebied", None))
        self.chart_button.setText(_translate(
            "DockWidget", "Toon totale balans", None))
        self.reset_waterbalans_button.setText(_translate(
            "DockWidget", "Verberg op kaart", None))


from typing import Optional, List
from fastapi import FastAPI, Request, Query
import sys
sys.path.insert(0,'../..')
from importlib import import_module

from python_apps.p20_ml_prediction import main

app = FastAPI()

# Creates new instance.
my_instance = main.p20_ml()

# Defaults
sample_data="Lee"

@app.get("/")
def read_root():
    """
    Displays main page
    """
    return (my_instance.predict_nationality(sample_data))

@app.get("/names/")
async def read_item(request: Request, name: str = Query(None, min_length=3, max_length=50)):
    if request.method == 'GET':
        namequery = request.query_params["name"]
        data = [namequery]
        model_file="/tmp/naive_bayes.pkl"
        result = my_instance.predict_nationality(data, model_file)
        return {"orig_name": data, "prediction": result, "model_file_used": model_file}

# uvicorn 20_ml1:app --reload
# http://127.0.0.1:8000/names/?name=Lee


import html
import json
from typing import Union
from uuid import uuid4

from django.contrib.postgres.fields import ArrayField
from django.db import models
from django.urls import reverse
from django.utils.html import strip_tags
from django.utils.translation import ugettext_lazy as _

from filer.fields.file import FilerFileField
from filer.fields.image import FilerImageField
from openformsclient.models import OpenFormsSlugField
from ordered_model.models import OrderedModel

from open_inwoner.utils.validators import DutchPhoneNumberValidator

from ..managers import ProductQueryset
from .mixins import GeoModel

class CategoryProduct(OrderedModel):
    """
    explicit many2many through model
    """

    category = models.ForeignKey("pdc.Category", on_delete=models.CASCADE)
    product = models.ForeignKey("pdc.Product", on_delete=models.CASCADE)
    order_with_respect_to = "category"

    class Meta:
        ordering = ("category", "product")

    def get_product_name(self):
        return self.product.name

    get_product_name.short_description = _("Name")

class Product(models.Model):
    name = models.CharField(
        verbose_name=_("Name"), max_length=100, help_text=_("Name of the product")
    )
    slug = models.SlugField(
        verbose_name=_("Slug"),
        max_length=100,
        unique=True,
        help_text=_("Slug of the product"),
    )
    published = models.BooleanField(
        verbose_name=_("Published"),
        default=False,
        help_text=_("Whether the product should be published or not."),
    )
    summary = models.TextField(
        verbose_name=_("Summary"),
        default="",
        max_length=300,
        help_text=_("Short description of the product, limited to 300 characters."),
    )
    content_is_collapsable = models.BooleanField(
        verbose_name=_("Make product content collapsable"),
        default=False,
        help_text=_("Whether the user can expand and collapse the product content."),
    )
    icon = FilerImageField(
        verbose_name=_("Icon"),
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="product_icons",
        help_text=_("Icon of the product"),
    )
    image = FilerImageField(
        verbose_name=_("Image"),
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="product_images",
        help_text=_("Image of the product"),
    )
    link = models.URLField(
        verbose_name=_("Link"),
        blank=True,
        default="",
        help_text=_("Action link to request the product."),
    )
    button_text = models.CharField(
        verbose_name=_("CTA Button text"),
        max_length=64,
        blank=True,
        default=_("Aanvraag starten"),
        help_text=_("The text displayed on the button."),
    )
    form = OpenFormsSlugField(
        _("Request form"),
        blank=True,
        help_text=_(
            "Select a form to show this form on the product page. If a form is selected, the link will not be shown."
        ),
    )
    content = models.TextField(
        verbose_name=_("Content"),
        help_text=_(
            "Product content with build-in WYSIWYG editor. By adding '[CTABUTTON]' you can embed a cta-button for linking to the defined form or link"
        ),
    )
    video = models.ForeignKey(
        "media.Video",
        blank=True,
        null=True,
        verbose_name=_("Video"),
        on_delete=models.PROTECT,
        related_name="products",
        help_text=_("Video to show after the intro"),
    )
    categories = models.ManyToManyField(
        "pdc.Category",
        verbose_name=_("Categories"),
        related_name="products",
        help_text=_("Categories which the product relates to"),
        through=CategoryProduct,
    )
    related_products = models.ManyToManyField(
        "pdc.Product",
        verbose_name=_("Related products"),
        blank=True,
        help_text=_("Related products to this product"),
    )
    tags = models.ManyToManyField(
        "pdc.Tag",
        verbose_name=_("Tags"),
        blank=True,
        related_name="products",
        help_text=_("Tags which the product is linked to"),
    )
    costs = models.DecimalField(
        verbose_name=_("Costs"),
        decimal_places=2,
        max_digits=8,
        default=0,
        help_text=_("Cost of the product in EUR"),
    )
    created_on = models.DateTimeField(
        verbose_name=_("Created on"),
        auto_now_add=True,
        help_text=_(
            "This is the date the product was created. This field is automatically set."
        ),
    )
    updated_on = models.DateTimeField(
        verbose_name=_("Updated on"),
        auto_now=True,
        help_text=_(
            "This is the date when the product was last changed. This field is automatically set."
        ),
    )
    organizations = models.ManyToManyField(
        "pdc.Organization",
        verbose_name=_("Organizations"),
        blank=True,
        related_name="products",
        help_text=_("Organizations which provides this product"),
    )
    keywords = ArrayField(
        models.CharField(max_length=100, blank=True),
        verbose_name=_("Keywords"),
        default=list,
        blank=True,
        help_text=_("List of keywords for search"),
    )
    uniforme_productnaam = models.CharField(
        verbose_name=_("Uniforme productnaam"),
        max_length=250,
        blank=True,
        help_text=_("Attribute to sync data from PDC's (like SDG)"),
    )
    contacts = models.ManyToManyField(
        "pdc.ProductContact",
        verbose_name=_("Product contacts"),
        related_name="products",
        blank=True,
        help_text=_("The contacts responsible for the product"),
    )
    locations = models.ManyToManyField(
        "pdc.ProductLocation",
        verbose_name=_("Product locations"),
        related_name="products",
        blank=True,
        help_text=_("Locations where the product is available at."),
    )
    conditions = models.ManyToManyField(
        "pdc.ProductCondition",
        related_name="products",
        verbose_name=_("Conditions"),
        blank=True,
        help_text=_("Conditions applicable for the product"),
    )

    objects = ProductQueryset.as_manager()

    class Meta:
        verbose_name = _("Product")
        verbose_name_plural = _("Products")

    def __str__(self):
        return self.name

    @property
    def form_link(self):
        return reverse("products:product_form", kwargs={"slug": self.slug})

    def get_absolute_url(self, category=None):
        if not category:
            return reverse("products:product_detail", kwargs={"slug": self.slug})

        category_slug = category.get_build_slug()
        return reverse(
            "products:category_product_detail",
            kwargs={"slug": self.slug, "category_slug": category_slug},
        )

    def has_cta_tag(self):
        return "\[CTABUTTON\]" in self.content

class ProductFile(models.Model):
    product = models.ForeignKey(
        "pdc.Product",
        verbose_name=_("Product"),
        related_name="files",
        on_delete=models.CASCADE,
        help_text=_("Related product"),
    )
    file = FilerFileField(
        verbose_name=_("File"),
        on_delete=models.CASCADE,
        related_name="product_files",
    )

    class Meta:
        verbose_name = _("Product file")
        verbose_name_plural = _("Product files")

    def __str__(self):
        if self.file:
            return self.file.name
        return ""

class ProductContact(models.Model):
    organization = models.ForeignKey(
        "pdc.Organization",
        verbose_name=_("Organization"),
        null=True,
        blank=True,
        related_name="product_contacts",
        on_delete=models.SET_NULL,
        help_text=_("The organization of the product contact"),
    )
    first_name = models.CharField(
        verbose_name=_("First name"),
        max_length=255,
        help_text=_("First name of the product contact"),
    )
    last_name = models.CharField(
        verbose_name=_("Last name"),
        max_length=255,
        help_text=_("Last name of the product contact"),
    )
    email = models.EmailField(
        verbose_name=_("Email address"),
        blank=True,
        help_text=_("The email address of the product contact"),
    )
    phonenumber = models.CharField(
        verbose_name=_("Phonenumber"),
        blank=True,
        max_length=15,
        validators=[DutchPhoneNumberValidator()],
        help_text=_("The phone number of the product contact"),
    )
    role = models.CharField(
        verbose_name=_("Rol"),
        blank=True,
        max_length=100,
        help_text=_("The role/function of the product contact"),
    )

    class Meta:
        verbose_name = _("Product contact")
        verbose_name_plural = _("Product contacts")

    def __str__(self):
        if self.organization:
            return f"{self.organization.name}: {self.first_name} {self.last_name}"
        return f"{self.first_name} {self.last_name}"

    def get_mailto_link(self):
        email = self.get_email()
        if not email:
            return
        return f"mailto://{email}"

    def get_email(self):
        if self.email:
            return self.email
        return self.organization.email

    def get_phone_number(self):
        if self.phonenumber:
            return self.phonenumber
        return self.organization.phonenumber

class ProductLink(models.Model):
    product = models.ForeignKey(
        "pdc.Product",
        verbose_name=_("Product"),
        related_name="links",
        on_delete=models.CASCADE,
        help_text=_("Related product"),
    )
    name = models.CharField(
        verbose_name=_("Name"), max_length=100, help_text=_("Name for the link")
    )
    url = models.URLField(verbose_name=_("Url"), help_text=_("Url of the link"))

    class Meta:
        verbose_name = _("Product link")
        verbose_name_plural = _("Product links")

    def __str__(self):
        return f"{self.product}: {self.name}"

class ProductLocation(GeoModel):
    uuid = models.UUIDField(verbose_name=_("UUID"), default=uuid4, unique=True)
    name = models.CharField(
        verbose_name=_("Name"),
        max_length=100,
        help_text=_("Location name"),
        blank=True,
        null=True,
    )
    email = models.EmailField(
        verbose_name=_("Email address"),
        blank=True,
        help_text=_("The email address of the current location"),
    )
    phonenumber = models.CharField(
        verbose_name=_("Phonenumber"),
        blank=True,
        default="",
        max_length=15,
        validators=[DutchPhoneNumberValidator()],
        help_text=_("The phonenumber of the current location"),
    )

    class Meta:
        verbose_name = _("Product location")
        verbose_name_plural = _("Product locations")

    def __str__(self) -> str:
        return f"{self.name}: {self.address_str}" if self.name else self.address_str

    def get_geojson_feature(self, stringify: bool = True) -> Union[str, dict]:
        feature = super().get_geojson_feature(False)

        if feature.get("properties"):
            feature["properties"]["location_url"] = self.get_absolute_url()

        if stringify:
            return json.dumps(feature)
        return feature

    def get_absolute_url(self) -> str:
        return reverse("products:location_detail", kwargs={"uuid": self.uuid})

class ProductCondition(OrderedModel):
    name = models.CharField(
        verbose_name=_("Name"),
        max_length=100,
        help_text=_("Short name of the condition"),
    )
    question = models.TextField(
        verbose_name=_("Question"),
        help_text=_("Question used in the question-answer game"),
    )
    positive_text = models.TextField(
        verbose_name=_("Positive text"),
        help_text=_("Description how to meet the condition"),
    )
    negative_text = models.TextField(
        verbose_name=_("Negative text"),
        help_text=_("Description how not to meet the condition"),
    )
    rule = models.TextField(
        verbose_name=_("Rule"),
        blank=True,
        default="",
        help_text=_("Rule for the automated check"),
    )

    class Meta:
        verbose_name = _("Condition")
        verbose_name_plural = _("Conditions")
        ordering = ("order",)

    def __str__(self):
        return self.name


# klondike_solitaire.py

import os
import cherrypy

class KlondikeSolitaireServer(object):
    def __init__(self, root_dir):
        self.root_dir = root_dir

    @cherrypy.expose
    def default(self, **kwargs):
        return cherrypy.lib.static.serve_file(self.root_dir + '/klondike_solitaire.html', content_type='text/html')

if __name__ == '__main__':
    root_dir = os.path.dirname(os.path.abspath(__file__))
    port = int(os.environ.get('PORT', 5200))
    server = KlondikeSolitaireServer(root_dir)
    config = {
        'global': {
            'server.socket_host': '0.0.0.0',
            'server.socket_port': port,
        },
        '/': {
            'tools.staticdir.root': root_dir,
            'tools.staticdir.on': True,
            'tools.staticdir.dir': '',
        }
    }
    cherrypy.quickstart(server, '/', config=config)