from typing import Any, Dict, List, Tuple, Type, Union

from pydantic import BaseModel

__all__ = ["Validator", "Parser", "Filter"]

class Validator:
    delimiter: str
    ordering: Union[bool, List[str]]

    def __init__(self, delimiter: str, ordering: Union[bool, List[str]]):
        self.delimiter = delimiter
        self.ordering = ordering

    def parse(self, model: Type[BaseModel]) -> Type[BaseModel]:
        raise NotImplementedError()

class Filter:
    _value: Any
    _field: Union[str, List[str]]

    def __init__(self, field: str, value: Any, delimiter: str):
        self._value = value

        self._field = [field]
        if delimiter in field:
            self._field = field.split(delimiter)

    def value(self) -> Any:
        return {
            "field": self._field,
            "value": self._value,
            "operation": self.operation(),
        }

    @classmethod
    def operation(cls) -> str:
        raise NotImplementedError()

    def name(self) -> str:
        raise NotImplementedError()

class Parser:
    delimiter: str
    validation_model: Type[BaseModel]
    ignore_unknown_name: bool = True

    def __init__(
        self,
        validation_model: Type[BaseModel],
        delimiter: str,
        ignore_unknown_name: bool = True,
    ):
        self.validation_model = validation_model
        self.delimiter = delimiter
        self.ignore_unknown_name = ignore_unknown_name

    def parse(
        self, raw_qs: str
    ) -> Tuple[Dict[Tuple[str, str], Any], Dict[Tuple[str, str], Type[Filter]]]:
        raise NotImplementedError()


""" Google Maps XBlock """
import urllib
from urlparse import urlunparse

import pkg_resources

from xblock.core import XBlock
from xblock.fields import Dict, Integer, Scope, String
from xblock.fragment import Fragment
from xblockutils.resources import ResourceLoader
from xblockutils.settings import XBlockWithSettingsMixin

from .utils import _, DummyTranslationService

LOADER = ResourceLoader(__name__)

@XBlock.needs('settings')
@XBlock.needs('i18n')
class GoogleMapsXBlock(XBlock, XBlockWithSettingsMixin):
    """
    XBlock providing a Google Map to a course.
    """

    # Class level constants
    block_settings_key = 'GoogleMapsXBlock'
    GOOGLE_EMBEDDED_MAPS_API_BASE_URL = 'www.google.com'
    GOOGLE_EMBEDDED_MAPS_PLACE_API_PATH = 'maps/embed/v1/place'
    GOOGLE_MAPS_API_BASE_URL = 'maps.googleapis.com'
    GOOGLE_MAPS_JS_API_PATH = 'maps/api/js'

    # Instance level fields
    display_name = String(
        display_name=_('Display Name'),
        default=_('Google Maps XBlock'),
        help=_('The display name for this component.'),
        scope=Scope.settings
    )

    iframe_height = Integer(
        display_name=_('Map height'),
        default=450,
        scope=Scope.settings
    )

    iframe_width = Integer(
        display_name=_('Map width'),
        default=600,
        scope=Scope.settings
    )

    place = Dict(
        display_name=_('Information about the place being searched'),
        default={
            'id': 'ChIJCwHM0mF344kRAWE3Rmv-RcY',  # ExtensionEngine, Boston headquarters
            'name': '196 Broadway, Cambridge, MA 02139, USA'
        },
        scope=Scope.settings
    )

    @property
    def api_key(self):
        """
        Returns the Google Maps API key from Settings Service.

        The API key should be set in both lms/cms env.json files inside XBLOCK_SETTINGS.

        Example:
            "XBLOCK_SETTINGS": {
                "GoogleMapsXBlock": {
                    "API_KEY": "YOUR API KEY GOES HERE"
                }
            },
        """
        return self.get_xblock_settings().get('API_KEY', '')

    @property
    def embedded_google_maps_url(self):
        """Returns the Google Embedded Maps URL"""
        return urlunparse(self.create_urlunparse_tuple(
            netloc=self.GOOGLE_EMBEDDED_MAPS_API_BASE_URL,
            path=self.GOOGLE_EMBEDDED_MAPS_PLACE_API_PATH,
            query=self.embedded_google_maps_url_parameters
        ))

    @property
    def embedded_google_maps_url_parameters(self):
        """
        Returns:
            parameters (dict): Dictionary that contains all the get parameters for the API call
        """
        return {'q': 'place_id:{}'.format(self.place_id), 'key': self.api_key}

    @property
    def google_maps_js_api_parameters(self):
        """
        Returns:
            parameters (dict): Dictionary that contains all the get parameters for the API call
        """
        return {'callback': 'googleMapsXBlockApp.initMap', 'key': self.api_key, 'libraries': 'places'}

    @property
    def place_id(self):
        """Returns the Google Maps place id string from place dictionary."""
        return self.place['id']

    @property
    def place_name(self):
        """Returns the Google Maps place name string from place dictionary."""
        return self.place['name']

    # TODO: If needed, replace create_urlunparse_tuple with properties

    @staticmethod
    def create_urlunparse_tuple(scheme='https', netloc='', path='', params='', query='', fragment=''):
        """
        Helper method that creates the tuple for urlunparse.

        For more information about the arguments read https://docs.python.org/2/library/urlparse.html#urlparse.urlparse

        Returns:
            urlunparse parameter (tuple): Parameters for building the url
        """
        return (scheme, netloc, path, params, urllib.urlencode(query), fragment)

    def i18n_service(self):
        """Obtains translation service."""
        i18n_service = self.runtime.service(self, "i18n")
        if i18n_service:
            return i18n_service
        return DummyTranslationService()

    @staticmethod
    def resource_string(path):
        """Handy helper for getting resources from our kit."""
        data = pkg_resources.resource_string(__name__, path)
        return data.decode('utf8')

    def student_view(self, context=None):
        """
        The primary view of the GoogleMapsXBlock, shown to students
        when viewing courses.
        """
        context = {
            'self': self
        }

        frag = Fragment()
        frag.add_content(LOADER.render_template('/public/html/google_maps.html', context))
        frag.add_css(self.resource_string('public/css/google_maps.css'))
        frag.add_javascript(self.resource_string('public/js/src/google_maps.js'))
        frag.initialize_js('GoogleMapsXBlock')
        return frag

    def studio_view(self, context=None):
        """
        The primary view of the GoogleMapsXBlock, shown to staff members
        when editing courses.
        """

        context = {
            'fields': self.fields,
            'loading_gif_src': self.runtime.local_resource_url(self, 'public/images/loading.gif'),
            'self': self,
        }

        frag = Fragment()
        frag.add_content(LOADER.render_template('/public/html/google_maps_edit.html', context))
        frag.add_css(self.resource_string('public/css/google_maps_edit.css'))
        frag.add_javascript(self.resource_string('public/js/src/google_maps_edit.js'))
        frag.add_javascript_url(urlunparse(self.create_urlunparse_tuple(
            netloc=self.GOOGLE_MAPS_API_BASE_URL,
            path=self.GOOGLE_MAPS_JS_API_PATH,
            query=self.google_maps_js_api_parameters
        )))
        frag.initialize_js(
            'GoogleMapsEditXBlock',
            {
                'googleMapsApiKey': self.api_key,
                'place': self.place
            }
        )
        return frag

    @XBlock.json_handler
    def studio_submit(self, data, suffix=''):
        """
        Called when submitting the form in studio.
        """

        for key in data:
            setattr(self, key, data[key])

        return {'result': 'success'}

    @staticmethod
    def workbench_scenarios():
        """A canned scenario for display in the workbench."""
        return [
            ("GoogleMapsXBlock",
             """<google_maps/>
             """),
            ("Multiple GoogleMapsXBlock",
             """<vertical_demo>
                <google_maps/>
                <google_maps/>
                <google_maps/>
                </vertical_demo>
             """),
        ]


import time                   # Allows use of time.sleep() for delays
import pycom                  # Base library for Pycom devices
import machine                # Interfaces with hardware components
import micropython            # Needed to run any MicroPython code
import config                 # The users stored data for connection to WIFI and Adafruit
import connectiondata         # Saves WLAN and Client generated in boot.py
from network import WLAN      # For operation of WiFi network
from pycoproc_2 import Pycoproc # Needed for getting access to accelerometer on the pysense board
from LIS2HH12 import LIS2HH12   # Libary for the accelerometer
from machine import Pin         # Imports the use of pins

halleffect = Pin('P15', mode=Pin.IN)
py = Pycoproc()
acc = LIS2HH12(py)
angle = 10
send_data_intervalls = 60 # seconds
time_dictionary = {'start_time_upright' : 0, 'start_time_reclined' : 0, 'total_time_upright' : 0, 'total_time_reclined' : 0, 'end_time' : 0, 'total_time' : 0}
is_sitting = False;

def check_is_chair_reclined():
    send_data(config.AIO_ANGLE, acc.pitch())
    if acc.pitch() < -angle or acc.pitch() > angle:
        return True
    else:
        return False

def time_convert_min(sec):
    min = sec // 60
    hour = min // 60
    dispMin = min % 60
    return dispMin

def time_convert_hour(sec):
    min = sec // 60
    hour = min // 60
    dispHour = hour % 60
    return dispHour

def send_data(inc_topic, value):
    try:
        connectiondata.CLIENT.publish(topic = inc_topic, msg=str(value))
        print('SENT: {}, {}'.format(inc_topic, value))
    except Exception as e:
        print('FAILED: {}, {}'.format(inc_topic, value))

reminder_sent_dictionary = {0: False, 1 : False, 2 : False, 3 : False, 4 : False, 5 : False, 6 : False}

def send_reminder(time):
    if reminder_sent_dictionary.get(time) == False:
        send_data(config.AIO_SEND_REMINDER, time)
        reminder_sent_dictionary[time] = True

def reset_data():
    global is_sitting

    if is_sitting == True:
        hall_sensor = halleffect()

        for key in time_dictionary.keys():
            time_dictionary[str(key)] = 0

        send_data(config.AIO_RECLINED_TIME, time_convert_min(time_dictionary['total_time_reclined']))
        send_data(config.AIO_UPRIGHT_TIME, time_convert_min(time_dictionary['total_time_upright']))
        send_data(config.AIO_TOTAL_SITTING_TIME, (time_convert_hour(time_dictionary['total_time']) + (time_convert_min(time_dictionary['total_time']) / 60)))
        send_data(config.AIO_CURRENTLY_SITTING, hall_sensor)

        is_sitting = False

    pycom.rgbled(0xFF0000)

try:                      # Code between try: and finally: may cause an error
                          # so ensure the client disconnects the server if
                          # that happens.
    while 1:              # Repeat this loop forever

            hall_sensor = halleffect()

            if hall_sensor == 0:

                if is_sitting == False:
                    is_sitting = True
                    send_data(config.AIO_CURRENTLY_SITTING, hall_sensor)

                if time_dictionary['end_time'] == 0:
                    time_dictionary['end_time'] = time.time()
                is_chair_reclined = check_is_chair_reclined()

                if is_chair_reclined == True:

                    time_dictionary['start_time_reclined'] = time.time()
                    current_time = time_dictionary['start_time_reclined'] - time_dictionary['end_time']
                    time_dictionary['total_time_reclined'] +=  current_time
                    send_data(config.AIO_RECLINED_TIME, round(time_dictionary['total_time_reclined'] / 60))
                    pycom.rgbled(0xFFFF00)

                else:

                    time_dictionary['start_time_upright'] = time.time()
                    current_time = time_dictionary['start_time_upright'] - time_dictionary['end_time']
                    time_dictionary['total_time_upright'] +=  current_time
                    send_data(config.AIO_UPRIGHT_TIME, round(time_dictionary['total_time_upright'] / 60))
                    pycom.rgbled(0x00FF00)

                time_dictionary['total_time'] = time_dictionary['total_time_upright'] + time_dictionary['total_time_reclined']
                send_reminder(time_convert_hour(time_dictionary['total_time']))
                send_data(config.AIO_TOTAL_SITTING_TIME, (time_convert_hour(time_dictionary['total_time']) + (time_convert_min(time_dictionary['total_time']) / 60)))

            else:
                reset_data()

            time_dictionary['end_time'] = time.time()
            time.sleep(send_data_intervalls)
finally:                                 # If an exception is thrown ...
    connectiondata.CLIENT.disconnect()   # ... disconnect the client and clean up.
    connectiondata.CLIENT = None
    connectiondata.WLAN.disconnect()
    connectiondata.WLAN = None
    pycom.rgbled(0x000022)  # Status blue: stopped
    print("Disconnected from Adafruit IO.")


import os
import logging

import joblib
import yaml
import pandas as pd
import numpy as np
import skimage

from blocks.core import duplocorpus, labels
from mathtools import utils
from kinemparse import imu, primesense

logger = logging.getLogger(__name__)

def fixStartEndIndices(action_seq, rgb_frame_timestamp_seq, selected_frame_indices):
    new_times = rgb_frame_timestamp_seq[selected_frame_indices]
    start_times = rgb_frame_timestamp_seq[action_seq['start']]
    end_times = rgb_frame_timestamp_seq[action_seq['end']]

    action_seq['start'] = utils.nearestIndices(new_times, start_times)
    action_seq['end'] = utils.nearestIndices(new_times, end_times)

    return action_seq

def seqBounds(rgb_timestamp_seq, depth_timestamp_seq, accel_seq=[], gyro_seq=[]):
    def getTimestampBounds(timestamp_seq):
        lower = timestamp_seq.min()
        upper = timestamp_seq.max()
        return lower, upper

    start_times = []
    end_times = []

    if rgb_timestamp_seq is not None and not utils.isEmpty(rgb_timestamp_seq):
        lower, upper = getTimestampBounds(rgb_timestamp_seq)
        start_times.append(lower)
        end_times.append(upper)

    if depth_timestamp_seq is not None and not utils.isEmpty(depth_timestamp_seq):
        lower, upper = getTimestampBounds(depth_timestamp_seq)
        start_times.append(lower)
        end_times.append(upper)

    if accel_seq:
        for block_index, imu_sample_seq in accel_seq.items():
            sample_timestamps = imu.getImuGlobalTimestamps(imu_sample_seq)
            lower, upper = getTimestampBounds(sample_timestamps)
            start_times.append(lower)
            end_times.append(upper)

    if gyro_seq:
        for block_index, imu_sample_seq in gyro_seq.items():
            sample_timestamps = imu.getImuGlobalTimestamps(imu_sample_seq)
            lower, upper = getTimestampBounds(sample_timestamps)
            start_times.append(lower)
            end_times.append(upper)

    last_start_time = max(start_times)
    first_end_time = min(end_times)

    if last_start_time > first_end_time:
        warn_str = 'Skipping this sequence -- no overlapping data samples'
        logger.warning(warn_str)
        return None

    return max(start_times), min(end_times)

def loadMetadata(metadata_file, metadata_criteria={}):
    metadata = pd.read_excel(metadata_file, index_col=None)
    metadata = metadata.drop(columns=['Eyetrackingfilename', 'Notes'])
    metadata = metadata.loc[:, ~metadata.columns.str.contains('^Unnamed')]

    metadata = metadata.dropna(subset=['TaskID', 'VidID'])
    for key, value in metadata_criteria.items():
        in_corpus = metadata[key] == value
        metadata = metadata[in_corpus]

    metadata['VidID'] = metadata['VidID'].astype(int)
    metadata['TaskID'] = metadata['TaskID'].astype(int)

    metadata = metadata.set_index('VidID', verify_integrity=True)
    return metadata

def main(
        out_dir=None, corpus_name=None, default_annotator=None,
        metadata_file=None, metadata_criteria={},
        start_from=None, stop_at=None,
        start_video_from_first_touch=None, save_video_before_first_touch=None,
        subsample_period=None, rgb_as_float=None,
        modalities=None, use_annotated_keyframes=None, download_gt_keyframes=None):

    if modalities is None:
        modalities = ('video', 'imu')

    out_dir = os.path.expanduser(out_dir)
    metadata_file = os.path.expanduser(metadata_file)

    logger = utils.setupRootLogger(filename=os.path.join(out_dir, 'log.txt'))

    fig_dir = os.path.join(out_dir, 'figures')
    if not os.path.exists(fig_dir):
        os.makedirs(fig_dir)

    out_data_dir = os.path.join(out_dir, 'data')
    if not os.path.exists(out_data_dir):
        os.makedirs(out_data_dir)

    def saveToWorkingDir(var, var_name):
        joblib.dump(var, os.path.join(out_data_dir, f"{var_name}.pkl"))

    selected_frame_indices = slice(None, None, subsample_period)

    metadata = loadMetadata(metadata_file, metadata_criteria=metadata_criteria)
    metadata.to_csv(os.path.join(out_data_dir, 'metadata.csv'), index=True)

    corpus = duplocorpus.DuploCorpus(corpus_name)
    trial_ids = metadata['VidID']

    annotator_names = {}
    for seq_idx, trial_id in enumerate(trial_ids):

        if start_from is not None and seq_idx < start_from:
            continue

        if stop_at is not None and seq_idx > stop_at:
            break

        logger.info(f"Processing video {seq_idx + 1} / {len(trial_ids)}  (trial {trial_id})")

        if start_video_from_first_touch:
            label_seq = corpus.readLabels(trial_id, default_annotator)[0]
            first_touch_idxs = label_seq['start'][label_seq['action'] == 7]
            if not len(first_touch_idxs):
                logger.info(f"  Skipping trial {trial_id}: no first touch annotated")
                continue
            first_touch_idx = int(first_touch_idxs[0])
            # Video is sampled at 30 FPS --> start one second before the first
            # touch was annotated (unless that frame would have happened before
            # the camera started recording)
            start_idx = max(0, first_touch_idx - 30)
            selected_frame_indices = slice(start_idx, None, subsample_period)

        if use_annotated_keyframes:
            annotator = 'Jonathan'
            keyframe_idxs = labels.loadKeyframeIndices(corpus, annotator, trial_id)
            if not keyframe_idxs.any():
                logger.info(f"  Skipping trial {trial_id}: no labeled keyframes")
                continue
            selected_frame_indices = keyframe_idxs

        logger.info("  Loading labels...")
        action_seq, annotator_name, is_valid = corpus.readLabels(trial_id, default_annotator)
        if not is_valid:
            logger.info(f"    Skipping trial {trial_id}: No labels")
            continue

        if not (action_seq['action'] == 7).sum():
            logger.warning(f'    trial {trial_id}: missing first touch labels')

        annotator_names[trial_id] = annotator_name

        rgb_frame_fn_seq = corpus.getRgbFrameFns(trial_id)
        if rgb_frame_fn_seq is None:
            logger.info(f"    Skipping trial {trial_id}: No RGB frames")
            continue

        rgb_frame_timestamp_seq = corpus.readRgbTimestamps(trial_id, times_only=True)
        if rgb_frame_timestamp_seq is None:
            logger.info(f"    Skipping trial {trial_id}: No RGB timestamps")
            continue

        depth_frame_fn_seq = corpus.getDepthFrameFns(trial_id)
        if depth_frame_fn_seq is None:
            logger.info(f"    Skipping trial {trial_id}: No depth frames")
            continue

        depth_frame_timestamp_seq = corpus.readDepthTimestamps(trial_id, times_only=True)
        if depth_frame_timestamp_seq is None:
            logger.info(f"    Skipping trial {trial_id}: No depth timestamps")
            continue

        if action_seq['start'].max() >= len(rgb_frame_fn_seq):
            logger.info(f"    Skipping trial {trial_id}: actions longer than rgb frames")
            continue

        if action_seq['end'].max() >= len(rgb_frame_fn_seq):
            logger.info(f"    Skipping trial {trial_id}: actions longer than rgb frames")
            continue

        action_seq = fixStartEndIndices(
            action_seq, rgb_frame_timestamp_seq, selected_frame_indices
        )
        rgb_frame_timestamp_seq = rgb_frame_timestamp_seq[selected_frame_indices]
        depth_frame_timestamp_seq = depth_frame_timestamp_seq[selected_frame_indices]

        try:
            assembly_seq, is_valid = labels.parseLabelSeq(
                action_seq, timestamps=rgb_frame_timestamp_seq,
                structure_change_only=True,
            )
            if not is_valid:
                logger.info(f"    Skipping trial {trial_id}: Bad labels")
                continue
            assembly_seq[-1].end_idx = len(rgb_frame_timestamp_seq)
        except AssertionError:
            logger.info(f"    Skipping trial {trial_id}: Bad labels")
            continue

        trial_str = f"trial={trial_id}"
        if 'imu' in modalities:
            logger.info("  Loading and saving IMU data...")

            accel_seq = imu.loadImuSampleSeq(corpus, trial_id, sensor_name='accel')
            gyro_seq = imu.loadImuSampleSeq(corpus, trial_id, sensor_name='gyro')

            if not accel_seq:
                logger.info(f"    Skipping trial {trial_id}: Missing accel features")
                continue

            if not gyro_seq:
                logger.info(f"    Skipping trial {trial_id}: Missing gyro features")
                continue

            imu_bounds = seqBounds(None, None, accel_seq=accel_seq, gyro_seq=gyro_seq)
            if imu_bounds is None:
                logger.info(f"    Skipping trial {trial_id}: No overlapping data samples")
                continue

            accel_seq = imu.resampleImuSeq(accel_seq, seq_bounds=imu_bounds)
            gyro_seq = imu.resampleImuSeq(gyro_seq, seq_bounds=imu_bounds)

            saveToWorkingDir(accel_seq, f'{trial_str}_accel-samples')
            saveToWorkingDir(gyro_seq, f'{trial_str}_gyro-samples')

        if 'video' in modalities:
            logger.info("  Loading and saving video data...")

            rgb_frame_seq = primesense.loadRgbFrameSeq(
                rgb_frame_fn_seq, rgb_frame_timestamp_seq,
                stack_frames=True
            )

            if not len(rgb_frame_seq):
                logger.warning(f"    Skipping trial {trial_id}: RGB frames are empty!")
                continue

            if rgb_as_float:
                rgb_frame_seq = np.stack(
                    tuple(skimage.img_as_float(f) for f in rgb_frame_seq),
                    axis=0
                )

            saveToWorkingDir(
                rgb_frame_seq[selected_frame_indices],
                f'{trial_str}_rgb-frame-seq'
            )

            depth_frame_seq = primesense.loadDepthFrameSeq(
                depth_frame_fn_seq, depth_frame_timestamp_seq,
                stack_frames=True
            )

            if not len(rgb_frame_seq):
                logger.warning(f"    Skipping trial {trial_id}: depth frames are empty!")
                continue

            saveToWorkingDir(
                depth_frame_seq[selected_frame_indices],
                f'{trial_str}_depth-frame-seq'
            )

            if save_video_before_first_touch:
                end_idx = max(0, first_touch_idx - 30)
                bft_frame_indices = slice(None, end_idx, subsample_period)
                saveToWorkingDir(
                    rgb_frame_seq[bft_frame_indices],
                    f'{trial_str}_rgb-frame-seq-before-first-touch'
                )
                saveToWorkingDir(
                    depth_frame_seq[bft_frame_indices],
                    f'{trial_str}_depth-frame-seq-before-first-touch'
                )

        saveToWorkingDir(rgb_frame_fn_seq, f'{trial_str}_rgb-frame-fn-seq')
        saveToWorkingDir(rgb_frame_timestamp_seq, f'{trial_str}_rgb-frame-timestamp-seq')
        saveToWorkingDir(depth_frame_fn_seq, f'{trial_str}_depth-frame-fn-seq')
        saveToWorkingDir(depth_frame_timestamp_seq, f'{trial_str}_depth-frame-timestamp-seq')
        saveToWorkingDir(action_seq, f'{trial_str}_action-seq')
        saveToWorkingDir(assembly_seq, f'{trial_str}_assembly-seq')

        if download_gt_keyframes:
            annotator = 'Jonathan'
            keyframe_idxs = labels.loadKeyframeIndices(corpus, annotator, trial_id)
            if not keyframe_idxs.any():
                continue
            if start_video_from_first_touch:
                raise NotImplementedError()
            if subsample_period is not None:
                keyframe_idxs = utils.roundToInt(keyframe_idxs / subsample_period)
            saveToWorkingDir(keyframe_idxs, f'{trial_str}_gt-keyframe-seq')

    trial_ids = sorted(annotator_names.keys())
    annotator_names = [annotator_names[t_id] for t_id in trial_ids]
    annotator_names = pd.DataFrame({'trial_id': trial_ids, 'annotator_name': annotator_names})
    annotator_names.to_csv(os.path.join(out_data_dir, 'annotator_names.csv'), index=False)

if __name__ == "__main__":
    # Parse command-line args and config file
    cl_args = utils.parse_args(main)
    config, config_fn = utils.parse_config(cl_args, script_name=__file__)

    # Create output directory, instantiate log file and write config options
    out_dir = os.path.expanduser(config['out_dir'])
    if not os.path.exists(out_dir):
        os.makedirs(out_dir)
    with open(os.path.join(out_dir, config_fn), 'w') as outfile:
        yaml.dump(config, outfile)
    utils.copyFile(__file__, out_dir)

    main(**config)


import tkinter, wikipediaapi
import requests
#Configuring tkinter for size and window
root = tkinter.Tk()
root.geometry('680x420')
root.title("Wikipedia Article gets")

#GLOBALS
article_tab =0
#Configuring wikipedia api\

#configuring language
wiki = wikipediaapi.Wikipedia('en', extract_format=wikipediaapi.ExtractFormat.WIKI)

S = requests.Session()
URL = "https://en.wikipedia.org/w/api.php"

PARAMS = {
    "action":"query",
    "format": "json",
    "generator":"random",
    "grnnamespace":"0",
    "rvprop":"content",
    "grnlimit": "7"
}

#Functions
def get_clicked(val):
    if(not val):
        return
    # print(val)
    for l in root.pack_slaves():
       l.destroy()

    page = wiki.article(val)
    article_tab = tkinter.Toplevel(root)
    article_tab.title(page.title)

    scrollbar = tkinter.Scrollbar(article_tab)
    scrollbar.pack(side=tkinter.RIGHT, fill=tkinter.Y)

    article = tkinter.Text(article_tab, font="Courier 11",  yscrollcommand= scrollbar.set)
    article.insert(tkinter.END, page.text)
    article.pack()

    scrollbar.config(command=article.yview)

def get_random_articles():
    for l in root.pack_slaves():
       l.destroy()
    R = S.get(url=URL,params=PARAMS)
    Data = R.json()['query']['pages']
    for d in Data:
        d =  Data[d]
        btn = tkinter.Button(root , text=d['title'], command=lambda i=d['title']:get_clicked(i))
        btn.pack()

#Labels and buttons
lb1 = tkinter.Label(root,text="Click Button to get random wikipedia article(s)")

lb1.place(x=340,y=230, anchor="center")
lb1.configure(font=("Courier",18))

btn1 = tkinter.Button(root ,command=get_random_articles, text="Click")
btn1.place(x=340, y=260, anchor="center")
root.mainloop()


from django.shortcuts import render
from django.conf.urls import url
from eventhub import views
from registration.backends.simple.views import RegistrationView

urlpatterns = [ url(r'^$', views.index, name='index'),
                url(r'about/$', views.about, name='about'),
                url(r'contact/$', views.contact, name='contact'),
                url(r'create_event/$', views.create, name='create'),
                url(r'event/(?P<eventID>[\w\-]+)/$', views.event, name='event'),
                url(r'search/$', views.search, name='search'),
                url(r'category/(?P<cat>[\w\-]+)/$', views.category, name='category'),
                url(r'profile/', views.profile, name='profile'),
                url(r'creator/', views.get_creator, name='get_creator'),
                url(r'recommended/', views.recommended, name='recommended'),
                url(r'post_prefs/', views.post_prefs, name='post_prefs'),
                url(r'removeLike/(?P<eid>[\w\-]+)/$', views.remove_like, name='remove_like'),
                url(r'addLike/(?P<eid>[\w\-]+)/$', views.add_like, name='add_like'),
                url(r'edit/(?P<eid>[\w\-]+)/$', views.edit, name='edit'),
                url(r'delete/(?P<eid>[\w\-]+)/$', views.delete, name='delete'),
            ]


import json
import logging
import time
from typing import Optional
from datetime import datetime, date

import requests
from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist
from django.db import IntegrityError

from cafe_shtin.delivery.models import Product, Category

logger = logging.getLogger(__name__)

class SbisPresto:

    def __init__(self):
        self.headers = {"X-SBISAccessToken": settings.SBISACCESSTOKEN}
        self.shops = self.__get_shops()
        self.menu = self.__get_menu()

    def __get_shops(self) -> list:
        parameters = {
            'withPhones': 'true',
            'withPrices': 'true',
            'product': 'delivery'
        }
        url = 'https://api.sbis.ru/retail/point/list?'
        response = requests.get(url, params=parameters, headers=self.headers).json()
        shops = [{'id': x['id'], 'name': x['name']} for x in response['salesPoints']]
        return shops

    def __get_menu(self) -> dict:
        parameters = {
            'pointId': self.shops[0]['id'],
            'actualDate': f'{datetime.now().strftime("%Y-%m-%d")}',
        }
        url = 'https://api.sbis.ru/retail/nomenclature/price-list?'
        response = requests.get(url, params=parameters, headers=self.headers).json()['priceLists']
        for menu in response:
            if menu['name'] == 'Доставка':
                return {'id': menu['id'], 'name': menu['name']}
        logger.error(f'Не найденo нужное меню. Взято из {self.shops[0]["name"]}')

    def update_catalog_site(self):
        categories, dishes = self._get_list_dishes(shop_id=self.shops[0]['id'],
                                                   menu_id=self.menu['id'],
                                                   stop_dish='false',
                                                   balance='false')
        logger.debug('Начало сохранения фото блюд')
        dishes = self._save_image(dishes=dishes)
        logger.debug('Фото блюд сохранены успешно')
        logger.debug('Актуализация категорий')
        for category in categories:
            obj, created = Category.objects.update_or_create(id=category['id'],
                                                             defaults={'name': category['name']})
        logger.debug('Актуализация блюд')
        for dish in dishes:
            try:
                obj, created = Product.objects.update_or_create(id=dish['id'],
                                                                defaults={
                                                                    'name': dish['name'],
                                                                    'category_id': Category.objects.get(
                                                                        id=dish['category_id']),
                                                                    'price': dish['cost'],
                                                                    'description': dish['description'],
                                                                    'image': dish['image_url'],
                                                                    'weight': dish['attributes'][
                                                                        'outQuantity'],
                                                                    'uuid': dish['uuid'],
                                                                    'price_list_id': dish['price_list_id']
                                                                }
                                                                )
            except ObjectDoesNotExist:
                logger.warning(f'Неудачная попытка добавить блюдо {dish["name"]}\n.'
                               f'Невозможно добавить блюдо с категорией id={dish["category_id"]}\n'
                               f'Блюдо пропущенно, преход к следующему блюду')
            except IntegrityError:
                logger.warning(f'Неудачная попытка добавить блюдо {dish["name"]}\n.'
                               f'Проверьте правильность внесения блюда в сбис престо!')
            except KeyError:
                logger.warning(f'Неудачная попытка добавить блюдо {dish["name"]}\n.'
                               f'В сбис престо не заполнено какое то поле (скорее всего вес)')
            else:
                logger.info(f'блюдо {dish["name"]} обновлено')

    def _get_list_dishes(self, shop_id: int, menu_id: int, stop_dish: str = 'false', balance: str = 'false'):
        parameters = {
            'pointId': shop_id,
            'priceListId': menu_id,
            'withBalance': balance,
            'noStopList': stop_dish,
        }
        url = 'https://api.sbis.ru/retail/nomenclature/list?'
        response = requests.get(url, params=parameters, headers=self.headers).json()
        categories = [{'id': x['hierarchicalId'], 'name': x['name']} for x in response['nomenclatures'] if
                      x['attributes'] is None]
        if balance == 'true':
            dishes = [{'id': x['id'],
                       'balance': x['balance'],
                       } for x in response['nomenclatures'] if x['attributes'] is not None]
        else:
            dishes = [{'id': x['id'],
                       'name': x['name'],
                       'category_id': x['hierarchicalParent'],
                       'cost': x['cost'],
                       'image_url': x['images'],
                       'description': x['description'],
                       'attributes': x['attributes'],
                       'uuid': x['externalId'],
                       'price_list_id': menu_id
                       } for x in response['nomenclatures'] if x['attributes'] is not None]
        return categories, dishes

    def _save_image(self, dishes: list):
        start = time.time()
        url = 'https://api.sbis.ru/retail/'
        for dish in dishes:
            image = requests.get(f'{url}{dish["image_url"][0]}', headers=self.headers).content
            image_url = f"media/dishes/{dish['name']}.jpg"
            with open(image_url, "wb") as file:
                file.write(image)
            dish["image_url"] = f'/{image_url}'
        logger.debug(f'Загрузка фото блюд заняло {(time.time() - start):0.2f} сек.')
        return dishes

    def update_count_dishes_in_shop(self):
        Product.objects.all().update(available=False)
        for shop in self.shops:
            categories, dishes = self._get_list_dishes(shop_id=shop['id'],
                                                       menu_id=self.menu['id'],
                                                       stop_dish='true',
                                                       balance='true')
            for dish in dishes:
                try:
                    obj, created = Product.objects.update_or_create(
                        dish_id=Product.objects.get(id=dish['id']),
                        defaults={
                            'count': dish['balance'],
                            'available': True})
                except ObjectDoesNotExist:
                    logger.warning(f'Ошибка добавления количества блюд для {dish["id"]}\n'
                                   f'Проверьте правильность shop_id={shop["id"]} и dish_id={dish["id"]}\n'
                                   f'Возможно еще не прошла актуализация каталога блюд на сайте')
                else:
                    logger.info(f'Количество блюд для id = {dish["id"]} обновлено')

class SbisOrder:
    def __init__(self, shop_id, user, nomenclatures, address, payment, is_pickup):
        self.headers = {"X-SBISAccessToken": settings.SBISACCESSTOKEN,
                        'Content-Type': 'application/json'}
        self.shop_id = shop_id
        self.user = user
        self.nomenclatures = nomenclatures
        self.address = address
        self.payment = payment
        self.is_pickup = is_pickup

    def make_order(self):
        url = "https://api.sbis.ru/retail/order/create"
        payload = {
            "product": "delivery",  # Обязательно delivery
            "pointId": self.shop_id,  # Точка продаж shop обязательна
            "comment": "Проба пера",
            "customer": {
                "externalId": self.user.uuid,
                "name": self.user.first_name,  # Обязательно
                "phone": self.user.phone_number  # Обязательно
            },
            "datetime": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),  # Время на которое создается заказ. Обязательно
            "nomenclatures": self.nomenclatures,
            "delivery": {
                "addressFull": "г. Киров, ул. Советская, д. 166",  # Обязательно при доставкеself.address,
                'addressJSON': '{"City": "г. Киров", "Street": "ул. Советская", "HouseNum": "д. 166", "Entrance": "3", "Floor": "87", "AptNum": "47", "Address": "г. Киров, ул. Советская, д. 166, кв. 87"}',
                # Попробывать без него
                "paymentType": self.payment,  # Обязательно, варианты: «card», «online», «cash»
                "persons": 4,
                "isPickup": self.is_pickup  # False - доставка, True - самовызов
            }
        }
        payload_json = json.dumps(payload)
        response = requests.request("POST", url, headers=self.headers, data=payload_json)
        return response.json()

    def _get_orders_user(self):
        pass

    def get_address(self):
        pass

class SbisUser:
    def __init__(self):
        self.headers = {
            "Host": "online.sbis.ru",
            "Content-Type": "application/json-rpc; charset=utf-8",
            "Accept": "application/json-rpc",
        }
        self.sid = self.__login_sbis()

    def __login_sbis(self) -> str:
        return self.__invoke(
            "СБИС.Аутентифицировать",
            {"Параметр": {"Логин": settings.SBIS_LOGIN, "Пароль": settings.SBIS_PASSWORD}},
            None,
        )["result"]

    def __invoke(self, method: str, params: dict, auth_sid) -> dict:
        """
            Вызов метода
            """
        payload = {
            "jsonrpc": "2.0",
            "method": method,
            "params": params,
            "protocol": 2,
            "id": 0,
        }

        if auth_sid:
            self.headers["X-SBISSessionID"] = auth_sid
            url = "https://online.sbis.ru/service/"
        else:
            url = "https://online.sbis.ru/auth/service/"

        response = requests.post(url, headers=self.headers, data=json.dumps(payload)).json()
        return response

    def get_user_crm(self, phone: str) -> dict:
        params = {
            "client_data": {
                "d": {
                    'ContactData': {'d': [[phone, 'mobile_phone', None]],
                                    's': [{'n': 'Value', 't': 'Строка'},
                                          {'n': 'Type', 't': 'Строка'},
                                          {'n': 'Priority', 't': 'Строка'}]},
                },
                "s": {
                    'ContactData': 'Выборка',
                },
            },
            "options": None
        }
        user_crm = self.__invoke("CRMClients"
                                 ".GetCustomerByParams", params, self.sid)['result']['d']
        if user_crm:
            return {
                    "is_user":  True,
                    'birthday': user_crm['BirthDay'],
                    'phone': phone,
                    'uuid': user_crm['UUID'],
                    'name': user_crm['FirstName'],
                }
        return {
                    "is_user":  False,
                    'phone': phone,
                }

class CardUser:
    def __init__(self, phone: str, name: Optional[str] = None, birthday: Optional[date] = None,
                 gender: Optional[int] = None):
        self.phone = phone
        self.name = name
        self.birthday = birthday
        self.gender = gender
        self.headers = {'Content-Type': 'application/json',
                        'Cookie': 'CpsUserId=034d5129-2037-4ddb-8af8-63b894d90a5a; s3csu=daab; s3su=00a28c6f-00a2a4ae; s3tok-daab=; sid=00a28c6f-00a2a4ae-000d-1111111111111111'
                        }
        self.card_type_uuid = "16f7b6d9-7908-443e-82f8-d22e45c31508"

    def verify_phone(self) -> dict:
        url = "https://sabyget.ru/discount-cards/service/?x_version=23.5104-16"
        payload = json.dumps({
            "jsonrpc": "2.0",
            "protocol": 6,
            "method": "Questionary.VerifyPhone",
            "params": {
                "CardTypeUUID": self.card_type_uuid,
                "PhoneNumber": self.phone
            },
            "id": 1
        })

        response = requests.request("POST", url, headers=self.headers, data=payload).json()
        uniq_id = response['result']
        return uniq_id

    def get_or_create_user(self, uniq_id: str, code_user: int) -> dict:
        url = "https://sabyget.ru/discount-cards/service/?x_version=23.2155-19"
        payload = json.dumps({
            "jsonrpc": "2.0",
            "protocol": 6,
            "method": "Questionary.CreateCard",
            "params": {
                "CardTypeUUID": self.card_type_uuid,
                "Data": {
                    "d": [
                        "44507f6a-b617-4c5a-a0c4-0ed4a4ad1e58",
                        self.phone,
                        uniq_id,
                        code_user,
                        {
                            "d": [
                                self.name,
                                None,
                                None,
                                None
                            ],
                            "s": [
                                {
                                    "t": "Строка",
                                    "n": "FIO"
                                },
                                {
                                    "t": "Строка",
                                    "n": "Email"
                                },
                                {
                                    "t": "Дата",
                                    "n": "BirthDay"
                                },
                                {
                                    "t": "Число целое",
                                    "n": "Gender"
                                }
                            ],
                            "_type": "record",
                            "f": 1
                        },
                        True,
                        True,
                        "discount-cards.gpay.channel:6990a57a-7459-4db2-b575-0553a0e7dc31",
                        "discount-cards.card-image.channel.d50c3c9d-1c15-4de3-85c8-552137e4f617:10658990",
                        None
                    ],
                    "s": [
                        {
                            "t": "UUID",
                            "n": "CardTypeUUID"
                        },
                        {
                            "t": "Строка",
                            "n": "PhoneNumber"
                        },
                        {
                            "t": "Строка",
                            "n": "SMSTicket"
                        },
                        {
                            "t": "Строка",
                            "n": "SMSCode"
                        },
                        {
                            "t": "Запись",
                            "n": "ClientData"
                        },
                        {
                            "t": "Логическое",
                            "n": "WithWalletQR"
                        },
                        {
                            "t": "Логическое",
                            "n": "WithGPayQR"
                        },
                        {
                            "t": "Строка",
                            "n": "EventChannelGPay"
                        },
                        {
                            "t": "Строка",
                            "n": "EventChannelCardImage"
                        },
                        {
                            "t": "Число целое",
                            "n": "PreferredPass"
                        }
                    ],
                    "_type": "record",
                    "f": 0
                }
            },
            "id": 1
        })
        response = requests.post(url, data=payload, headers=self.headers)


"""
Leetcode 199 : Binary tree right side view

Here the first idea that came to my mind was traverse to right of the tree, if no right node, take left node and go on.
This should give the right most path in the tree.

But this is not enough. If there are paths that has more edges than right most path, that will be seen from right view as well.
So we need to start with right most path and progress to left most path. While doing it, if there is any path that has more edges than right most
path, then add the extra to the right most path and update it.

So I took the code for getting all the paths from GoG and put my logic in the end of path. The original code first gives the left most path in the tree,
and move to right. One clever thing I've done here was, I changed the order of traversal such that, I get the right most path, first.

"""

from typing import Optional, List

class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

class Solution:

    def rightSideView(self, root: Optional[TreeNode]) -> List[int]:

        def find_all_paths(stack, root):

            if root is None:
                return

            stack.append(root.val)
            if root.left == None and root.right == None:
                if self.result == None:
                    self.result = stack[:]
                else:
                    if len(stack) > len(self.result):
                        tmp = stack[:]
                        self.result += tmp[len(self.result):]
            find_all_paths(stack, root.right)
            find_all_paths(stack, root.left)
            stack.pop()
            return

        if root is None:
            return []

        self.result = None
        stack = []
        find_all_paths(stack, root)

        return self.result


# DEPRECATED

import os
import shutil

from jinja2 import Environment, PackageLoader, FileSystemLoader
from jinja2.exceptions import TemplateNotFound

from papier import helper

class NoStaticFilesWarning(RuntimeWarning):
    """ Warning for no static files """

class StaticFileExistWarning(RuntimeWarning):
    """ Warning for no static files """

class Renderer(object):
    def __init__(self):
        self._default_src = helper.path('template')
        self._default_env = Environment(loader = PackageLoader('papier', 'template'))
        self._basepath    = None
        self._environment = None
        print(self._default_src)

    def set_basepath(self, basepath):
        self._basepath    = basepath
        self._environment = Environment(loader = FileSystemLoader(self._basepath))

    def template(self, path):
        if self._environment:
            return self._environment.get_template(path)

        return self._default_env.get_template(path)

    def copy_static_files(self, destination_path):
        src_static_path = os.path.join(self._basepath or self._default_src)

        if not os.path.exists(src_static_path):
            raise NoStaticFilesWarning(src_static_path)

        if os.path.exists(destination_path):
            raise StaticFileExistWarning(destination_path)

        shutil.copytree(src_static_path, destination_path)


"""
Filename: odu_plot_densities.py
Authors: John Stachurski, Thomas J. Sargent

"""
import numpy as np
import matplotlib.pyplot as plt
from quantecon.models import SearchProblem

sp = SearchProblem(F_a=1, F_b=1, G_a=3, G_b=1.2)
grid = np.linspace(0, 2, 150)
fig, ax = plt.subplots()
ax.plot(grid, sp.f(grid), label=r'$f$', lw=2)
ax.plot(grid, sp.g(grid), label=r'$g$', lw=2)
ax.legend(loc=0)
plt.show()


#!/usr/bin/env python3
def get_gcd(*numbers):
    import math
    from functools import reduce
    return reduce(math.gcd, numbers)

def main():
    N = int(input())
    A = list(map(int, input().split()))
    ans = []
    l, r = [0], [0]

    for i in range(N):

        # l[i]: A1 ~ Ai-1の最大公約数
        l.append(get_gcd(l[i], A[i]))

        # r[i]: Ai ~ Anの最大公約数
        r.append(get_gcd(r[i], A[N-1-i]))

    r = r[::-1]
    for i in range(N):
        ans.append(get_gcd(l[i], r[i+1]))

    return max(ans)

print(main())

# 最大公約数の計算は順不同
# 最大公約数計算＋累積の考え方で計算量削減


import random
import matplotlib.pyplot as mlt
import numpy as nmpi

Elementlist = ["Ted", "Barney", "Marshall", "Lily", "Robin"]

for Breathtaking in range(2):
    print(random.choices(Elementlist))

print(random.randint(0, 9))

print(random.random())

print(random.uniform(9.5, 80.5))

print(round(random.uniform(33.33, 66.66),2))

print(random.SystemRandom().uniform(5, 10))

print(" ")

min_value = int(input("Write the minimum number "))
max_value = int(input("Write the maximum number "))
Guess = int(input("choose one random number between them "))

ypoints1 = []
xpoints = []

def binary_search(Guess, min_value, max_value):
    tries = 0
    found = False

    if max_value < min_value:
        print("Maximum value must be bigger than the minimum value")
    elif Guess < min_value or Guess > max_value:
        print("The number must be between min_value and max_value")
    else:
        while min_value < max_value and not found:
            tries += 1
            xpoints.append(tries)
            mid_value = (min_value + max_value) // 2
            ypoints1.append(mid_value)
            if mid_value == Guess:
                found = True
            else:
                if Guess < mid_value:
                    max_value = mid_value - 1
                else:
                    min_value = mid_value + 1

            print([(min_value, max_value), (mid_value, Guess), tries])

        print("The number is:", str(Guess))
        print("Tries:", str(tries))

print(binary_search(Guess, min_value, max_value))

print(ypoints1)
print(xpoints)

#xnumber = nmpi.array(xpoints)
#ynumber1 = nmpi.array(ypoints1)
#ypoint2 = nmpi.array([Guess])

xnumber=nmpi.array([12,10,20,50])
ynumber1=nmpi.array([18,20,26,57])

print(xnumber)
print(ynumber1)

mlt.plot(xnumber,ynumber1)
mlt.show()

print(" ")

Max_number = 100
Good_number = random.randrange(Max_number)
Guess = int(input("Guess the number"))
number_of_Guesses = 0
while Guess != Good_number:
    if Guess < Good_number:
        print("Higher")
        Guess = int(input("Guess again: "))
        number_of_Guesses += 1
    elif Guess > Good_number:
        print("Lower")
        Guess = int(input("Guess again: "))
        number_of_Guesses += 1
    if Guess == Good_number:
        print("You win")
    if number_of_Guesses == 10:
        print("Game over")

print(" ")

import math

lowest_number = int(input("Enter Lower bound:- "))

highest_number = int(input("Enter Upper bound:- "))

x = random.randint(lowest_number, highest_number)
print("\n\tYou've only ",
      round(math.log(highest_number - lowest_number + 1, 2)),
      " chances to guess the integer!\n")

count = 0

while count < math.log(highest_number - lowest_number + 1, 2):
    count += 1

    guess = int(input("Guess a number:- "))

    if x == guess:
        print("Congratulations you did it in ",
              count, " try")
        break
    elif x > guess:
        print("You guessed too small!")
    elif x < guess:
        print("You Guessed too high!")

if count >= math.log(highest_number - lowest_number + 1, 2):
    print("\nThe number is %d" % x)
    print("\tBetter Luck Next time!")

#Random

import random

Integerslist = []

for Great in range(0, 10):
    Numbers1 = random.randint(30, 50)
    Integerslist.append(Numbers1)

print(Integerslist)

Floatlist = []

for Awesome in range(0, 5):
    Numbers2 = random.uniform(1, 10)
    Floatlist.append(Numbers2)

print(Floatlist)

Gaussianlist = []

for Astounding in range(0, 7):
    Numbers3 = random.gauss(50, 25)
    Gaussianlist.append(Numbers3)

print(Gaussianlist)

Elementlist = ["Ted", "Barney", "Marshall", "Lily", "Robin"]

for Breathtaking in range(2):
    print(random.choices(Elementlist))

#Break

Number = 0

while Number <= 100:
    Number += 1
    print(Number)
    if Number > 49:
        break

#Try and Except

Integer = int(input("Skriv ett number "))
String = str(input("Skriv ett ord "))

try:
    print(Integer, String)
except:
    print("What is wrong with you! You can not add an integer to a string ")
try:
    print(12/0)
except:
    print("A number can not be divided by zero ")


import cv2

def thresh_cvt(img,res=None):
    num,_,stats,_ = cv2.connectedComponentsWithStats(img, connectivity=4, ltype=None)
    stats_cvt = stats[1:,:]
    start = []
    end = []
    img_list = []
    title_list = []

    for i in range(0,num-1):
        start.append([stats_cvt[i][1],stats_cvt[i][0]])
        end.append([stats_cvt[i][1]+stats_cvt[i][3]+1,stats_cvt[i][0]+stats_cvt[i][2]+1])

    for i in range(num-1):
        img_list.append(img[start[i][0]:end[i][0],start[i][1]:end[i][1]])
        title_list.append('area_%i'%i)

    return start,end,img_list,title_list,num


from django.urls import path

from . import views

app_name = 'schemas'
urlpatterns = [
    path('', views.IndexView.as_view(), name='index'),
    path('new/schema/', views.CreateSchemaView.as_view(), name='create_schema'),
    path('new/column/', views.ColumnCreateView.as_view(), name='create_column'),
    path('column/delete/<int:column_id>/', views.ColumnDeleteView.as_view(), name='delete_column'),
    path('schema/<int:schema_id>/', views.SchemaView.as_view(), name='schema_detail'),
    path('schema/delete/<int:schema_id>', views.DeleteSchemaView.as_view(), name='delete_schema'),
    path('schema/get_file/<int:dataset_id>/', views.FileDownloadView.as_view(), name='get_file'),
    path('tasks/<str:task_id>/', views.TaskStatusView.as_view(), name='task_status')
]


import configparser
from datetime import datetime
import os
from pyspark.sql import SparkSession
from pyspark.sql.functions import udf, col
from pyspark.sql.functions import year, month, dayofmonth, hour, weekofyear, date_format
from pyspark.sql.types import StructType as Stt, StructField as Sfd, DoubleType as Dbl, StringType as Str, IntegerType as Int, TimestampType as Tst

config = configparser.ConfigParser()
config.read('dl.cfg')

os.environ['AWS_ACCESS_KEY_ID']=config['Default']['AWS_ACCESS_KEY_ID']
os.environ['AWS_SECRET_ACCESS_KEY']=config['Default']['AWS_SECRET_ACCESS_KEY']

def create_spark_session():
    """
    Initializes a Spark session or retrieves one if already present
    """
    spark = SparkSession \
        .builder \
        .config("spark.jars.packages", "org.apache.hadoop:hadoop-aws:2.7.0") \
        .getOrCreate()
    return spark

def process_song_data(spark, input_data, output_data):
    """
    Loads the song_data JSONs from the given S3, processes them by extracting the data into
    song and artist tables in parquet format in the target output folder

    Paramters
    spark: spark session
    input_data: path to location of target json files
    output_data: path to S3 bucket where processed data will be stored
    """
    # get filepath to song data file
    song_data = os.path.join(input_data, "song_data/*/*/*/*.json")

    song_schema = Stt([
        Sfd("artist_id", Str()),
        Sfd("artist_latitude", Dbl()),
        Sfd("artist_location", Str()),
        Sfd("artist_longitude", Dbl()),
        Sfd("artist_name", Str()),
        Sfd("duration", Dbl()),
        Sfd("num_songs", Int()),
        Sfd("title", Str()),
        Sfd("year", Int())
    ])

    # read song data file
    df = spark.read.json(song_data, schema = song_schema)

    # extract columns to create songs table
    songs_cols = ["title", "artist_id", "year", "duration"]
    songs_table = df.select(songs_cols).dropDuplicates().withColumn("song_id", monotonically_increasing_id())

    # write songs table to parquet files partitioned by year and artist
    songs_table.write.partitionBy("year", "artist_id").parquet(output_data + "songs")

    # extract columns to create artists table
    artists_cols = ["artist_id", "artist_name", "artist_location", "artist_latitude", "artist_longitude"]
    artists_table = df.selectExpr(artists_cols).dropDuplicates()

    # write artists table to parquet files
    artists_table.write.parquet(output_data + "artists")

def process_log_data(spark, input_data, output_data):
    """
    Loads the log_data JSONs from the given S3, processes them by extracting the data into
    users, songplays and times tables in parquet format in the target output folder

    Paramters
    spark: spark session
    input_data: path to location of target json files
    output_data: path to S3 bucket where processed data will be stored
    """
    # get filepath to log data file
    log_data = os.path.join(input_data, "log_data/*/*/*.json")

    # read log data file
    logs_df = spark.read.json(log_data)

    # filter by actions for song plays
    logs_df = logs_df.filter(logs_df.page == "NextSong")

    # extract columns for users table
    users_cols = ["userId as user_id", "firstName as first_name", "lastName as last_name", "gender", "level"]
    users_table = logs_df.selectExpr(users_cols).dropDuplicates()

    # write users table to parquet files
    users_table.write.parquet(output_data + "users")

    # create timestamp column from original timestamp column
    get_timestamp = udf(lambda x: x / 1000, Tst())
    logs_df = logs_df.withColumn("timestamp", get_timestamp(logs_df.timestamp))

    # create datetime column from original timestamp column
    get_datetime = udf(lambda x: datetime.fromtimestamp(x), Tst())
    logs_df = logs_df.withColumn("start_time", get_datetime(logs_df.timestamp))

    # extract columns to create time table
    times_table = logs_df.withColumn("hour", hour("start_time")) \
                         .withColumn("day", dayofmonth("start_time")) \
                         .withColumn("week", weekofyear("start_time")) \
                         .withColumn("month", month("start_time")) \
                         .withColumn("year", year("start_time")) \
                         .withColumn("weekday", dayofweek("start_time")) \

    times_table = times_table.select("start_time", "hour", "day", "week", "month", "year", "weekday")

    # write time table to parquet files partitioned by year and month
    times_table.write.partitionBy("year", "month").parquet(output_data + "times")

    # read in song data to use for songplays table
    songs_df = spark.read.parquet(output_data + "songs/*/*/*")
    artists_df = spark.read.parquet(output_data + "artists")

    songs_logs = logs_df.join(songs_df, (logs_df.song == songs_df.title))
    artists_songs_logs = songs_logs.join(artists_df, (songs_logs.artist == artists_df.artist_name))

    # extract columns from joined song and log datasets to create songplays table
    songplays_table = artists_songs_logs.join(
                      times_table, artists_songs_logs.timestamp == times_table.start_time, "left"
                      ).drop(artists_songs_logs.year)

    songplays_table = songplays_table.select(
        col("start_time"),
        col("userId").alias("user_id"),
        col("level"),
        col("song_id"),
        col("artist_id"),
        col("sessionId").alias("session_id"),
        col("location"),
        col("userAgent").alias("user_agent"),
        col("year"),
        col("month")
    ).repartition("year", "month")

    # write songplays table to parquet files partitioned by year and month
    songplays_table.write.partitionBy("year", "month").parquet(output_data + "songplays")

def main():
    spark = create_spark_session()
    input_data = "s3a://udacity-dend/"
    output_data = ""

    process_song_data(spark, input_data, output_data)
    process_log_data(spark, input_data, output_data)

if __name__ == "__main__":
    main()


import torch.nn as nn
import torch
import onion.util as util
import torch.nn.functional as F

class StackedGRU(nn.Module):

    def __init__(self, input_size, hidden_size, num_layers, residual=False, bidirectional=False, **kwargs):
        super(StackedGRU, self).__init__()
        assert num_layers > 0
        util.autoassign(locals())
        self.bottom = nn.GRU(input_size, hidden_size, 1, bidirectional=bidirectional, **kwargs)
        self.layers = nn.ModuleList()
        if bidirectional:
            self.downscale = nn.Linear(hidden_size * 2, hidden_size)

        for i in range(num_layers-1):
            layer = nn.GRU(hidden_size, hidden_size, 1, bidirectional=self.bidirectional, **kwargs)
            self.layers.append(layer)

    def forward(self, x, h0):
        j = 2 if self.bidirectional else 1
        out, _ = self.bottom(x, h0[0:1,:,:])
        out = self.proj(out)
        for i in range(self.num_layers-1):
            out =  self.Residual(self.layers[i], out, h0[i+1:i+2,:,:])
        return out

    def proj(self, x):
        if self.bidirectional:
            return self.downscale(x)
        else:
            return x

    def Residual(self, f, x, *args):
        fx, _ = f(x, *args)
        #print(f)
        #print(" x:", x.size())
        #print("fx:", fx.size())
        px = self.proj(fx)
        #print("px:", px.size())
        if self.residual:
            return x + px
        else:
            return px


from multiprocessing.connection import wait
import pyttsx3
import time
from pygame import mixer, _sdl2
import sounddevice as sd

while True:
    #get the text needed and convert it to mp3
    text = input('Enter your text: ')
    print('saying: ' + text)
    engine = pyttsx3.init()
    engine.save_to_file(text, 'text.mp3')
    engine.runAndWait()
    print('\n')

    #play the mp3 over the microphone
    mixer.init()

    print("Inputs:", _sdl2.audio.get_audio_device_names(True))
    print("Outputs:", _sdl2.audio.get_audio_device_names(False))

    mixer.init(devicename = 'CABLE-A Input (VB-Audio Virtual Cable A)') # Initialize it with the correct device
    mixer.music.load("text.mp3") # Load the mp3
    mixer.music.play() # Play it

    while mixer.music.get_busy():  # wait for music to finish playing
        time.sleep(1)


'''
문) breast_cancer 데이터셋을 이용하여 다음과 같이 keras 모델을 생성하시오.
  조건1> keras layer
       L1 =  30 x 64
       L2 =  64 x 32
       L3 =  32 x 2
  조건2> optimizer = 'adam',
  조건3> loss = 'binary_crossentropy'
  조건4> metrics = 'accuracy'
  조건5> epochs = 300
'''

from sklearn.datasets import load_breast_cancer # data set
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import minmax_scale # 정규화
from tensorflow.keras.utils import to_categorical # one hot encoding
from tensorflow.keras import Sequential # model 생성
from tensorflow.keras.layers import Dense # DNN layer

# 1. breast_cancer data load
cancer = load_breast_cancer()

x_data = cancer.data
y_data = cancer.target
print(x_data.shape) # (569, 30) : matrix
print(y_data.shape) # (569,) : vector

# x_data : 정규화
x_data = minmax_scale(x_data) # 0~1

# y변수 one-hot-encoding
y_one_hot = to_categorical(y_data)
y_one_hot.shape # (569, 2)

# 2. 공급 data 생성 : 훈련용, 검증용
x_train, x_val, y_train, y_val = train_test_split(
    x_data, y_one_hot, test_size = 0.3)

# 3. keras model
model=Sequential()

# 4. DNN model layer 구축
model.add(Dense(units=64,input_shape=(30,), activation = 'relu'))
model.add(Dense(units=32, activation = 'relu'))
model.add(Dense(units=2, activation = 'sigmoid'))

# 5. model compile
model.compile(optimizer='adam',
              loss='binary_crossentropy',
              metrics=['accuracy'])

# 6. model training : training dataset
model.fit(x=x_train,y=y_train, # 훈련셋
          epochs=300, # 반복학습
          verbose=1, # 출력여부
          validation_data=(x_val, y_val)) # 검증셋

# 7. model evaluation : validation dataset
print('='*30)
print('model evalution')
model.evaluate(x=x_val, y=y_val)


# coding: utf-8

"""
    APIs OpenData do Open Banking Brasil

    As APIs descritas neste documento são referentes as APIs da fase OpenData do Open Banking Brasil.  # noqa: E501

    OpenAPI spec version: 1.0.0-rc5.2
    Contact: apiteam@swagger.io
    Generated by: https://github.com/swagger-api/swagger-codegen.git
"""

import pprint
import re  # noqa: F401

import six

class Branch(object):
    """NOTE: This class is auto generated by the swagger code generator program.

    Do not edit the class manually.
    """
    """
    Attributes:
      swagger_types (dict): The key is attribute name
                            and the value is attribute type.
      attribute_map (dict): The key is attribute name
                            and the value is json key in definition.
    """
    swagger_types = {
        'identification': 'BranchIdentification',
        'postal_addresses': 'BranchPostalAddress',
        'availability': 'BranchAvailability',
        'phones': 'list[BranchPhone]',
        'services': 'list[BranchService]'
    }

    attribute_map = {
        'identification': 'identification',
        'postal_addresses': 'postalAddresses',
        'availability': 'availability',
        'phones': 'phones',
        'services': 'services'
    }

    def __init__(self, identification=None, postal_addresses=None, availability=None, phones=None, services=None):  # noqa: E501
        """Branch - a model defined in Swagger"""  # noqa: E501
        self._identification = None
        self._postal_addresses = None
        self._availability = None
        self._phones = None
        self._services = None
        self.discriminator = None
        self.identification = identification
        if postal_addresses is not None:
            self.postal_addresses = postal_addresses
        self.availability = availability
        if phones is not None:
            self.phones = phones
        self.services = services

    @property
    def identification(self):
        """Gets the identification of this Branch.  # noqa: E501

        :return: The identification of this Branch.  # noqa: E501
        :rtype: BranchIdentification
        """
        return self._identification

    @identification.setter
    def identification(self, identification):
        """Sets the identification of this Branch.

        :param identification: The identification of this Branch.  # noqa: E501
        :type: BranchIdentification
        """
        if identification is None:
            raise ValueError("Invalid value for `identification`, must not be `None`")  # noqa: E501

        self._identification = identification

    @property
    def postal_addresses(self):
        """Gets the postal_addresses of this Branch.  # noqa: E501

        :return: The postal_addresses of this Branch.  # noqa: E501
        :rtype: BranchPostalAddress
        """
        return self._postal_addresses

    @postal_addresses.setter
    def postal_addresses(self, postal_addresses):
        """Sets the postal_addresses of this Branch.

        :param postal_addresses: The postal_addresses of this Branch.  # noqa: E501
        :type: BranchPostalAddress
        """

        self._postal_addresses = postal_addresses

    @property
    def availability(self):
        """Gets the availability of this Branch.  # noqa: E501

        :return: The availability of this Branch.  # noqa: E501
        :rtype: BranchAvailability
        """
        return self._availability

    @availability.setter
    def availability(self, availability):
        """Sets the availability of this Branch.

        :param availability: The availability of this Branch.  # noqa: E501
        :type: BranchAvailability
        """
        if availability is None:
            raise ValueError("Invalid value for `availability`, must not be `None`")  # noqa: E501

        self._availability = availability

    @property
    def phones(self):
        """Gets the phones of this Branch.  # noqa: E501

        Lista de telefones da Dependência  # noqa: E501

        :return: The phones of this Branch.  # noqa: E501
        :rtype: list[BranchPhone]
        """
        return self._phones

    @phones.setter
    def phones(self, phones):
        """Sets the phones of this Branch.

        Lista de telefones da Dependência  # noqa: E501

        :param phones: The phones of this Branch.  # noqa: E501
        :type: list[BranchPhone]
        """

        self._phones = phones

    @property
    def services(self):
        """Gets the services of this Branch.  # noqa: E501

        Traz a relação de serviços disponbilizados pelo Canal de Atendimento  # noqa: E501

        :return: The services of this Branch.  # noqa: E501
        :rtype: list[BranchService]
        """
        return self._services

    @services.setter
    def services(self, services):
        """Sets the services of this Branch.

        Traz a relação de serviços disponbilizados pelo Canal de Atendimento  # noqa: E501

        :param services: The services of this Branch.  # noqa: E501
        :type: list[BranchService]
        """
        if services is None:
            raise ValueError("Invalid value for `services`, must not be `None`")  # noqa: E501

        self._services = services

    def to_dict(self):
        """Returns the model properties as a dict"""
        result = {}

        for attr, _ in six.iteritems(self.swagger_types):
            value = getattr(self, attr)
            if isinstance(value, list):
                result[attr] = list(map(
                    lambda x: x.to_dict() if hasattr(x, "to_dict") else x,
                    value
                ))
            elif hasattr(value, "to_dict"):
                result[attr] = value.to_dict()
            elif isinstance(value, dict):
                result[attr] = dict(map(
                    lambda item: (item[0], item[1].to_dict())
                    if hasattr(item[1], "to_dict") else item,
                    value.items()
                ))
            else:
                result[attr] = value
        if issubclass(Branch, dict):
            for key, value in self.items():
                result[key] = value

        return result

    def to_str(self):
        """Returns the string representation of the model"""
        return pprint.pformat(self.to_dict())

    def __repr__(self):
        """For `print` and `pprint`"""
        return self.to_str()

    def __eq__(self, other):
        """Returns true if both objects are equal"""
        if not isinstance(other, Branch):
            return False

        return self.__dict__ == other.__dict__

    def __ne__(self, other):
        """Returns true if both objects are not equal"""
        return not self == other


from enum import Enum
import numpy as np
import scipy.sparse as sparse
import scipy.sparse.linalg

from .geometrizor import interpolate
from .converter import formulation_parser
from .utils.logger import Logger
from .utils.wrappers import typechecker, funcunpacker
from .utils.misc import to_3args, to_func, is_number

class VariationalTerm(object):

    DPHI = {
        'P1': { 0: np.matrix([[-1], [-1]]),
             1: np.matrix([[1], [0]]),
             2: np.matrix([[0], [1]]) },
        'P2': { 0: np.matrix([[-1], [-1]]),
             1: np.matrix([[1], [0]]),
             2: np.matrix([[0], [1]]) }
    }

    def __init__(self, type, domain, func=None):
        self.type   = type
        # check for correct variational term type, or exit with error
        if not self.type in ['MASS', 'RIGIDITY', 'MIXED', 'RHS']:
            Logger.serror('Unknown Variational Term type: "{}"'.format(self.type))

        if self.type == 'MIXED':
            self.func = np.array([1.,1.]) if func is None else func
        else:
            if self.type == 'RHS': f = to_func(0. if func is None else func)
            else: f = to_func(1. if func is None else func)
            # check for number of input arguments: rematch to 3d space
            # (general case)
            self.func = to_3args(f)
        self.domain = domain

    def __str__(self):
        return 'Variational Term [domain: "{}"]\tType: "{}"\n'.format(self.domain.name,
                self.type)

    def assemble(self):
        """Assembles the matrix or vector corresponding to the Variational Term."""
        Nv = self.domain.Nv # domain size (number of vertices)
        f  = funcunpacker(self.func)

        def _mass1d(seg):
            row = list(); col = list(); A = list()
            n1 = self.domain.nodes[seg[0]]
            n2 = self.domain.nodes[seg[1]]
            length = np.linalg.norm(n2 - n1)
            Me = length/6.*np.matrix([[2.,1.], [1.,2.]])
            for i in range(2):
                I = seg[i]
                for j in range(2):
                    J = seg[j]
                    row.append((I))
                    col.append((J))
                    A.append((f(self.domain.nodes[J]) * Me[i,j]))
            return row, col, A
        def _mass2d(p, det):
            row = list(); col = list(); A = list()
            Me  = self.domain.mats_mass[p]
            for i in range(self.domain.tricount):
                I = self.domain.loc2glob(p,i)
                for j in range(self.domain.tricount):
                    J = self.domain.loc2glob(p,j)
                    row.append((I))
                    col.append((J))
                    A.append((f(self.domain.nodes[J])*Me[i,j]))
            return row, col, A

        def _rigidity1d(seg):
            row = list(); col = list(); A = list()
            n1 = self.domain.nodes[seg[0]]
            n2 = self.domain.nodes[seg[1]]
            length = np.linalg.norm(n2 - n1)
            De = length*np.matrix([[1.,-1.], [-1.,1.]])
            for i in range(2):
                I = seg[i]
                for j in range(2):
                    J = seg[j]
                    row.append((I))
                    col.append((J))
                    A.append((f(self.domain.nodes[J]) * De[i,j]))
            return row, col, A
        def _rigidity2d(p, det):
            row = list(); col = list(); A = list()
            De  = self.domain.mats_rigidity[p]
            for i in range(self.domain.tricount):
                I = self.domain.loc2glob(p,i)
                for j in range(self.domain.tricount):
                    J = self.domain.loc2glob(p,j)
                    row.append((I))
                    col.append((J))
                    A.append((f(self.domain.nodes[J]) * De[i,j]))
            return row, col, A

        def _mixed2d(p, det):
            row = list(); col = list(); A = list()
            T = self.domain.transfer_vector(p)
            w = self.func
            for i in range(3):
                I = self.domain.loc2glob(p,i)
                for j in range(3):
                    J = self.domain.loc2glob(p,j)
                    row.append((I))
                    col.append((J))
                    A.append((float(w.dot(T[i]))/6.))
            return row, col, A

        def _rhs1d(seg): return interpolate('edge', self.domain, f, seg)
        def _rhs2d(p):   return interpolate('surface', self.domain, f, p)

        if self.type == 'RHS':
            B = np.zeros((Nv,1))
            if self.domain.is_border:
                for seg in self.domain.edges: B += _rhs1d(seg)
            else:
                for p in range(self.domain.Nt): B += _rhs2d(p)
            return B
        else:
            row = list(); col = list(); A = list()
            if self.domain.is_border:
                for seg in self.domain.edges:
                    if self.type == 'MASS':
                        r, c, coeff = _mass1d(seg)
                    elif self.type == 'RIGIDITY':
                        r, c, coeff = _rigidity1d(seg)
                    else:
                        r, c, coeff = [], [], []
                    row.extend(r)
                    col.extend(c)
                    A.extend(coeff)
            else:
                # get all triangles determinants
                det_tri = self.domain.det_triangles
                for p in range(self.domain.Nt):
                    if self.type == 'MASS':
                        r, c, coeff = _mass2d(p, det_tri[p])
                    elif self.type == 'RIGIDITY':
                        r, c, coeff = _rigidity2d(p, det_tri[p])
                    else:
                        r, c, coeff = _mixed2d(p, det_tri[p])
                    row.extend(r)
                    col.extend(c)
                    A.extend(coeff)
            return sparse.coo_matrix((A, (row, col)), shape=(Nv,Nv)).tocsr()

class VariationalFormulation(object):

    """Object that represents a Variational Formulation, i.e. a 'nice'
    equivalent of a PDE with average quantities rather than point to point
    conditions."""

    def __init__(self, domain, a, l, dirichlet=None):
        for term in a:
            if term[0] == 'RHS':
                Logger.serror('You cannot define a right-hand side in the '
                              'left part of your variational formulation!')
        self.domain = domain
        self.a      = [VariationalTerm(*term) for term in a]
        self.l      = []
        for term in l:  # detailed unpacking for Python 2.x compatibility
            l_terms = ['RHS']
            l_terms = l_terms + [t for t in term]
            self.l.append(VariationalTerm(*l_terms))

        self.dirichlet = []
        if dirichlet is not None:
            if not isinstance(dirichlet, list): dirichlet = [dirichlet]
            for d in dirichlet:
                if len(d) < 2 or not (callable(d[-1]) or is_number(d[-1])):
                    Logger.serror('Invalid Dirichlet condition: {}.'.format(d))
                t = to_func(d[-1])
                for s in d[:-1]:
                    if self.domain(s) == self.domain or not self.domain(s).is_border:
                        Logger.serror('Invalid Dirichlet condition: "{}" is not '
                                      'a border of your domain!'.format(s))
                    self.dirichlet.append((s,t))

        self._precompute_domain()

    def __str__(self):
        """Overrides the print representation of this instance to provide
        useful information about it."""
        s = 'Variational Formulation:\n' + '=' * 24 + '\n'
        s += 'Left-hand side:\n' + '-' * 15 + '\n'
        for a in self.a: s += str(a)
        s += '\nRight-hand side:\n' + '-' * 16 + '\n'
        for l in self.l: s += str(l)
        if len(self.dirichlet) > 0:
            s += '\nDirichlet conditions on border(s): '
            s += ', '.join([ds for ds, _ in self.dirichlet])
            s += '\n'
        return s

    def _precompute_domain(self):
        """Precomputes mass and/or rigidity matrices of the associated
        DiscreteDomain instance, depending on the ones necessary for the problem
        to solve."""
        if not self.domain.mass_mtr_prepared:
            if len([term for term in self.a if term.type == 'MASS']) > 0:
                self.domain.precompute_mtr_mass()
        if not self.domain.rig_mtr_prepared:
            if len([term for term in self.a if term.type == 'RIGIDITY']) > 0:
                self.domain.precompute_mtr_rigidity()

        Logger.slog('Prepared Discrete Domain instance for problem to solve.',
                    stackoffset=2)

    @classmethod
    @typechecker((type,str,dict), None)
    def from_str(cls, s, loc, **kwargs):
        """Constructor to build a VariationalFormulation instance from a
        formatted string (by parsing).

        Parameters
        ----------
        s : str
            String to parse and deconstruct.
        loc : dict()
            Available local variables.
        """
        domain, a, l, dirichlet = formulation_parser(s, loc)
        return cls(domain, a, l, dirichlet=dirichlet, **kwargs)

    def split_dirichlet(self):
        """Returns the vertices indexes foreach Dirichlet condition of the
        problem instance."""
        masks = []
        for border, value in self.dirichlet:
            subdomain = self.domain(border)
            mask = np.zeros(subdomain.Nv, dtype=bool)
            edges = []
            for n0, n1 in subdomain.edges: edges.extend([n0,n1])
            mask[np.unique(edges)] = True
            masks.append((mask, value))
        return masks

    def mass_matrix(self, splitted=False):
        """Computes the mass matrix of the Variational Formulation."""
        M = sum([term.assemble() for term in self.a if term.type == 'MASS'])
        if not splitted: return M
        else:
            if len(self.dirichlet) > 1:
                Logger.serror('Mass matrix can only be splitted for a single '
                              'Dirichlet condition, for now!')
            # split 'interior' and 'Dirichlet' nodes
            mask = self.get_dirichlet_idx()[0][0]
            M_int = M[np.ix_(~mask,~mask)]
            M_dir = M[np.ix_(~mask, mask)]
            return M_int, M_dir, mask

    def rigidity_matrix(self, splitted=False):
        """Computes the rigidity matrix of the Variational Formulation."""
        D = sum([term.assemble() for term in self.a if term.type == 'RIGIDITY'])
        if not splitted: return D
        else:
            if len(self.dirichlet) > 1:
                Logger.serror('Rigidity matrix can only be splitted for a single '
                              'Dirichlet condition, for now!')
            # split 'interior' and 'Dirichlet' nodes
            mask = self.get_dirichlet_idx()[0][0]
            D_int = D[np.ix_(~mask,~mask)]
            D_dir = D[np.ix_(~mask, mask)]
            return D_int, D_dir, mask

    def mixed_matrix(self):
        """Computes the mixed matrix of the Variational Formulation."""
        return sum([term.assemble() for term in self.a if term.type == 'MIXED'])

    def rhs(self):
        """Computes the right-hand side (vector) of the Variational Formulation."""
        return sum([term.assemble() for term in self.l if term.type == 'RHS'])

    def apply_dirichlet(self, A, B=None):
        d = A.diagonal()
        v = d.sum() / A.shape[0]
        if B is not None:
            # .. check for complex equations: cast to complex if necessary
            A_is_complex = np.any(np.iscomplex(A.toarray()))
            B_is_complex = np.any(np.iscomplex(B))
            if A_is_complex or B_is_complex:
                v *= 1j
                if A_is_complex and not B_is_complex: B = B.astype(np.complex)
            for border, value in self.dirichlet:
                subdomain = self.domain(border)
                if subdomain.fe_type == 'P1':
                    for I, J in subdomain.edges:
                        d[I] = v; A[I,:] = 0.
                        d[J] = v; A[J,:] = 0.
                        s1x, s1y, _ = subdomain.nodes[I]
                        s2x, s2y, _ = subdomain.nodes[J]
                        B[I] = value(s1x,s1y)*v
                        B[J] = value(s2x,s2y)*v
                elif subdomain.fe_type == 'P2':
                    for I, J, K in subdomain.edges:
                        d[I] = v; A[I,:] = 0.
                        d[J] = v; A[J,:] = 0.
                        d[K] = v; A[K,:] = 0.
                        s1x, s1y, _     = subdomain.nodes[I]
                        s2x, s2y, _     = subdomain.nodes[J]
                        sMidx, sMidy, _ = subdomain.nodes[K]
                        B[I] = value(s1x,s1y)*v
                        B[J] = value(s2x,s2y)*v
                        B[K] = value(sMidx,sMidy)*v
                elif subdomain.fe_type == 'P3':
                    for I, J, K, L in subdomain.edges:
                        d[I] = v; A[I,:] = 0.
                        d[J] = v; A[J,:] = 0.
                        d[K] = v; A[K,:] = 0.
                        d[L] = v; A[L,:] = 0.
                        s1x, s1y, _ = subdomain.nodes[I]
                        s2x, s2y, _ = subdomain.nodes[J]
                        s3x, s3y, _ = subdomain.nodes[K]
                        s4x, s4y, _ = subdomain.nodes[L]
                        B[I] = value(s1x,s1y)*v
                        B[J] = value(s2x,s2y)*v
                        B[K] = value(s3x,s3y)*v
                        B[L] = value(s4x,s4y)*v
        else:
            # .. check for complex equations: cast to complex if necessary
            A_is_complex = np.any(np.iscomplex(A.toarray()))
            if A_is_complex: v *= 1j
            for border, value in self.dirichlet:
                subdomain = self.domain(border)
                if subdomain.fe_type == 'P1':
                    for I, J in subdomain.edges:
                        d[I] = v; A[I,:] = 0.
                        d[J] = v; A[J,:] = 0.
                elif subdomain.fe_type == 'P2':
                    for I, J, K in subdomain.edges:
                        d[I] = v; A[I,:] = 0.
                        d[J] = v; A[J,:] = 0.
                        d[K] = v; A[K,:] = 0.
                elif subdomain.fe_type == 'P3':
                    for I, J, K, L in subdomain.edges:
                        d[I] = v; A[I,:] = 0.
                        d[J] = v; A[J,:] = 0.
                        d[K] = v; A[K,:] = 0.
                        d[L] = v; A[L,:] = 0.
        A.setdiag(d)
        if B is not None: return A, B
        else: return A

    def assemble_matrices(self):
        """Assembles the matrix and right-hand side of the linear system for
        this Variational Formulation instance."""
        # compute system matrices and right-hand side
        A = (self.mass_matrix() + self.rigidity_matrix() + self.mixed_matrix()).tolil()
        B = self.rhs()
        # if necessary: set Dirichlet border conditions
        if len(self.dirichlet) > 0:
            A, B = self.apply_dirichlet(A, B)

        return A.tocsr(), B

    def solve(self, info=True):
        """Solves the linear system associated with the problem from its matrix
        and right-hand side.

        Parameters
        ----------
        info : bool, optional
            If true, some information is logged at the end of the computation.
        """
        A, B = self.assemble_matrices()
        u    = sparse.linalg.spsolve(A, B)
        if u.dtype == np.complex128:
            uabs = np.abs(u)
            min, max = np.min(uabs), np.max(uabs)
        else:
            min, max = np.min(u), np.max(u)
        if info:
            Logger.slog('Solved equation: min = {}\tmax = {}'.format(min, max))
        return u

class FiniteDifferenceScheme(object):

    """
    Abstract top class to implement a finite difference scheme.
    Warning: it does not implement any real computation!
    --------
    """

    def __init__(self, initial_sol, dt, **kwargs):
        self.init_u = initial_sol
        self.dt = dt

    def solve(self, nb_iters, all_solutions=False):
        """Computes a given number of iterations with a finite difference scheme.

        Parameters
        ----------
        nb_iters : int
            Number of iterations to compute.
        all_solutions : bool, optional
            If true, all intermediate results are recorded and returned. Else,
            only the last solution is returned.
        """
        u = np.copy(self.init_u)
        solutions = [u]
        next_u = np.zeros_like(u)
        for i in range(nb_iters):
            # compute new solution vector
            next_u = self.iter(u, i)
            # record solution
            u = np.copy(next_u)
            solutions.append(u)

        if u.dtype == np.complex128:
            uabs = np.abs(u)
            min, max = np.min(uabs), np.max(uabs)
        else:
            min, max = np.min(u), np.max(u)
        Logger.slog('Solved {} iterations: min = {}\tmax = {}'.format(nb_iters, min, max))

        if all_solutions: return solutions
        else: return solutions[-1]

    def iter(self, u, i):
        """Computes a new solution vector from the previous one.

        Parameters
        ----------
        u : numpy.ndarray
            Current solution vector.
        i : int
            Index of the current timestep.
        """
        raise NotImplementedError('Define a specific iteration function!')

class ThetaScheme(FiniteDifferenceScheme):

    """Parent class to implement a theta-scheme."""

    def __init__(self, initial_sol, domain, dt, f, dirichlet, theta, **kwargs):
        super().__init__(initial_sol, dt, **kwargs)
        self.domain = domain
        self.f      = f

        system = VariationalFormulation(domain,
            [('RIGIDITY', domain), ('MASS', domain)], [(domain, f)],
            dirichlet=dirichlet)

        self.border_idx = system.split_dirichlet()
        M, D = system.mass_matrix(), system.rigidity_matrix()

        self.mtr_left  = sparse.linalg.inv((M + theta*dt*D).tocsc())
        self.mtr_right = M - (1-theta)*dt*D
        self.coeff_f1  = theta*dt
        self.coeff_f2  = (1-theta)*dt

    def iter(self, u, i):
        # compute right-hand side for current time
        cur_t, next_t = i * self.dt, (i+1) * self.dt
        f_cur  = lambda x, y: self.f(x, y, cur_t)
        F_cur  = f_cur(*self.domain.points).reshape((self.domain.Nv, 1))
        f_next = lambda x, y: self.f(x, y, next_t)
        F_next = f_next(*self.domain.points).reshape((self.domain.Nv, 1))

        next_u = np.empty_like(u)
        next_u = (self.mtr_left*self.mtr_right).dot(u) \
                 + self.coeff_f1 * F_next + self.coeff_f2 * F_cur

        for dir_idx, dir_val in self.border_idx:
            next_u[dir_idx] = dir_val(0.,0.) # the value is constant...

        return next_u

class EulerExplicit(ThetaScheme):

    """Class to implement the forward/explicit Euler scheme (theta-scheme,
    with theta = 0)."""

    def __init__(self, init_sol, dom, dt, f, dirichlet=None, **kwargs):
        super().__init__(init_sol, dom, dt, f, dirichlet, theta=0, **kwargs)

class EulerImplicit(ThetaScheme):

    """Class to implement the backward/implicit Euler scheme (theta-scheme,
    with theta = 1)."""

    def __init__(self, init_sol, dom, dt, f, dirichlet=None, **kwargs):
        super().__init__(init_sol, dom, dt, f, dirichlet, theta=1, **kwargs)

class CrankNicholson(ThetaScheme):

    """Class to implement the Crank-Nicholson scheme (theta-scheme, with
    theta = 0.5)."""

    def __init__(self, init_sol, dom, dt, f, dirichlet=None, **kwargs):
        super().__init__(init_sol, dom, dt, f, dirichlet, theta=0.5, **kwargs)