#! python3
# -*- coding: utf-8 -*-
# Check status of links within catalog records
import requests
import csv
import time
import sqlite3
import smtplib
from os.path import basename
from email.utils import formatdate
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email import encoders

#===============================================================================
# defs
#===============================================================================

def check_url(u):
# Check url u. Return HTTP response.
    try:
        r = requests.head(u, allow_redirects=True, timeout=(15, 15))
        code = r.status_code
    except (requests.HTTPError, requests.URLRequired, requests.exceptions.RequestException, requests.TooManyRedirects, requests.ConnectionError, requests.ConnectTimeout, requests.ReadTimeout, requests.exceptions.Timeout):
        code = 'Error'
    if not str(code).startswith('2'):
        try:
            r = requests.get(u, allow_redirects=True, timeout=(15, 15))
            code = r.status_code
        except (requests.ConnectTimeout, requests.ReadTimeout, requests.exceptions.Timeout):
            code = 'Timeout Error'
        except (requests.TooManyRedirects, requests.ConnectionError):
            code = 'Connection Error'
        except (requests.HTTPError, requests.URLRequired, requests.exceptions.RequestException):
            code = 'Other Error'
    return code

def send_email(attach_file, body, recipients, subject):
# Send email with file attachment.
    sender = '''ENTER_EMAIL_ADDRESSES'''
    msg = MIMEMultipart()
    msg['from'] = sender
    msg['To'] = ','.join(recipients)
    msg['date'] = formatdate(localtime=True)
    msg['subject'] = subject
    msg.attach(MIMEText(body, 'plain'))

    try:
        file = attach_file
        part = MIMEBase('application', "octet-stream")
        with open(file, 'rb') as fh:
            part.set_payload(fh.read())
            encoders.encode_base64(part)
            part.add_header('Content-Disposition', 'attachment', \
                                          filename=basename(file))
            msg.attach(part)
    except (ValueError, TypeError):
        pass
    smtp = smtplib.SMTP('''ENTER_HOST''')
    smtp.sendmail(sender, recipients, msg.as_string())
    smtp.close()

#===============================================================================
# variables
#===============================================================================

# list of reporting departments
#For example, unit_list = ['ART', 'DIV']
unit_list = ['''ENTER_LIST_OF_DEPTS''']

# recipients' email addresses of each department
# For example, recipients = dict(ART=['abc@xxx.edu'], DIV=['abc@xxx.edu','def@xxx.edu'])
recipients = dict('''ENTER_EMAIL_ADDRESSES''')

#===============================================================================
# main
#===============================================================================

# Create and connect database
# For example, conn = sqlite3.connect('xxx.sqlite')
conn = sqlite3.connect('''ENTER_DATABASE''')

cur = conn.cursor()

# Create table
cur.executescript('''
CREATE TABLE IF NOT EXISTS Print (
    ID  INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
    BibID  INTEGER,
    Host    TEXT,
    URL     TEXT,
	Status		TEXT,
    MFHD_Location   TEXT,
	Check_Date  DATE,
    Unit    TEXT
);
''')

# Record start time
start_time = time.time()

# Select input csv file and generate output csv file
input_name = input('Enter input name: ')
output_name = input_name[:-4] + "_output.csv"

# Select unit name to email
unit = ''
while unit not in unit_list:
    unit = input('Select unit name: ')

# Email subject
subject = 'Link Errors (' + unit + ')'

# Error counts
not_found = connection_error = timeout_error = other_error = 0

# Open output csv file
with open(output_name, 'w', newline='', encoding='utf-8-sig') as csvoutput:
    csv_out = csv.writer(csvoutput, delimiter = ',', quotechar = '"', quoting = csv.QUOTE_ALL)
    csv_out.writerow(["ID","HOST","URL","STATUS","MFHD_LOC"])

# Open input csv file
    rows = csv.reader(open(input_name, 'r', encoding='utf-8-sig'))
    num_lines = len(open(input_name).read().splitlines())
    count = 0

# List of unique urls
    url_list = dict()

# Check each url
# If same link has not been checked yet, check its http status and update results to url_list
# If same link has already been checked, get result from url_list
    for row in rows:
        bib = str(row[0])
        host = row[1]
        url = row[2]
        mfhd_loc = row[3]
        if url not in url_list:
            code = check_url(url)
            url_list.update({url:code})
        else:
            code = url_list.get(url)
        count += 1

# Write results to console
        print (str(count) +  " of " + str(num_lines) + " | " + bib, code)

# Write error results to output csv file
        if str(code).startswith('400') or str(code).startswith('404'):
            csv_out.writerow([bib, host, url, code, mfhd_loc])
            not_found += 1
        if str(code).startswith('Connection'):
            csv_out.writerow([bib, host, url, code, mfhd_loc])
            connection_error += 1
        if str(code).startswith('Timeout'):
            csv_out.writerow([bib, host, url, code, mfhd_loc])
            timeout_error += 1
        if str(code).startswith('Other'):
            csv_out.writerow([bib, host, url, code, mfhd_loc])
            other_error += 1

# Write results to database
        cur.execute('SELECT ID FROM Print WHERE BibID = ? AND URL = ?', (bib, url, ))
        fetch = cur.fetchone()
        if fetch is None:
            cur.execute('INSERT INTO Print (BibID, Host, URL, Status, MFHD_Location, Check_Date, Unit) VALUES ( ?, ?, ?, ?, ?, CURRENT_DATE, ? )', (bib, host, url, code, mfhd_loc, unit) )
        else:
            cur.execute('UPDATE Print SET Status = ?, Check_Date = CURRENT_DATE, Unit = ? WHERE ID = ?', (code, unit, fetch[0],))
        conn.commit()
csvoutput.close()
cur.close()

# If there is any error, email results
text_body = 'Among ' + str(num_lines) + ' links' + '\n\n400/404 Error = ' + str(not_found) + '\nConnection Error = ' + str(connection_error) + '\nTimeout Error = ' + str(timeout_error) + '\nOther Error = ' + str(other_error)
if not_found + connection_error + timeout_error + other_error > 0:
    send_email(output_name, text_body, recipients[unit], subject)
print (text_body)
print("\n%f seconds" % (time.time() - start_time))


from __future__ import annotations
import ast
import marshal
from types import CodeType
from typing import Dict, List, Tuple, Iterator, Any
from importlib.machinery import ModuleSpec
from . import Code, CodeBuilder

__all__ = ("ByteCode", "ByteCodeBuilder")

GENERATOR_NOOP: List[ast.AST] = [  # to force a generator in case there are no text sections
    ast.Return(
        value=None,     # simple 'return' to end generator before yield
        lineno=1,
        col_offset=0
    ),
    ast.Expr(           # to convince python to make a generator
        value=ast.Yield(
            value=ast.Constant(
                value="",
                lineno=1,
                col_offset=0
            ),
            lineno=1,
            col_offset=0
        ),
        lineno=1,
        col_offset=0
    )
]

class ByteCode(Code):
    """Code implementation using a compiled ast"""
    __slots__ = ("code", "spec")

    code: CodeType

    spec: ModuleSpec

    def __init__(self, code: CodeType, spec: ModuleSpec) -> None:
        """construct a instance with a compiled ast and a spec"""
        self.code = code
        self.spec = spec

    def __getstate__(self) -> Tuple[bytes, ModuleSpec]:     # pickle cant process code objects
        """support pickling"""
        return marshal.dumps(self.code), self.spec

    def __setstate__(self, state: Tuple[bytes, ModuleSpec]) -> None:
        """support pickling"""
        self.code = marshal.loads(state[0])
        self.spec = state[1]

    def __eq__(self, other: object) -> bool:
        if isinstance(other, ByteCode):
            return self.code == other.code and self.spec == other.spec
        return NotImplemented

    def execute(self, variables: Dict[str, Any]) -> Iterator[str]:
        """execute the code, yielding the text sections between code sections"""
        variables["__spec__"] = self.spec
        variables["__name__"] = self.spec.name
        variables["__loader__"] = self.spec.loader  # lets hope they remove these constants
        variables["__file__"] = self.spec.origin
        variables["__path__"] = self.spec.submodule_search_locations
        variables["__cached__"] = self.spec.cached
        variables["__package__"] = self.spec.parent
        exec(self.code, variables)                  # create generator
        return variables["<module>"]()              # the compiled generator

class ByteCodeBuilder(CodeBuilder):
    """code builder for the bytecode implementation"""
    __slots__ = ("nodes", "has_text", "optimization_level")

    nodes: List[ast.AST]

    has_text: bool

    optimization_level: int

    def __init__(self, optimization_level: int = -1) -> None:
        """construct a instance with the optimization level to compile code sections"""
        self.nodes = []
        self.has_text = False
        self.optimization_level = optimization_level

    def __eq__(self, other: object) -> bool:
        if isinstance(other, ByteCodeBuilder):
            return self.nodes == other.nodes \
                and self.has_text == other.has_text \
                and self.optimization_level == other.optimization_level
        return NotImplemented

    def add_code(self, code: str, offset: int) -> None:
        """add a code section with a line offset"""
        try:
            nodes = [
                ast.increment_lineno(node, offset) for node in ast.parse(code, "<string>", mode="exec").body
            ]
        except SyntaxError as e:    # set correct lineno and reraise
            if e.lineno is not None:
                e.lineno += offset
            raise
        self.nodes.extend(nodes)

    def add_text(self, text: str, offset: int) -> None:   # pylint: disable=W0613
        """add a text section with a line offset"""
        if text:    # ignore empty sections
            self.nodes.append(
                ast.Expr(
                    value=ast.Yield(
                        value=ast.Constant(
                            value=text,
                            lineno=offset + 1,
                            col_offset=0
                        ),
                        lineno=offset + 1,
                        col_offset=0
                    ),
                    lineno=offset + 1,
                    col_offset=0
                )
            )
            self.has_text = True

    def code(self, spec: ModuleSpec) -> ByteCode:
        """build a code object from the received sections"""
        if self.has_text:   # result will be a generator
            nodes = self.nodes
        else:               # result will be a function, add noop yield
            nodes = self.nodes + GENERATOR_NOOP
        code = compile(
            ast.Module(
                body=[
                    ast.FunctionDef(
                        name="<module>",
                        args=ast.arguments(
                            posonlyargs=[],
                            args=[],
                            kwonlyargs=[],
                            kw_defaults=[],
                            defaults=[]
                        ),
                        body=nodes,
                        decorator_list=[],
                        lineno=1,
                        col_offset=0
                    )
                ],
                type_ignores=[]
            ),
            "<string>" if spec.origin is None else spec.origin,
            "exec",
            optimize=self.optimization_level,
            dont_inherit=True
        )
        return ByteCode(code, spec)

    def copy(self) -> ByteCodeBuilder:
        """copy the builder with his current state"""
        builder = self.__class__.__new__(self.__class__)
        builder.nodes = self.nodes.copy()
        builder.has_text = self.has_text
        builder.optimization_level = self.optimization_level
        return builder


# -*- coding: utf-8 -*-

#%%
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits import axes_grid1
from matplotlib.colors import ListedColormap
import matplotlib.patches as mpatches
import os
import sys
#%%
class Bandas:
    def __init__(self):
        '''
        Inicializo variables de bandas

        '''
        self.banda_1 = np.load('LC08_L1TP_225084_20180213_20180222_01_T1_sr_band1_clip.npy')
        self.banda_2 = np.load('LC08_L1TP_225084_20180213_20180222_01_T1_sr_band2_clip.npy')
        self.banda_3 = np.load('LC08_L1TP_225084_20180213_20180222_01_T1_sr_band3_clip.npy')
        self.banda_4 = np.load('LC08_L1TP_225084_20180213_20180222_01_T1_sr_band4_clip.npy')
        self.banda_5 = np.load('LC08_L1TP_225084_20180213_20180222_01_T1_sr_band5_clip.npy')
        self.banda_6 = np.load('LC08_L1TP_225084_20180213_20180222_01_T1_sr_band6_clip.npy')
        self.banda_7 = np.load('LC08_L1TP_225084_20180213_20180222_01_T1_sr_band7_clip.npy')

    def NDVI(self):
        '''
        Estimación del NDVI
        '''
        bNIR_bR = self.banda_5 + self.banda_4
        bNIR_bR[bNIR_bR == 0] = 0.001
        ndvi = (self.banda_5 - self.banda_4) / (bNIR_bR)
        return ndvi

#%%
def add_colorbar(im,aspect=20,pad_fraction=0.5):
    divider=axes_grid1.make_axes_locatable(im.axes)
    width= axes_grid1.axes_size.AxesY(im.axes, aspect=1./aspect)
    pad=axes_grid1.axes_size.Fraction(pad_fraction, width)
    current_ax=plt.gca()
    cax=divider.append_axes("right", size=width, pad=pad)
    plt.sca(current_ax)
    return im.axes.figure.colorbar(im, cax=cax)

def vmini(x):
    return np.percentile(x.flatten(), 10)

def vmaxi(x):
    return np.percentile(x.flatten(), 90)

def ploteo(x):
    '''
    Ploteo básico de bandas.

    '''
    plt.figure(figsize=(7,7), dpi=150)
    im = plt.imshow(x, vmin = vmini(x), vmax = vmaxi(x))
    add_colorbar(im)
    plt.xticks([])
    plt.yticks([])
    plt.show()

def clases_ndvi(x):
    '''
    Asigna clases del 0 al 4, según los valores
    que encuentre en la matriz ingresada.
    (pensada para el caso con x = ndvi)

    '''
    clases = np.zeros(x.shape)
    for i in range(x.shape[0]):
        for j in range(x.shape[1]):
            if x[i][j] < 0:
                clases[i][j] = 0
            elif 0 < x[i][j] < 0.1:
                clases[i][j] = 1
            elif 0.1 <= x[i][j] < 0.25:
                clases[i][j] = 2
            elif 0.25 <= x[i][j] < 0.4:
                clases[i][j] = 3
            elif 0.4 <= x[i][j] < 3:
                clases[i][j] = 4
            else:
                pass
    return clases

def plot_ndvi(x):
    '''
    Ploteo especial para visualizar NDVI y
    sus clases.

    '''
    plt.figure(figsize=(7,7), dpi=150)
    colormap = ListedColormap(['black','y', 'yellowgreen', 'g', 'darkgreen'])
    im = plt.imshow(x, cmap=colormap)
    plt.xticks([])
    plt.yticks([])
    p0 = mpatches.Patch(color='black', label='No vegetada')
    p1 = mpatches.Patch(color='y', label='Área desnuda')
    p2 = mpatches.Patch(color='yellowgreen', label='Vegetación baja')
    p3 = mpatches.Patch(color='g', label='Vegetación moderada')
    p4 = mpatches.Patch(color='darkgreen', label='Vegetación densa')
    plt.legend(handles=[p0, p1, p2, p3, p4], fontsize='xx-small',loc='upper left', bbox_to_anchor=(1, 1))
    plt.title('Landsat 8 - Clases de NDVI')
    plt.show()

def crear_img_png(carpeta, banda):
    '''
    En la "carpeta" ingresada, formato str, se grafica el número
    de banda ingresado en la variable "banda". (banda debe ser una
    cadena entre el 1 y el 7 inclusive.

    '''
    current = os.getcwd()
    directorio = os.path.join(current,carpeta)
    var_name = eval('Bandas().banda_{}'.format(str(banda)))
    plt.figure(figsize=(7,7), dpi=150)
    im = plt.imshow(var_name, vmin = vmini(var_name), vmax = vmaxi(var_name))
    add_colorbar(im)
    plt.xticks([])
    plt.yticks([])
    plt.title('Banda={}'.format(str(banda)))
    plt.savefig(directorio+'\\'+'banda_'+str(banda)+'.png')
    plt.show()

def crear_hist_png(carpeta, banda, bins):
    '''
    En la "carpeta" ingresada, formato str, se grafica el histograma
    asociado al nro de banda ingresado en la variable "banda"
    (banda debe ser una cadena entre el 1 y el 7 inclusive.
    bins son la cantidad de intervalos del histograma, debe ser int.
    '''
    current = os.getcwd()
    directorio = os.path.join(current,carpeta)
    var_name = eval('Bandas().banda_{}'.format(str(banda)))
    plt.figure(figsize=(7,7), dpi=150)
    plt.hist(var_name.flatten(),bins = bins)
    plt.xlim((vmini(var_name)-0.5,vmaxi(var_name)+0.5))
    plt.title('Histograma Banda={}'.format(str(banda)))
    plt.savefig(directorio+'\\'+'hist_banda_'+str(banda)+'.png')
    plt.show()

#%% Ver una banda
# bandauno = Bandas().banda_1
# ploteo(bandauno)
#%%Análisis NDVI
# ndvi = Bandas().NDVI()
# ndvi_class = clases_ndvi(ndvi)
# plot_ndvi(ndvi_class)
#%%Un poquito de testeo
# crear_img_png('testing', '1')
# crear_hist_png('testing', '2', 100)
#%%
for i in range(1,8):
    crear_img_png('testing', i)
    crear_hist_png('testing', i, 100)
#%%Banda 5: La que veo con mayor bimodalidad.

binaria = Bandas().banda_5
binaria[binaria<=1] = -1
binaria[binaria>1] = 1
plt.figure(figsize=(7,7), dpi=150)
colormap = ListedColormap(['blue','red'])
im = plt.imshow(binaria, cmap=colormap)
plt.xticks([])
plt.yticks([])
plt.title('Binaria banda 5')
plt.show()
# viendo esto intuyo fuertemente que los dos tipos de pixeles responden a sectores
# a tierra (rojo) o agua (azul)

#ESTO QUEDA PARA MI, NO VER.
# banda_1 = np.load('LC08_L1TP_225084_20180213_20180222_01_T1_sr_band1_clip.npy')
# banda_2 = np.load('LC08_L1TP_225084_20180213_20180222_01_T1_sr_band2_clip.npy')
# banda_3 = np.load('LC08_L1TP_225084_20180213_20180222_01_T1_sr_band3_clip.npy')
# banda_4 = np.load('LC08_L1TP_225084_20180213_20180222_01_T1_sr_band4_clip.npy')
# banda_5 = np.load('LC08_L1TP_225084_20180213_20180222_01_T1_sr_band5_clip.npy')
# banda_6 = np.load('LC08_L1TP_225084_20180213_20180222_01_T1_sr_band6_clip.npy')
# banda_7 = np.load('LC08_L1TP_225084_20180213_20180222_01_T1_sr_band7_clip.npy')
# for i in range(1,8):
#     var_name = 'banda_{}'.format(i)
#     plt.imshow(eval(var_name))
#     plt.show()


import os
import json
import random
import uvloop
import logging
import asyncio
import aioredis
import aiofiles
import aiomysql
import logging.config
import face_recognition
from datetime import datetime

from src.utils.face_util import FaceUtil
from concurrent.futures import ProcessPoolExecutor

CURRENT_WORK_DIR = os.path.dirname(os.path.abspath(__file__))
logging.config.fileConfig(os.path.join(CURRENT_WORK_DIR, "conf", "logging.conf"))

logger = logging.getLogger("worker")
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())

def get_model(fpath) -> (str, str):
    """获取人脸encoding和location信息"""
    img = face_recognition.load_image_file(fpath)
    locations = face_recognition.face_locations(img)
    encodings = face_recognition.face_encodings(img, locations)
    if not (locations and encodings):
        return None
    return json.dumps(locations), json.dumps([e.tolist() for e in encodings])

async def save_model(fpath: str, data: str):
    async with aiofiles.open(fpath, "w") as f:
        await f.write(data)

async def worker(loop, executor, ch):
    """启用redis订阅模式，获取消息并将任务提交给进程池处理
       拿到face encoding和location之后分别保存在文件里
    Args:
        loop: 事件循环
        executor: 进程池实例
        ch (int): 信道编号
    """
    logger.info(f"worker: {ch} started.")
    conn = await aioredis.create_redis("redis://localhost", loop=loop)
    channel = (await conn.subscribe(f"channel:{ch}"))[0]
    while await channel.wait_message():
        fpath = await channel.get(encoding="utf-8")
        logger.info(f"start get_model(fpath): {fpath}")
        data = await loop.run_in_executor(executor, get_model, fpath)
        logger.info(f"end get_model(fpath): {fpath}")
        if not data:
            logger.error(f"picture {fpath} recognize error.")
            continue

        code = fpath.rsplit("/", 1)[-1].rsplit(".", 1)[0]
        encoding_path = f"{FaceUtil.model_path}/{code}-encode.model"
        location_path = f"{FaceUtil.model_path}/{code}-location.model"
        await asyncio.gather(
            save_model(location_path, data[0]),
            save_model(encoding_path, data[1])
        )
        logger.info(f"worker{ch}: the model of picture {code} have saved.")

async def main(loop):
    with ProcessPoolExecutor(max_workers=8) as executor:
        await asyncio.gather(*[worker(loop, executor, ch) for ch in range(8)])

if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main(loop))


# M1. Actividad
# Erika García A01745158
# David Damián A01752785

import mesa

def compute_clean_cells(model):
    cells = model.height * model.width
    dirty_cells = 0
    for agent in model.schedule.agents:
        if isinstance(agent, DirtynessAgent):
            if not agent.is_clean():
                dirty_cells += 1
    clean_ratio = (cells - dirty_cells) / cells * 100
    return clean_ratio

def compute_agent_moves(model):
    movements = 0
    for agent in model.schedule.agents:
        if isinstance(agent, AspiradoraAgent):
            movements += agent.moves
    return movements

class DirtynessAgent(mesa.Agent):
    def __init__(self, unique_id, model):
        super().__init__(unique_id, model)
        self.dirty = True

    def step(self):
        pass

    def clean(self):
        self.dirty = False

    def is_clean(self):
        return not self.dirty

class AspiradoraAgent(mesa.Agent):
    def __init__(self, unique_id, model):
        super().__init__(unique_id, model)
        self.moves = 0

    def step(self):
        cellmates = self.model.grid.get_cell_list_contents([self.pos])
        turn = False
        for cellmate in cellmates:
            if isinstance(cellmate, DirtynessAgent):
                if not cellmate.is_clean():
                    cellmate.clean()
                    turn = True
                else:
                    self.move()
                    turn = True
        if not turn:
            self.move()

    def move(self):
        possible_steps = self.model.grid.get_neighborhood(
            self.pos,
            moore=True,
            include_center=False)
        new_position = self.random.choice(possible_steps)
        self.model.grid.move_agent(self, new_position)
        self.moves += 1

class AspiradoraModel(mesa.Model):
    def __init__(self, width, height, num_agents,
                 dirty_percentage):
        self.num_agents = num_agents
        self.width = width
        self.height = height
        self.dirty_percentage = dirty_percentage
        self.grid = mesa.space.MultiGrid(width, height, False)
        self.schedule = mesa.time.RandomActivation(self)
        self.running = True
        num_dirty_cells = int(self.dirty_percentage * width * height)
        used_coordinates = set()
        for i in range(num_dirty_cells):
            agent = DirtynessAgent(i, self)
            self.schedule.add(agent)
            x = self.random.randrange(self.grid.width)
            y = self.random.randrange(self.grid.height)
            while (x, y) in used_coordinates:
                x = self.random.randrange(self.grid.width)
                y = self.random.randrange(self.grid.height)
            self.grid.place_agent(agent, (x, y))
            used_coordinates.add((x, y))

        for i in range(self.num_agents):
            agent = AspiradoraAgent(i + num_dirty_cells, self)
            self.schedule.add(agent)
            self.grid.place_agent(agent, (1, 1))

        self.datacollector = mesa.DataCollector(
            model_reporters={"CleanCells": compute_clean_cells,
                             "TotalMovements": compute_agent_moves}
        )

    def step(self):
        self.datacollector.collect(self)
        self.schedule.step()

if __name__ == '__main__':
    width_param = int(input("Ingrese ancho: "))
    height_param = int(input("Ingrese alto: "))
    num_agents_param = int(input("Ingrese el numero de agentes: "))
    dirty_percentage_param = \
        float(input("Ingrese el porcentaje de casillas sucias (0 a 1): "))
    max_steps_param = int(input("Ingrese el numero de pasos maximos: "))
    iterations_param = int(input("Ingrese el numero de iteraciones: "))
    params = {"width": width_param,
              "height": height_param,
              "num_agents": num_agents_param,
              "dirty_percentage": dirty_percentage_param}

    results = mesa.batch_run(
        AspiradoraModel,
        parameters=params,
        iterations=iterations_param,
        max_steps=max_steps_param,
        number_processes=1,
        data_collection_period=1,
        display_progress=True,
    )

    import pandas as pd
    results_df = pd.DataFrame(results)
    clean_cells = []
    total_movements = []
    steps = []
    for i in range(iterations_param):
        # Obtain the row containing the general outcome of the
        # i-th run
        run_info = results_df.query(f'RunId == {i}') \
            .nlargest(1, 'TotalMovements')
        print(run_info)
        clean_cells.append(run_info.iloc[0]['CleanCells'])
        total_movements.append(run_info.iloc[0]['TotalMovements'])
    for i in range(iterations_param):
        step_info = results_df.query(f'RunId == {i} & CleanCells == 100')
        if step_info.size != 0:
            steps.append(step_info['Step'].iloc[0])
            print(step_info.iloc[0])
        else:
            steps.append(max_steps_param)

    import matplotlib.pyplot as plt
    plt.hist(clean_cells, bins=20)
    plt.title('Percentage of clean cells')
    plt.xlabel("Percentage")
    plt.ylabel("Frequency")
    plt.show()
    plt.hist(total_movements, bins=20)
    plt.title('Total movements')
    plt.xlabel("Number of steps")
    plt.ylabel("Frequency")
    plt.show()
    plt.hist(steps, bins=20)
    plt.title('Maximum time')
    plt.xlabel("Time (steps)")
    plt.ylabel("Frequency")
    plt.show()


#!/usr/bin/python

#$Id: scenariobuilder.py,v 1.56 2010-07-19 13:50:21 jorn Exp $

from xmlstore.qt_compat import QtGui, QtCore, QtWidgets, qt4_backend, qt4_backend_version

# Import modules from standard Python library
import sys,xml, os.path

from .core import scenario, result
from . import commonqt
import xmlstore.util, xmlstore.gui_qt4

class ScenarioWidget(QtWidgets.QWidget):

    onCompleteStateChanged = QtCore.Signal()

    def __init__(self,parent=None,mrupaths=[]):
        QtWidgets.QWidget.__init__(self,parent)

        self.bngroup      = QtWidgets.QButtonGroup()
        self.radioNew     = QtWidgets.QRadioButton('Create a new scenario.',self)
        self.radioOpen    = QtWidgets.QRadioButton('Open an existing scenario.',self)
        self.radioImport1 = QtWidgets.QRadioButton('Import a namelist-based scenario from an existing directory.',self)
        self.radioImport2 = QtWidgets.QRadioButton('Import a namelist-based scenario from an archive.',self)

        #self.labTemplate = QtWidgets.QLabel('Template:',self)
        #default2path = scenario.Scenario.getDefaultValues()
        #self.comboTemplates = QtWidgets.QComboBox(parent)
        #for (name,path) in default2path.items():
        #    self.comboTemplates.addItem(name,name)
        #self.templatelayout = QtWidgets.QHBoxLayout()
        #self.templatelayout.addWidget(self.labTemplate)
        #self.templatelayout.addWidget(self.comboTemplates,1)
        #self.templatelayout.addStretch()

        self.pathOpen    = commonqt.PathEditor(self,header='File to open: ',mrupaths=mrupaths)
        self.pathImport1 = commonqt.PathEditor(self,header='Directory to import: ',getdirectory=True)
        self.pathImport2 = commonqt.PathEditor(self,header='Archive to import: ')

        self.pathOpen.filter    = 'GOTM scenario files (*.gotmscenario);;GOTM result files (*.gotmresult);;dataless GOTM scenario files (*.xml);;All files (*.*)'
        self.pathImport2.filter = 'tar/gz files (*.tar.gz);;zip files (*.zip);;All files (*.*)'

        self.bngroup.addButton(self.radioNew,    0)
        self.bngroup.addButton(self.radioOpen,   1)
        self.bngroup.addButton(self.radioImport1,2)
        self.bngroup.addButton(self.radioImport2,3)

        layout = QtWidgets.QGridLayout()
        layout.addWidget(self.radioNew,       0,0,1,3)
        #layout.addLayout(self.templatelayout, 1,1)
        layout.addWidget(self.radioOpen,      2,0,1,3)
        layout.addWidget(self.pathOpen,       3,1,1,2)
        layout.addWidget(self.radioImport1,   4,0,1,3)
        layout.addWidget(self.pathImport1,    5,1,1,2)
        layout.addWidget(self.radioImport2,   6,0,1,3)
        layout.addWidget(self.pathImport2,    7,1,1,2)

        self.checkSkipToSimulation = QtWidgets.QCheckBox('Simulate the scenario without further configuration.',self)
        layout.addWidget(self.checkSkipToSimulation,8,0,1,3)

        layout.setColumnStretch(2,1)

        layout.setColumnMinimumWidth(0,commonqt.getRadioWidth())

        layout.setContentsMargins(0,0,0,0)

        self.setLayout(layout)

        self.bngroup.buttonClicked.connect(self.onSourceChange)
        self.pathOpen.onChanged.connect(self.completeStateChanged)
        self.pathImport1.onChanged.connect(self.completeStateChanged)
        self.pathImport2.onChanged.connect(self.completeStateChanged)

        self.radioNew.setChecked(True)
        self.onSourceChange()

    def setPath(self,path):
        self.radioOpen.click()
        self.pathOpen.setPath(path)

    def onSourceChange(self):
        self.setUpdatesEnabled(False)
        checkedid = self.bngroup.checkedId()
        #self.labTemplate.setVisible(checkedid==0)
        #self.comboTemplates.setVisible(checkedid==0)
        self.pathOpen.setVisible(checkedid==1)
        self.pathImport1.setVisible(checkedid==2)
        self.pathImport2.setVisible(checkedid==3)
        self.checkSkipToSimulation.setVisible(checkedid!=0)
        self.completeStateChanged()
        self.setUpdatesEnabled(True)

    def isComplete(self):
        checkedid = self.bngroup.checkedId()
        if   checkedid==0:
            return True
        elif checkedid==1:
            return self.pathOpen.hasPath()
        elif checkedid==2:
            return self.pathImport1.hasPath()
        elif checkedid==3:
            return self.pathImport2.hasPath()

    def getScenario(self,callback=None,completecallback=None):
        if not self.isComplete(): return None
        QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.WaitCursor))
        try:
            checkedid = self.bngroup.checkedId()
            if   checkedid==0:
                #index = self.comboTemplates.currentIndex()
                #defname = unicode(self.comboTemplates.itemData(index).toString())
                #defscenario = scenario.Scenario.getDefault(defname,scenario.guiscenarioversion)
                #xmldom = defscenario.toXmlDom()
                scen = scenario.Scenario.fromSchemaName(scenario.guiscenarioversion)
                #scen.setStore(xmldom)
                scen.fillMissingValues()
                scen.resetChanged()
            elif checkedid==1:
                path = self.pathOpen.path()
                if path.endswith('.gotmresult'):
                    try:
                        res = result.Result()
                        res.load(path)
                    except Exception as e:
                        raise Exception('An error occurred while loading the result: '+str(e))
                    scen = res.scenario.addref()  # Note: the scenario version will be guiscenarioversion, set by Result
                    res.release()
                elif path.endswith('.xml'):
                    try:
                        scen = scenario.Scenario.fromSchemaName(scenario.guiscenarioversion)
                        scen.load(path)
                    except Exception as e:
                        raise Exception('An error occurred while loading the scenario: '+str(e))
                else:
                    try:
                        scen = scenario.Scenario.fromSchemaName(scenario.guiscenarioversion)
                        scen.loadAll(path,callback=callback)
                    except Exception as e:
                        raise Exception('An error occurred while loading the scenario: '+str(e))
            elif checkedid==2:
                try:
                    scen = scenario.Scenario.fromNamelists(self.pathImport1.path(),strict = False,targetversion=scenario.guiscenarioversion,requireplatform='gotm')
                except Exception as e:
                    raise Exception('Cannot parse namelist files. Error: '+str(e))
            elif checkedid==3:
                try:
                    scen = scenario.Scenario.fromNamelists(self.pathImport2.path(),strict = False,targetversion=scenario.guiscenarioversion,requireplatform='gotm')
                except Exception as e:
                    raise Exception('Cannot parse namelist files. Error: '+str(e))

        finally:
            QtWidgets.QApplication.restoreOverrideCursor()

        if checkedid!=0:
            # We have loaded a scenario from file. Look for empty nodes and reset these to their defaults.
            #emptynodes = scen.root.getEmptyNodes()
            emptynodes = [n for n in scen.root.getEmptyNodes() if not n.isHidden()]
            for node in emptynodes:
                defval = node.getDefaultValue()
                assert defval is not None, 'No value set for "%s", but no default value is available.' % node
                if isinstance(defval,xmlstore.util.referencedobject): defval.release()
            emptycount = len(emptynodes)
            if emptycount>0:
                if completecallback is not None: completecallback()
                QtWidgets.QMessageBox.information(self,'Scenario is incomplete','In this scenario the following %i variables do not have a value:\n\n%s\n\nThese variables will be set to their default value.' % (emptycount,'\n'.join(['/'.join(n.location) for n in emptynodes])),QtWidgets.QMessageBox.Ok)
                scen.changed = True

        return scen

    def setSkipToSimulation(self,value):
        self.checkSkipToSimulation.setChecked(value)

    def skipToSimulation(self):
        return self.bngroup.checkedId()!=0 and self.checkSkipToSimulation.isChecked()

    def completeStateChanged(self):
        self.onCompleteStateChanged.emit()

class PageOpen(commonqt.WizardPage):

    def __init__(self,parent=None):
        commonqt.WizardPage.__init__(self, parent)

        self.label = QtWidgets.QLabel('How do you want to obtain a scenario?',self)
        self.scenariowidget = ScenarioWidget(self)
        self.scenariowidget.onCompleteStateChanged.connect(self.completeStateChanged)

        layout = QtWidgets.QVBoxLayout()
        layout.addWidget(self.label)
        layout.addWidget(self.scenariowidget)
        layout.addStretch()
        self.setLayout(layout)

    def isComplete(self):
        return self.scenariowidget.isComplete()

    def saveData(self,mustbevalid):
        dialog = commonqt.ProgressDialog(self,title='Please wait...',suppressstatus=True)
        try:
            newscen = self.scenariowidget.getScenario(callback=dialog.onProgressed,completecallback=dialog.close)
        except Exception as e:
            QtWidgets.QMessageBox.critical(self, 'Unable to obtain scenario', str(e), QtWidgets.QMessageBox.Ok, QtWidgets.QMessageBox.NoButton)
            dialog.close()
            return False
        self.owner.setProperty('result',None)
        self.owner.setProperty('scenario',newscen)
        dialog.close()
        return True

class ScenarioPage(commonqt.WizardPage):
    def __init__(self,parent=None):
        commonqt.WizardPage.__init__(self, parent)

        # Needed to make sure that GUI editors for xmlplot data types (gotmdatafile) are registered.
        import xmlplot.gui_qt4

        self.scenario = parent.getProperty('scenario')
        if self.scenario is None: raise Exception('No scenario available; this page should not have been available.')

        self.factory = xmlstore.gui_qt4.PropertyEditorFactory(self.scenario,live=True,allowhide=True,datasourcedir=parent.getProperty('datasourcedir'))

    def saveData(self,mustbevalid):

        editednodes = self.factory.getEditedNodes()
        if mustbevalid:
            progressdialog = commonqt.ProgressDialog(self,title='Validating settings...')
            try:
                errors = self.scenario.validate(editednodes,callback=progressdialog.onProgressed,repair=1)
            finally:
                progressdialog.close()
            if len(errors)>0:
                msg = '\n'.join(errors)
                if len(errors)>1: msg = 'The following problems remain:\n\n%s' % msg
                QtWidgets.QMessageBox.critical(self,'Scenario has not been configured correctly',msg,QtWidgets.QMessageBox.Ok,QtWidgets.QMessageBox.NoButton)
                return False
        else:
            self.scenario.clearValidationHistory(editednodes)

        return True

    def isComplete(self):
        return True

    def destroy(self,destroyWindow = True,destroySubWindows = True):
        self.factory.unlink()
        commonqt.WizardPage.destroy(self,destroyWindow,destroySubWindows)

class PageLocation(ScenarioPage):

    def __init__(self,parent=None):
        ScenarioPage.__init__(self, parent)

        layout = QtWidgets.QVBoxLayout()
        layout.setSpacing(25)

        self.title = self.createHeader('Simulated location and period','Here you specify the geographic location and time period that you want to simulate. Optionally you can provide a title for the simulation and location.')
        layout.addWidget(self.title)

        # Editor for title

        editGeneral = self.factory.createEditor('title',self,groupbox=True)
        groupboxGeneral = editGeneral.editor
        layoutTitle = QtWidgets.QHBoxLayout()
        editTitle = self.factory.createEditor('title',self)
        editTitle.addToBoxLayout(layoutTitle,addstretch=False,unit=False)
        groupboxGeneral.setLayout(layoutTitle)
        layout.addWidget(groupboxGeneral)

        # Editors for geographic location

        editStation = self.factory.createEditor('station',self,groupbox=True)
        groupboxLocation = editStation.editor

        layoutLocation = QtWidgets.QGridLayout()
        editName      = self.factory.createEditor('station/name',     self)
        editLongitude = self.factory.createEditor('station/longitude',self)
        editLatitude  = self.factory.createEditor('station/latitude', self)
        editDepth     = self.factory.createEditor('station/depth',    self)

        editName.addToGridLayout(layoutLocation,unit=False,colspan=3)
        editLongitude.addToGridLayout(layoutLocation)
        editLatitude.addToGridLayout(layoutLocation)
        editDepth.addToGridLayout(layoutLocation)
        layoutLocation.setColumnStretch(3,1)
        groupboxLocation.setLayout(layoutLocation)
        layout.addWidget(groupboxLocation)

        # Editors for simulated period

        editPeriod = self.factory.createEditor('time',self,groupbox=True)
        groupboxPeriod = editPeriod.editor

        layoutPeriod = QtWidgets.QGridLayout()
        editStart = self.factory.createEditor('time/start',self)
        editStop  = self.factory.createEditor('time/stop' ,self)
        editStart.addToGridLayout(layoutPeriod)
        editStop.addToGridLayout(layoutPeriod)
        layoutPeriod.setColumnStretch(3,1)
        groupboxPeriod.setLayout(layoutPeriod)
        layout.addWidget(groupboxPeriod)

        # Build final layout

        layout.addStretch()
        self.setLayout(layout)

    def saveData(self,mustbevalid):
        if self.scenario.hasChanged() and not mustbevalid:
            res = QtWidgets.QMessageBox.warning(self,'Leaving scenario editor','All changes to your scenario will be lost. Do you want to continue?',QtWidgets.QMessageBox.Yes|QtWidgets.QMessageBox.No,QtWidgets.QMessageBox.No)
            if res==QtWidgets.QMessageBox.No: return False

        return ScenarioPage.saveData(self,mustbevalid)

class PageDiscretization(ScenarioPage):

    def __init__(self,parent=None):
        ScenarioPage.__init__(self, parent)

        layout = QtWidgets.QVBoxLayout()
        layout.setSpacing(25)

        self.title = self.createHeader('Discretization of space and time','Here you specify how the water column and the time period are discretized. Generally this involves a balance between accuracy and simulation time.')
        layout.addWidget(self.title)

        editColumn = self.factory.createEditor('grid',self,groupbox=True)

        layoutGrid = QtWidgets.QGridLayout()

        # Create controls for grid layout.
        editGridMethod = self.factory.createEditor('grid/grid_method',self,selectwithradio=True)
        self.bngroup = editGridMethod.editor
        editLevelCount  = self.factory.createEditor('grid/nlev',self)
        editZoomSurface = self.factory.createEditor('grid/ddu', self)
        editZoomBottom  = self.factory.createEditor('grid/ddl', self)
        editGridFile = self.factory.createEditor('grid/grid_file',self)

        # Add controls for grid layout to the widget.
        layoutGrid.addWidget(self.bngroup.buttonFromValue(0),    0,0,1,4)
        editLevelCount.addToGridLayout (layoutGrid,1,1)
        editZoomSurface.addToGridLayout(layoutGrid,2,1)
        editZoomBottom.addToGridLayout (layoutGrid,3,1)
        layoutGrid.addWidget(self.bngroup.buttonFromValue(1),    4,0,1,4)
        layoutGrid.addWidget(self.bngroup.buttonFromValue(2),    5,0,1,4)
        editGridFile.addToGridLayout(layoutGrid,   6,1,label=False,unit=False)

        editColumn.editor.setLayout(layoutGrid)

        layoutGrid.setColumnStretch(3,1)
        layoutGrid.setColumnMinimumWidth(0,commonqt.getRadioWidth())

        editTime = self.factory.createEditor('timeintegration',self,groupbox=True)

        layoutTime = QtWidgets.QGridLayout()
        editTimeStep = self.factory.createEditor('timeintegration/dt',self)
        editOutputStep = self.factory.createEditor('output/dtsave',self)
        editTimeStep.addToGridLayout(layoutTime)
        editOutputStep.addToGridLayout(layoutTime)
        layoutTime.setColumnStretch(3,1)
        editTime.editor.setLayout(layoutTime)

        layout.addWidget(editColumn.editor)
        layout.addWidget(editTime.editor)
        layout.addStretch()
        self.setLayout(layout)

class PageSalinity(ScenarioPage):

    def __init__(self,parent=None):
        ScenarioPage.__init__(self, parent)

        layout = QtWidgets.QVBoxLayout()

        self.title = self.createHeader('Salinity observations','Here you can provide observations on vertical salinity profiles. These are used to initialize the column, and optionally to relax model results to.')
        layout.addWidget(self.title)
        layout.addSpacing(20)

        # Create main layout
        layoutSalinity = QtWidgets.QGridLayout()

        # Create button group for the salinity option, and an explanatory label.
        editSalinity = self.factory.createEditor('obs/sprofile',self,selectwithradio=True)

        # Create editors for all different salinity configurations.
        editSalinityConstant       = self.factory.createEditor('obs/sprofile/s_const',self)
        editSalinityUpperThickness = self.factory.createEditor('obs/sprofile/z_s1',self)
        editSalinityUpper          = self.factory.createEditor('obs/sprofile/s_1',self)
        editSalinityLowerThickness = self.factory.createEditor('obs/sprofile/z_s2',self)
        editSalinityLower          = self.factory.createEditor('obs/sprofile/s_2',self)
        editSalinitySurface        = self.factory.createEditor('obs/sprofile/s_surf',self)
        editSalinityNSquare        = self.factory.createEditor('obs/sprofile/s_obs_NN',self)
        editSalinityFile           = self.factory.createEditor('obs/sprofile/s_prof_file',self)

        # Create editors for relaxation.
        editSalinityRelaxation     = self.factory.createEditor('obs/sprofile/SRelax',self,boolwithcheckbox=True)
        editSalinityBulk           = self.factory.createEditor('obs/sprofile/SRelax/SRelaxTauM',self)

        # Add explanatory label
        layoutSalinity.addWidget(editSalinity.createLabel(),       0,0,1,2)

        # Add button for no salinity
        layoutSalinity.addWidget(editSalinity.editor.buttonFromValue(0),     1,0,1,2)

        # Add button for constant salinity.
        layoutSalinity.addWidget(editSalinity.editor.buttonFromValue(11),    2,0,1,2)

        # Add controls to edit constant salinity.
        layoutConstant = QtWidgets.QGridLayout()
        editSalinityConstant.addToGridLayout(layoutConstant)
        layoutConstant.setColumnStretch(3,1)
        layoutSalinity.addLayout(layoutConstant,3,1)

        # Add button for two-layer salinity.
        layoutSalinity.addWidget(editSalinity.editor.buttonFromValue(12),    4,0,1,2)

        # Add controls to edit two-layer salinity.
        layoutLayer = QtWidgets.QGridLayout()
        editSalinityUpperThickness.addToGridLayout(layoutLayer)
        editSalinityUpper.addToGridLayout(layoutLayer)
        editSalinityLowerThickness.addToGridLayout(layoutLayer)
        editSalinityLower.addToGridLayout(layoutLayer)
        layoutLayer.setColumnStretch(3,1)
        layoutSalinity.addLayout(layoutLayer,5,1)

        # Add button for stably stratified salinity.
        layoutSalinity.addWidget(editSalinity.editor.buttonFromValue(13),    6,0,1,2)

        # Add controls to edit stably stratified salinity.
        layoutNSquare = QtWidgets.QGridLayout()
        editSalinitySurface.addToGridLayout(layoutNSquare)
        editSalinityNSquare.addToGridLayout(layoutNSquare)
        layoutNSquare.setColumnStretch(3,1)
        layoutSalinity.addLayout(layoutNSquare,7,1)

        # Add button for custom salinities.
        layoutSalinity.addWidget(editSalinity.editor.buttonFromValue(2),     8,0,1,2)

        # Add control to choose custom salinities.
        layoutFile = QtWidgets.QHBoxLayout()
        editSalinityFile.addToBoxLayout(layoutFile,label=False,unit=False)
        layoutFile.addStretch()
        layoutSalinity.addLayout(layoutFile,9,1)

        layoutSalinity.setColumnMinimumWidth(0,commonqt.getRadioWidth())
        layout.addLayout(layoutSalinity)

        # Add control to configure bulk relaxation.
        layout.addSpacing(20)
        layoutRelaxation = QtWidgets.QGridLayout()
        editSalinityRelaxation.addToGridLayout(layoutRelaxation,0,0,1,5,label=False,unit=False)
        editSalinityBulk.addToGridLayout(layoutRelaxation,1,1)
        layoutRelaxation.setColumnMinimumWidth(0,commonqt.getRadioWidth())
        layoutRelaxation.setColumnStretch(3,1)
        layout.addLayout(layoutRelaxation)

        layout.addStretch(1)

        # Set layout.
        self.setLayout(layout)

class PageTemperature(ScenarioPage):

    def __init__(self,parent=None):
        ScenarioPage.__init__(self, parent)

        layout = QtWidgets.QVBoxLayout()

        self.title = self.createHeader('Temperature observations','Here you can provide observations on vertical temperature profiles. These are used to initialize the column, and optionally to relax model results to.')
        layout.addWidget(self.title)
        layout.addSpacing(20)

        # Create main layout
        layoutTemperature = QtWidgets.QGridLayout()

        # Create button group for the temperature option, and an explanatory label.
        editTemperature = self.factory.createEditor('obs/tprofile',self,selectwithradio=True)

        # Create editors for all different temperature configurations.
        editTemperatureConstant       = self.factory.createEditor('obs/tprofile/t_const',self)
        editTemperatureUpperThickness = self.factory.createEditor('obs/tprofile/z_t1',self)
        editTemperatureUpper          = self.factory.createEditor('obs/tprofile/t_1',self)
        editTemperatureLowerThickness = self.factory.createEditor('obs/tprofile/z_t2',self)
        editTemperatureLower          = self.factory.createEditor('obs/tprofile/t_2',self)
        editTemperatureSurface        = self.factory.createEditor('obs/tprofile/t_surf',self)
        editTemperatureNSquare        = self.factory.createEditor('obs/tprofile/t_obs_NN',self)
        editTemperatureFile           = self.factory.createEditor('obs/tprofile/t_prof_file',self)

        # Create editors for relaxation.
        editTemperatureRelaxation     = self.factory.createEditor('obs/tprofile/TRelax',self,boolwithcheckbox=True)
        editTemperatureBulk           = self.factory.createEditor('obs/tprofile/TRelax/TRelaxTauM',self)

        # Add explanatory label
        layoutTemperature.addWidget(editTemperature.createLabel(),       0,0,1,2)

        # Add button for no temperature
        layoutTemperature.addWidget(editTemperature.editor.buttonFromValue(0),     1,0,1,2)

        # Add button for constant temperature.
        layoutTemperature.addWidget(editTemperature.editor.buttonFromValue(11),    2,0,1,2)

        # Add controls to edit constant temperature.
        layoutConstant = QtWidgets.QGridLayout()
        editTemperatureConstant.addToGridLayout(layoutConstant)
        layoutConstant.setColumnStretch(3,1)
        layoutTemperature.addLayout(layoutConstant,3,1)

        # Add button for two-layer temperature.
        layoutTemperature.addWidget(editTemperature.editor.buttonFromValue(12),    4,0,1,2)

        # Add controls to edit two-layer temperature.
        layoutLayer = QtWidgets.QGridLayout()
        editTemperatureUpperThickness.addToGridLayout(layoutLayer)
        editTemperatureUpper.addToGridLayout(layoutLayer)
        editTemperatureLowerThickness.addToGridLayout(layoutLayer)
        editTemperatureLower.addToGridLayout(layoutLayer)
        layoutLayer.setColumnStretch(3,1)
        layoutTemperature.addLayout(layoutLayer,5,1)

        # Add button for stably stratified temperature.
        layoutTemperature.addWidget(editTemperature.editor.buttonFromValue(13),    6,0,1,2)

        # Add controls to edit stably stratified temperature.
        layoutNSquare = QtWidgets.QGridLayout()
        editTemperatureSurface.addToGridLayout(layoutNSquare)
        editTemperatureNSquare.addToGridLayout(layoutNSquare)
        layoutNSquare.setColumnStretch(3,1)
        layoutTemperature.addLayout(layoutNSquare,7,1)

        # Add button for custom salinities.
        layoutTemperature.addWidget(editTemperature.editor.buttonFromValue(2),     8,0,1,2)

        # Add control to choose custom salinities.
        layoutFile = QtWidgets.QHBoxLayout()
        editTemperatureFile.addToBoxLayout(layoutFile,label=False,unit=False)
        layoutFile.addStretch()
        layoutTemperature.addLayout(layoutFile,9,1)

        layoutTemperature.setColumnMinimumWidth(0,commonqt.getRadioWidth())
        layout.addLayout(layoutTemperature)

        # Add control to configure bulk relaxation.
        layout.addSpacing(20)
        layoutRelaxation = QtWidgets.QGridLayout()
        editTemperatureRelaxation.addToGridLayout(layoutRelaxation,0,0,1,5,label=False,unit=False)
        editTemperatureBulk.addToGridLayout(layoutRelaxation,1,1)
        layoutRelaxation.setColumnMinimumWidth(0,commonqt.getRadioWidth())
        layoutRelaxation.setColumnStretch(3,1)
        layout.addLayout(layoutRelaxation)

        layout.addStretch(1)

        # Set layout.
        self.setLayout(layout)

class PageTurbulence(ScenarioPage):

    def __init__(self,parent=None):
        ScenarioPage.__init__(self, parent)

        # Create main layout
        layout = QtWidgets.QVBoxLayout()

        self.title = self.createHeader('Turbulence model','Here you choose the turbulence model to be used.')
        layout.addWidget(self.title)
        layout.addSpacing(20)

        layoutTurbulence = QtWidgets.QGridLayout()

        # Create button group for the turbulence method option, and an explanatory label.
        editMethod = self.factory.createEditor('gotmturb/turb_method',self,selectwithradio=True)
        bngrpMethod = editMethod.editor

        # Add explanatory label
        layoutTurbulence.addWidget(editMethod.createLabel(),0,0,1,3)

        # Add button for convective adjustment
        layoutTurbulence.addWidget(bngrpMethod.buttonFromValue(0), 1,0,1,3)

        # Add button for turbulence model calculating TKE and length scale
        layoutTurbulence.addWidget(bngrpMethod.buttonFromValue(2), 2,0,1,3)

        # Add controls specific to first-order model
        layoutFirstOrder = QtWidgets.QGridLayout()
        editStabilityMethod = self.factory.createEditor('gotmturb/stab_method',self)
        editStabilityMethod.addToGridLayout(layoutFirstOrder,0,0)
        layoutTurbulence.addLayout(layoutFirstOrder,      3,1)

        # Add button for second-order model
        layoutTurbulence.addWidget(bngrpMethod.buttonFromValue(3), 4,0,1,3)

        # Add controls specific to second-order model
        layoutSecondOrder = QtWidgets.QGridLayout()
        editSecondCoef = self.factory.createEditor('gotmturb/scnd/scnd_coeff',self)
        editSecondCoef.addToGridLayout(layoutSecondOrder,0,0)
        layoutTurbulence.addLayout(layoutSecondOrder,     5,1)

        # Add button for KPP model
        layoutTurbulence.addWidget(bngrpMethod.buttonFromValue(99),6,0,1,3)

        layoutTurbulence.setColumnStretch(3,1)
        layoutTurbulence.setColumnMinimumWidth(0,commonqt.getRadioWidth())
        layout.addLayout(layoutTurbulence)

        layout.addSpacing(20)

        layoutOther = QtWidgets.QGridLayout()
        editTkeMethod = self.factory.createEditor('gotmturb/tke_method',self)
        editLenScaleMethod = self.factory.createEditor('gotmturb/len_scale_method',self)
        editTkeMethod.addToGridLayout(layoutOther,0,0)
        editLenScaleMethod.addToGridLayout(layoutOther)
        layoutOther.setColumnStretch(3,1)
        layout.addLayout(layoutOther)

        layout.addStretch(1)

        # Set layout.
        self.setLayout(layout)

class PageAirSeaInteraction(ScenarioPage):

    def __init__(self,parent=None):
        ScenarioPage.__init__(self, parent)

        radiowidth = commonqt.getRadioWidth()

        # Create main layout
        layout = QtWidgets.QVBoxLayout()

        self.title = self.createHeader('Air-sea interaction: heat and momentum','Here you specify fluxes of heat and momentum across the ocean-atmosphere interface. Note that heat fluxes include latent and sensible fluxes, but not short-wave solar radiation.')
        layout.addWidget(self.title)
        layout.addSpacing(20)

        layoutAirSea = QtWidgets.QGridLayout()
        layoutAirSea.setColumnMinimumWidth(0,radiowidth)

        editCalcFluxes = self.factory.createEditor('airsea/flux_source',self,selectwithradio=True)

        # Meteo file and unit

        meteolayout = QtWidgets.QVBoxLayout()
        editMeteoFile = self.factory.createEditor('airsea/meteo_file',self)
        editWetMode   = self.factory.createEditor('airsea/hum_method',  self)

        meteofilelayout = QtWidgets.QHBoxLayout()
        editMeteoFile.addToBoxLayout(meteofilelayout,label=False,unit=False)
        meteofilelayout.addStretch()
        meteolayout.addLayout(meteofilelayout)

        meteowetmodelayout = QtWidgets.QHBoxLayout()
        editWetMode.addToBoxLayout(meteowetmodelayout)
        meteowetmodelayout.addStretch()
        meteolayout.addLayout(meteowetmodelayout)

        # Shortwave radiation

        #groupboxSwr = self.factory.createEditor('airsea/swr_method',self,groupbox=True).editor
        #
        #swrlayout = QtWidgets.QGridLayout()
        #swrlayout.setColumnMinimumWidth(0,radiowidth)
#
        #editSwrMethod = self.factory.createEditor('airsea/swr_method', self,selectwithradio=True)
        #editConstSwr  = self.factory.createEditor('airsea/const_swr',  self)
        #editSwrFile   = self.factory.createEditor('airsea/swr_file',   self)
        #
        #swrlayout.addWidget(editSwrMethod.editor.buttonFromValue(0),1,0,1,2)
#
        #swrlayout.addWidget(editSwrMethod.editor.buttonFromValue(1),2,0,1,2)
        #constswrlayout = QtWidgets.QHBoxLayout()
        #editConstSwr.addToBoxLayout(constswrlayout)
        #swrlayout.addLayout(constswrlayout,3,1)
        #
        #swrlayout.addWidget(editSwrMethod.editor.buttonFromValue(2),4,0,1,2)
        #swrfilelayout = QtWidgets.QHBoxLayout()
        #editSwrFile.addToBoxLayout(swrfilelayout,label=False,unit=False)
        #swrfilelayout.addStretch()
        #swrlayout.addLayout(swrfilelayout,5,1)
#
        #swrlayout.addWidget(editSwrMethod.editor.buttonFromValue(3),6,0,1,2)
        #
        #swrlayout.setColumnStretch(2,1)
        #
        #groupboxSwr.setLayout(swrlayout)
#
        # Heat flux

        groupboxHeat = self.factory.createEditor('airsea/heat_method',self,groupbox=True).editor

        heatlayout = QtWidgets.QGridLayout()
        heatlayout.setColumnMinimumWidth(0,radiowidth)

        editHeatMethod   = self.factory.createEditor('airsea/heat_method',  self,selectwithradio=True)
        editConstHeat    = self.factory.createEditor('airsea/const_heat',   self)
        editHeatfluxFile = self.factory.createEditor('airsea/heatflux_file',self)

        heatlayout.addWidget(editHeatMethod.editor.buttonFromValue(0),1,0,1,2)

        heatlayout.addWidget(editHeatMethod.editor.buttonFromValue(1),2,0,1,2)
        constheatlayout = QtWidgets.QHBoxLayout()
        editConstHeat.addToBoxLayout(constheatlayout)
        heatlayout.addLayout(constheatlayout,3,1)

        heatlayout.addWidget(editHeatMethod.editor.buttonFromValue(2),4,0,1,2)
        heatfilelayout = QtWidgets.QHBoxLayout()
        editHeatfluxFile.addToBoxLayout(heatfilelayout,label=False,unit=False)
        heatfilelayout.addStretch()
        heatlayout.addLayout(heatfilelayout,5,1)

        heatlayout.setColumnStretch(2,1)

        groupboxHeat.setLayout(heatlayout)

        # Momentum fluxes

        groupboxMomentum = self.factory.createEditor('airsea/momentum_method',self,groupbox=True).editor

        layoutMomentum = QtWidgets.QGridLayout()
        layoutMomentum.setColumnMinimumWidth(0,radiowidth)

        editMomentumMethod  = self.factory.createEditor('airsea/momentum_method',  self,selectwithradio=True)
        editMomentumConstTx = self.factory.createEditor('airsea/const_tx',         self)
        editMomentumConstTy = self.factory.createEditor('airsea/const_ty',         self)
        editmomentumFile    = self.factory.createEditor('airsea/momentumflux_file',self)

        layoutMomentum.addWidget(editMomentumMethod.editor.buttonFromValue(0),1,0,1,2)

        layoutMomentum.addWidget(editMomentumMethod.editor.buttonFromValue(1),2,0,1,2)
        constmomentumlayout = QtWidgets.QGridLayout()
        editMomentumConstTx.addToGridLayout(constmomentumlayout)
        editMomentumConstTy.addToGridLayout(constmomentumlayout)
        layoutMomentum.addLayout(constmomentumlayout,3,1)

        layoutMomentum.addWidget(editMomentumMethod.editor.buttonFromValue(2),4,0,1,2)
        momentumfilelayout = QtWidgets.QHBoxLayout()
        editmomentumFile.addToBoxLayout(momentumfilelayout,label=False,unit=False)
        momentumfilelayout.addStretch()
        layoutMomentum.addLayout(momentumfilelayout,5,1)

        layoutMomentum.setColumnStretch(2,1)

        groupboxMomentum.setLayout(layoutMomentum)

        # Freshwater fluxes

#        groupboxPe = QtWidgets.QGroupBox('fresh water flux',self)

#        layoutPe = QtWidgets.QGridLayout()
#        layoutPe.setColumnMinimumWidth(0,radiowidth)

#        self.editPeMethod  = self.factory.createEditor(['airsea','p_e_method'],   self,selectwithradio=True)
#        self.editPeConst   = self.factory.createEditor(['airsea','const_p_e'],    self)
#        self.editPeFile    = self.factory.createEditor(['airsea','p_e_flux_file'],self)

#        layoutPe.addWidget(self.editPeMethod.editor.buttonFromValue(0),1,0,1,2)

#        layoutPe.addWidget(self.editPeMethod.editor.buttonFromValue(1),2,0,1,2)
#        constpelayout = QtWidgets.QGridLayout()
#        self.editPeConst.addToGridLayout(constpelayout)
#        layoutPe.addLayout(constpelayout,3,1)

#        layoutPe.addWidget(self.editPeMethod.editor.buttonFromValue(2),4,0,1,2)
#        pefilelayout = QtWidgets.QHBoxLayout()
#        self.editPeFile.addToBoxLayout(pefilelayout,label=False,unit=False)
#        pefilelayout.addStretch()
#        layoutPe.addLayout(pefilelayout,5,1)

#        groupboxPe.setLayout(layoutPe)

        # Create final layout

        layoutAirSea.addWidget(editCalcFluxes.createLabel(),   0,0,1,2)
        layoutAirSea.addWidget(editCalcFluxes.editor.buttonFromValue(0),1,0,1,2)
        layoutAirSea.addLayout(meteolayout,                    2,1)
        layoutAirSea.addWidget(editCalcFluxes.editor.buttonFromValue(1),3,0,1,2)
        layoutAirSea.addWidget(groupboxHeat,                   4,1)
        layoutAirSea.addWidget(groupboxMomentum,               5,1)
        #layoutAirSea.addWidget(groupboxSwr,                    6,0,1,2)
        #layoutAirSea.addWidget(groupboxPe,4,0,1,2)

        layout.addLayout(layoutAirSea)

        layout.addStretch(1)

        self.setLayout(layout)

class PageAirSeaInteraction2(ScenarioPage):

    def __init__(self,parent=None):
        ScenarioPage.__init__(self, parent)

        radiowidth = commonqt.getRadioWidth()

        # Create main layout
        layout = QtWidgets.QVBoxLayout()

        self.title = self.createHeader('Air-sea interaction: short-wave radiation and precipitation','Here you specify short-wave solar radiation and precipitation at the ocean-atmosphere interface.')
        layout.addWidget(self.title)
        layout.addSpacing(20)

        layoutAirSea = QtWidgets.QGridLayout()
        layoutAirSea.setColumnMinimumWidth(0,radiowidth)

        # Shortwave radiation

        groupboxSwr = self.factory.createEditor('airsea/swr_method',self,groupbox=True).editor

        swrlayout = QtWidgets.QGridLayout()
        swrlayout.setColumnMinimumWidth(0,radiowidth)

        editSwrMethod = self.factory.createEditor('airsea/swr_method', self,selectwithradio=True)
        editConstSwr  = self.factory.createEditor('airsea/const_swr',  self)
        editSwrFile   = self.factory.createEditor('airsea/swr_file',   self)

        swrlayout.addWidget(editSwrMethod.editor.buttonFromValue(0),1,0,1,2)

        swrlayout.addWidget(editSwrMethod.editor.buttonFromValue(1),2,0,1,2)
        constswrlayout = QtWidgets.QHBoxLayout()
        editConstSwr.addToBoxLayout(constswrlayout)
        swrlayout.addLayout(constswrlayout,3,1)

        swrlayout.addWidget(editSwrMethod.editor.buttonFromValue(2),4,0,1,2)
        swrfilelayout = QtWidgets.QHBoxLayout()
        editSwrFile.addToBoxLayout(swrfilelayout,label=False,unit=False)
        swrfilelayout.addStretch()
        swrlayout.addLayout(swrfilelayout,5,1)

        swrlayout.addWidget(editSwrMethod.editor.buttonFromValue(3),6,0,1,2)

        swrlayout.setColumnStretch(2,1)

        groupboxSwr.setLayout(swrlayout)

        # Freshwater fluxes

        groupboxPe = self.factory.createEditor('airsea/precip_method',self,groupbox=True).editor

        layoutPe = QtWidgets.QGridLayout()
        layoutPe.setColumnMinimumWidth(0,radiowidth)

        self.editPeMethod  = self.factory.createEditor('airsea/precip_method',self,selectwithradio=True)
        self.editPeConst   = self.factory.createEditor('airsea/const_precip', self)
        self.editPeFile    = self.factory.createEditor('airsea/precip_file',  self)

        layoutPe.addWidget(self.editPeMethod.editor.buttonFromValue(0),1,0,1,2)

        layoutPe.addWidget(self.editPeMethod.editor.buttonFromValue(1),2,0,1,2)
        constpelayout = QtWidgets.QGridLayout()
        self.editPeConst.addToGridLayout(constpelayout)
        layoutPe.addLayout(constpelayout,3,1)

        layoutPe.addWidget(self.editPeMethod.editor.buttonFromValue(2),4,0,1,2)
        pefilelayout = QtWidgets.QHBoxLayout()
        self.editPeFile.addToBoxLayout(pefilelayout,label=False,unit=False)
        pefilelayout.addStretch()
        layoutPe.addLayout(pefilelayout,5,1)

        layoutPe.setColumnStretch(2,1)

        groupboxPe.setLayout(layoutPe)

        # Create final layout
        layoutAirSea.addWidget(groupboxSwr,1,0,1,2)
        layoutAirSea.addWidget(groupboxPe, 2,0,1,2)

        layout.addLayout(layoutAirSea)

        layout.addStretch(1)

        self.setLayout(layout)

class PageBio(ScenarioPage):

    def __init__(self,parent=None):
        ScenarioPage.__init__(self, parent)

        layout = QtWidgets.QVBoxLayout()
        layout.setSpacing(25)

        self.title = self.createHeader('Biogeochemistry','Here you can specify a biogeochemical model to use, and configure this model.')
        layout.addWidget(self.title)

        # Set up model/treeview for bio section of scenario
        self.tree = xmlstore.gui_qt4.TypedStoreTreeView(self,self.scenario,self.scenario['/bio/bio_model'],datasourcedir=parent.getProperty('datasourcedir'))
        self.tree.setRootIsDecorated(True)

        editBioModel = self.factory.createEditor('bio/bio_model',self)
        self.factory.attachExternalEditor(self.tree,'bio/bio_model',conditiontype='ne',conditionvalue=0)
        editBioModel.addToBoxLayout(layout)
        layout.addWidget(self.tree,10000)
        layout.addStretch(1)

        self.setLayout(layout)

    def destroy(self,destroyWindow = True,destroySubWindows = True):
        self.tree.destroy(destroyWindow,destroySubWindows)
        commonqt.WizardPage.destroy(self,destroyWindow,destroySubWindows)

class PageAdvanced(commonqt.WizardPage):

    def __init__(self,parent=None):
        commonqt.WizardPage.__init__(self, parent)

        self.scenario = parent.getProperty('scenario')
        if self.scenario is None: raise Exception('No scenario available; this page should not have been available.')

        self.tree = xmlstore.gui_qt4.TypedStoreTreeView(self,self.scenario,datasourcedir=parent.getProperty('datasourcedir'))

        layout = QtWidgets.QVBoxLayout()

        self.title = self.createHeader('Advanced settings','Here you can change all properties of your scenario. Values that differ from the default are shown in bold; they can be reset to their default by clicking them with the right mousebutton and choosing "Reset value".')
        layout.addWidget(self.title)
        layout.addSpacing(20)
        layout.addWidget(self.tree)
        self.setLayout(layout)

    def isComplete(self):
        return True

    def saveData(self,mustbevalid):
        if mustbevalid:
            progressdialog = commonqt.ProgressDialog(self,title='Validating settings...')
            try:
                errors = self.scenario.validate(callback=progressdialog.onProgressed,repair=1)
            finally:
                progressdialog.close()
            if len(errors)>0:
                msg = '\n'.join(errors)
                if len(errors)>1: msg = 'The following problems remain:\n\n%s' % msg
                QtWidgets.QMessageBox.critical(self,'Scenario is incomplete',msg,QtWidgets.QMessageBox.Ok,QtWidgets.QMessageBox.NoButton)
                return False
        return True

    def destroy(self,destroyWindow = True,destroySubWindows = True):
        self.tree.destroy(destroyWindow,destroySubWindows)
        commonqt.WizardPage.destroy(self,destroyWindow,destroySubWindows)

class PageSave(commonqt.WizardPage):

    def __init__(self,parent=None):
        commonqt.WizardPage.__init__(self, parent)

        self.scenario = parent.getProperty('scenario')

        self.label = QtWidgets.QLabel('The scenario has been modified. Do you want to save it?',self)
        self.bngroup     = QtWidgets.QButtonGroup()
        self.radioNoSave = QtWidgets.QRadioButton('No, I do not want to save the modified scenario.', parent)
        self.radioSave   = QtWidgets.QRadioButton('Yes, I want to save the scenario to file.', parent)

        self.pathSave = commonqt.PathEditor(self,header='File to save to: ',save=True)
        self.pathSave.filter = 'GOTM scenario files (*.gotmscenario);;All files (*.*)'

        if self.scenario.path is not None:
            self.pathSave.setPath(self.scenario.path)

        self.bngroup.addButton(self.radioNoSave, 0)
        self.bngroup.addButton(self.radioSave,   1)

        layout = QtWidgets.QGridLayout()
        layout.addWidget(self.label,0,0,1,2)
        layout.addWidget(self.radioNoSave,1,0,1,2)
        layout.addWidget(self.radioSave,2,0,1,2)
        layout.addWidget(self.pathSave,3,1,1,1)

        layout.setRowStretch(4,1)
        layout.setColumnStretch(1,1)

        layout.setColumnMinimumWidth(0,commonqt.getRadioWidth())

        self.setLayout(layout)

        self.bngroup.buttonClicked.connect(self.onSourceChange)
        self.pathSave.onChanged.connect(self.completeStateChanged)

        self.radioSave.setChecked(True)
        self.onSourceChange()

    def onSourceChange(self):
        checkedid = self.bngroup.checkedId()
        self.pathSave.setVisible(checkedid==1)
        self.completeStateChanged()

    def isComplete(self):
        checkedid = self.bngroup.checkedId()
        if   checkedid==0:
            return True
        elif checkedid==1:
            return self.pathSave.hasPath()

    def saveData(self,mustbevalid):
        if not mustbevalid: return True
        checkedid = self.bngroup.checkedId()
        if checkedid==1:
            targetpath = self.pathSave.path()
            if os.path.isfile(targetpath):
                ret = QtWidgets.QMessageBox.warning(self, 'Overwrite existing file?', 'There already exists a file at the specified location. Overwrite it?', QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No)
                if ret==QtWidgets.QMessageBox.No:
                    return False
            dialog = commonqt.ProgressDialog(self,title='Saving...',suppressstatus=True)
            try:
                self.scenario.saveAll(targetpath,callback=dialog.onProgressed)
            except Exception as e:
                QtWidgets.QMessageBox.critical(self, 'Unable to save scenario', str(e), QtWidgets.QMessageBox.Ok, QtWidgets.QMessageBox.NoButton)
                dialog.close()
                return False
            dialog.close()
            self.owner.getSettings().addUniqueValue('Paths/RecentScenarios','Path',targetpath)
        return True

    def doNotShow(self):
        return (not self.scenario.hasChanged())

class PageFinal(commonqt.WizardPage):

    def __init__(self,parent=None):
        commonqt.WizardPage.__init__(self, parent)

        self.label = QtWidgets.QLabel('Your scenario is now complete.',self)

        layout = QtWidgets.QVBoxLayout()
        layout.addWidget(self.label)
        layout.addStretch()
        self.setLayout(layout)

    def isComplete(self):
        return True

class SequenceEditScenario(commonqt.WizardSequence):
    def __init__(self):
        #commonqt.WizardSequence.__init__(self,[PageLocation,PageDiscretization,PageAirSeaInteraction,PageAirSeaInteraction2,PageTurbulence,PageTemperature,PageSalinity,PageBio,PageAdvanced,PageSave])
        commonqt.WizardSequence.__init__(self,[PageLocation,PageDiscretization,PageAirSeaInteraction,PageAirSeaInteraction2,PageTurbulence,PageTemperature,PageSalinity,PageAdvanced,PageSave])

def loadScenario():
    # Create the application and enter the main message loop.
    createQApp = QtWidgets.QApplication.startingUp()
    if createQApp:
        app = QtWidgets.QApplication([" "])
    else:
        app = QtWidgets.qApp

    # Create wizard dialog
    wiz = commonqt.Wizard(headerlogo=os.path.join(core.common.getDataRoot(),'logo.png'),allowfinish=True)
    wiz.setWindowTitle('Load scenario')
    wiz.resize(800, 600)
    seq = commonqt.WizardSequence([PageOpen])
    wiz.setSequence(seq)
    wiz.show()

    ret = app.exec_()

    scenario = wiz.getProperty('scenario')
    if scenario is not None: scenario.addref()

    wiz.destroy()

    return scenario

def editScenario(scenario):
    # Create the application and enter the main message loop.
    createQApp = QtWidgets.QApplication.startingUp()
    if createQApp:
        app = QtWidgets.QApplication([" "])
    else:
        app = QtWidgets.qApp

    import xmlplot.gui_qt4

    # Create wizard dialog
    wiz = commonqt.Wizard(headerlogo=os.path.join(core.common.getDataRoot(),'logo.png'),allowfinish=True)
    wiz.setWindowTitle('Scenario builder')
    wiz.resize(800, 600)

    wiz.setProperty('scenario',scenario.addref())
    wiz.setSequence(SequenceEditScenario())

    wiz.show()

    ret = app.exec_()

    scenario = wiz.getProperty('scenario')
    if scenario is not None: scenario.addref()

    wiz.destroy()

def main():
    # Debug info
    print('Python version: '+str(sys.version_info))
    print('%s version: %s' % (qt4_backend,qt4_backend_version))
    print('Qt version: '+QtCore.qVersion())
    print('xml version: '+xml.__version__)

    # Create the application and enter the main message loop.
    createQApp = QtWidgets.QApplication.startingUp()
    if createQApp:
        app = QtWidgets.QApplication([" "])
    else:
        app = QtWidgets.qApp

    import xmlplot.gui_qt4

    # Create wizard dialog
    wiz = commonqt.Wizard(headerlogo='./logo.png')
    wiz.setWindowTitle('Scenario builder')
    wiz.resize(800, 600)

    seq = commonqt.WizardSequence([PageOpen,SequenceEditScenario(),PageFinal])
    wiz.setSequence(seq)

    wiz.show()

    ret = app.exec_()

    scenario = wiz.getProperty('scenario')
    if scenario is not None: scenario.addref()

    wiz.destroy()

    return scenario

# If the script has been run (as opposed to imported), enter the main loop.
if (__name__=='__main__'):
    ret = main()
    sys.exit(0)


from ffpp.Network import Network
import time
import asyncio
import urllib.request

async def main():
    # sol
    pr=Network("192.168.0.189")
    # sag
    #pr=Network("192.168.0.167")

    await pr.connect()
    await pr.sendControlRelease()

    try:
        while True:
            print(await pr.sendProgressRequest())
            print(await pr.sendPositionRequest())
            urllib.request.urlretrieve(f"http://{pr.ip}:8080/?action=snapshot", "camimg.jpg")
            time.sleep(5)
    finally:
        await pr.disconnect()

asyncio.run(main())


import pytest
import requests

class TestFirstAPI:
    names = [
        ("John"),
        ("Jane"),
        ("Jack"),
        (""),
    ]

    @pytest.mark.parametrize("name", names)
    def test_first_api(self, name):
        url = 'https://playground.learnqa.ru/api/hello'
        # name = 'Sodikmirzo'
        data = {'name': name}

        response = requests.get(url, params=data)

        assert response.status_code == 200, "Wrong response code"

        response_dick = response.json()
        assert "answer" in response_dick, "There is no field 'answer' in the response"

        if len(name) == 0:
            expected_response_text = "Hello, someone"
        else:
            expected_response_text = f"Hello, {name}"
        actual_response_text = response_dick['answer']
        assert actual_response_text == expected_response_text, "Actual response text is not equal to expected"