"""
Custom Resource for finding latest the AMI id for a given Description

Parameters:
 * Region: (default: current region)
 * Name: Like: "amzn-ami-minimal-hvm*"
 * OwnerAlias
 * OwnerId
 * Architecture: Defaults to: "x86_64",
 * DeviceType: Defaults to: "ebs",
 * VirtualizationType: Defaults to: "hvm",
 * State: Defaults to: 'available',
"""

import os
import boto3
import structlog

from cfn_custom_resource import CloudFormationCustomResource
from _metadata import CUSTOM_RESOURCE_NAME

REGION = os.environ['AWS_REGION']

structlog.configure(processors=[structlog.processors.JSONRenderer()])

def dict_element_copy_if_exists(
        source_dict: dict, source_key: str,
        target_dict: dict, target_key: str):
    if source_key in source_dict:
        target_dict[target_key] = source_dict[source_key]

class FindAmi(CloudFormationCustomResource):
    RESOURCE_TYPE_SPEC = CUSTOM_RESOURCE_NAME
    DISABLE_PHYSICAL_RESOURCE_ID_GENERATION = True  # Return AMI ID as physical ID

    def validate(self):
        self.filter = {}

        try:
            self.filter['name'] = self.resource_properties['Name']
            dict_element_copy_if_exists(
                self.resource_properties, 'OwnerAlias',
                self.filter, 'owner-alias'
            )
            dict_element_copy_if_exists(
                self.resource_properties, 'OwnerId',
                self.filter, 'owner-id'
            )
            self.filter['architecture'] = self.resource_properties.get('Architecture', 'x86_64')
            self.filter['root-device-type'] = self.resource_properties.get('DeviceType', 'ebs')
            self.filter['virtualization-type'] = self.resource_properties.get('VirtualizationType', 'hvm')
            self.filter['state'] = self.resource_properties.get('State', 'available')
            return True

        except (AttributeError, KeyError):
            return False

    def create(self):
        structlog.get_logger().log("Handling request", filter=self.filter)
        ec2_client = boto3.client(  # Don't use self.get_boto3_client, since we may vary regions
            'ec2',
            region_name=self.resource_properties.get('Region', REGION),
        )

        ami_filter = []
        for key, value in self.filter.items():
            ami_filter.append({
                'Name': key,
                'Values': [value],
            })

        structlog.get_logger().log("Converted to AMI filter; doing API call", filter=ami_filter)
        ami_list = ec2_client.describe_images(
            Filters=ami_filter
        )
        structlog.get_logger().log("API call done, sorting")
        sorted_ami_list = sorted(
            ami_list['Images'],
            key=lambda k: k.get('CreationDate', ''),
            reverse=True
        )
        if len(sorted_ami_list) == 0:
            self.status = self.STATUS_FAILED
            self.failure_reason = "No image found matching filters."
            return {}

        latest_ami = sorted_ami_list[0]
        self.physical_resource_id = latest_ami['ImageId']
        return {}

    def update(self):
        return self.create()

    def delete(self):
        # Nothing to delete
        pass

handler = FindAmi.get_handler()


# coding=utf-8
# Copyright 2017 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

from __future__ import (absolute_import, division, generators, nested_scopes, print_function,
                        unicode_literals, with_statement)

import os
import re

from pants.backend.jvm import argfile
from pants.backend.jvm.subsystems.shader import Shader
from pants.backend.jvm.tasks.nailgun_task import NailgunTask
from pants.base.exceptions import TaskError
from pants.base.revision import Revision
from pants.base.workunit import WorkUnitLabel
from pants.java.jar.jar_dependency import JarDependency
from pants.util.dirutil import safe_mkdir
from pants.util.memo import memoized_property
from pants.util.strutil import safe_shlex_split

class ErrorProne(NailgunTask):
  """Check Java code for Error Prone violations.  See http://errorprone.info/ for more details."""

  _ERRORPRONE_MAIN = 'com.google.errorprone.ErrorProneCompiler'
  _JAVA_SOURCE_EXTENSION = '.java'

  @classmethod
  def register_options(cls, register):
    super(ErrorProne, cls).register_options(register)

    register('--skip', type=bool, help='Skip Error Prone.')
    register('--transitive', default=False, type=bool,
             help='Run Error Prone against transitive dependencies of targets '
                  'specified on the command line.')
    register('--command-line-options', type=list, default=[], fingerprint=True,
             help='Command line options passed to Error Prone')
    register('--exclude-patterns', type=list, default=[], fingerprint=True,
             help='Patterns for targets to be excluded from analysis.')

    cls.register_jvm_tool(register,
                          'errorprone',
                          classpath=[
                            JarDependency(org='com.google.errorprone',
                                          name='error_prone_core',
                                          rev='2.3.1'),
                          ],
                          main=cls._ERRORPRONE_MAIN,
                          custom_rules=[
                            Shader.exclude_package('com.google.errorprone', recursive=True)
                          ]
                         )

    # The javac version should be kept in sync with the version used by errorprone above.
    cls.register_jvm_tool(register,
                          'errorprone-javac',
                          classpath=[
                            JarDependency(org='com.google.errorprone',
                                          name='javac',
                                          rev='9+181-r4173-1'),
                          ])

  @classmethod
  def prepare(cls, options, round_manager):
    super(ErrorProne, cls).prepare(options, round_manager)
    round_manager.require_data('runtime_classpath')

  @memoized_property
  def _exclude_patterns(self):
    return [re.compile(x) for x in set(self.get_options().exclude_patterns or [])]

  def _is_errorprone_target(self, target):
    if not target.has_sources(self._JAVA_SOURCE_EXTENSION):
      self.context.log.debug('Skipping [{}] because it has no {} sources'.format(target.address.spec, self._JAVA_SOURCE_EXTENSION))
      return False
    if target.is_synthetic:
      self.context.log.debug('Skipping [{}] because it is a synthetic target'.format(target.address.spec))
      return False
    for pattern in self._exclude_patterns:
      if pattern.search(target.address.spec):
        self.context.log.debug(
          "Skipping [{}] because it matches exclude pattern '{}'".format(target.address.spec, pattern.pattern))
        return False
    return True

  @property
  def cache_target_dirs(self):
    return True

  def execute(self):
    if self.get_options().skip:
      return

    if self.get_options().transitive:
      targets = self.context.targets(self._is_errorprone_target)
    else:
      targets = filter(self._is_errorprone_target, self.context.target_roots)

    targets = list(set(targets))

    target_count = 0
    errorprone_failed = False
    with self.invalidated(targets, invalidate_dependents=True) as invalidation_check:
      total_targets = len(invalidation_check.invalid_vts)
      for vt in invalidation_check.invalid_vts:
        target_count += 1
        self.context.log.info('[{}/{}] {}'.format(
          str(target_count).rjust(len(str(total_targets))),
          total_targets,
          vt.target.address.spec))

        result = self.errorprone(vt.target)
        if result != 0:
          errorprone_failed = True
          if self.get_options().fail_fast:
            break
        else:
          vt.update()

      if errorprone_failed:
        raise TaskError('ErrorProne checks failed')

  def calculate_sources(self, target):
    return {source for source in target.sources_relative_to_buildroot()
            if source.endswith(self._JAVA_SOURCE_EXTENSION)}

  def errorprone(self, target):
    runtime_classpaths = self.context.products.get_data('runtime_classpath')
    runtime_classpath = [jar for conf, jar in runtime_classpaths.get_for_targets(target.closure(bfs=True))]

    output_dir = os.path.join(self.workdir, target.id)
    safe_mkdir(output_dir)
    runtime_classpath.append(output_dir)

    # Try to run errorprone with the same java version as the target
    # The minimum JDK for errorprone is JDK 1.8
    min_jdk_version = max(target.platform.target_level, Revision.lenient('1.8'))
    if min_jdk_version.components[0] == 1:
      max_jdk_version = Revision(min_jdk_version.components[0], min_jdk_version.components[1], '9999')
    else:
      max_jdk_version = Revision(min_jdk_version.components[0], '9999')
    self.set_distribution(minimum_version=min_jdk_version, maximum_version=max_jdk_version, jdk=True)

    jvm_options = self.get_options().jvm_options[:]
    if self.dist.version < Revision.lenient('9'):
      # For Java 8 we need to add the errorprone javac jar to the bootclasspath to
      # avoid the "java.lang.NoSuchFieldError: ANNOTATION_PROCESSOR_MODULE_PATH" error
      # See https://github.com/google/error-prone/issues/653 for more information
      jvm_options.extend(['-Xbootclasspath/p:{}'.format(self.tool_classpath('errorprone-javac')[0])])

    args = [
      '-d', output_dir,
    ]

    # Errorprone does not recognize source or target 10 yet
    if target.platform.source_level < Revision.lenient('10'):
      args.extend(['-source', str(target.platform.source_level)])

    if target.platform.target_level < Revision.lenient('10'):
      args.extend(['-target', str(target.platform.target_level)])

    errorprone_classpath_file = os.path.join(self.workdir, '{}.classpath'.format(os.path.basename(output_dir)))
    with open(errorprone_classpath_file, 'w') as f:
      f.write('-classpath ')
      f.write(':'.join(runtime_classpath))
    args.append('@{}'.format(errorprone_classpath_file))

    for opt in self.get_options().command_line_options:
      args.extend(safe_shlex_split(opt))

    with argfile.safe_args(self.calculate_sources(target), self.get_options()) as batched_sources:
      args.extend(batched_sources)
      result = self.runjava(classpath=self.tool_classpath('errorprone'),
                            main=self._ERRORPRONE_MAIN,
                            jvm_options=jvm_options,
                            args=args,
                            workunit_name='errorprone',
                            workunit_labels=[WorkUnitLabel.LINT])

      self.context.log.debug('java {main} ... exited with result ({result})'.format(
        main=self._ERRORPRONE_MAIN, result=result))

    return result


import argparse
import re
import glob
import sys
import pickle

_dictionary = {}
a = []

# разбиваем строку на слова, опционально приводим к нижнему регистру
def correct_line(line, lowercase):
    line = re.sub(r'[^A-Za-zА-Яа-я ]', '', line)
    line = re.sub(r'\s+', ' ', line)
    if lowercase:
        line = line.lower()
    return line.strip().split()

# создаем словарь {слово1 : {слово2 : частота...}...}
def create_dict(a, dictionary):
    flag = 0
    global ending_word
    for i in range(len(a)):
        word2 = a[i]
        if flag == 0:
            word1 = ending_word
        elif flag == len(a) - 1:
            word1 = a[i - 1]
            ending_word = word2
        else:
            word1 = a[i - 1]
        if word1 != "":
            if dictionary.get(word1) is not None:
                if dictionary.get(word1).get(word2) is not None:
                    dictionary.get(word1)[word2] += 1
                else:
                    dictionary.get(word1).setdefault(word2, 1)
            else:
                default = {word2: 1}
                dictionary.setdefault(word1, default)
        flag += 1
    return dictionary

parser = argparse.ArgumentParser(description='Create a model.')
parser.add_argument(
    '--input-dir',
    dest='inp',
    default='stdin',
    help='input a path to the directory (else stdin)')
parser.add_argument(
    '--model',
    dest='mo',
    required=True,
    help='input a path to the file with model')
parser.add_argument(
    '--lc',
    action='store_true',
    default=False,
    help='converts text to lowercase')
args = parser.parse_args()
lowercase = args.lc

if args.inp != 'stdin':
    path = args.inp
    # извлекаем все файлы из указанной директории
    files = glob.glob(pathname=path)
    for name in files:
        ending_word = ''
        with open(name, encoding='ANSI') as f:
            for line in f:
                a = correct_line(line, lowercase)
                _dictionary = create_dict(a, _dictionary)
else:
    ending_word = ''
    for line in sys.stdin:
        correct_line(line, lowercase)
        _dictionary = create_dict(a, _dictionary)
model = args.mo
with open(model, 'wb') as t:    # сохраняем словарь в указанный файл
    pickle.dump(_dictionary, t)


import telebot
from bot import my_config
from mongoengine import connect
from models.my_cats_and_products import (Texts, Product, Category, Cart, OrdersHistory)
from bson import ObjectId
from models.my_user_madel import User
from telebot.types import (
    InlineKeyboardButton,
    InlineKeyboardMarkup,
    ReplyKeyboardMarkup
)
import time
from flask import Flask, request, abort

#sudo apt-get install openssl
#openssl genrsa -out webhook_pkey.pem 2048
#openssl req -new -x509 -days 3650 -key webhook_pkey.pem -out webhook_cert.pem

API_TOKEN = my_config.TOKEN

WEBHOOK_HOST = '34.69.155.24'
WEBHOOK_PORT = 80  # 443, 80, 88 or 8443 (port need to be 'open')
WEBHOOK_LISTEN = '0.0.0.0'  # In some VPS you may need to put here the IP addr

WEBHOOK_SSL_CERT = './webhook_cert.pem'  # Path to the ssl certificate
WEBHOOK_SSL_PRIV = './webhook_pkey.pem'  # Path to the ssl private key

WEBHOOK_URL_BASE = "https://%s:%s" % (WEBHOOK_HOST, WEBHOOK_PORT)
WEBHOOK_URL_PATH = "/%s/" % (API_TOKEN)

bot = telebot.TeleBot(my_config.TOKEN)
app = Flask(__name__)

connect('my_shop')
main_language = "_"

@app.route(WEBHOOK_URL_PATH, methods=['POST'])
def webhook():
    print("blah")
    if  request.headers.get('content-type') == 'application/json':
        json_string = request.get_data().decode('utf-8')
        update = telebot.types.Update.de_json(json_string)
        bot.process_new_updates([update])
        return ''
    else:
        abort(403)

@bot.message_handler(commands=['start'])
def start_page(message):
    kb = ReplyKeyboardMarkup(resize_keyboard=True)
    kb.add(*my_config.LANG_KEYBOARD.values())
    User.get_or_create_user(message)
    current_user = User.objects.get(user_id=message.chat.id)
    user_lang = current_user.language_code
    global main_language
    if user_lang == "ru" or None:
        main_language = "ru"
    else:
        main_language = "en"
    greetings_str = Texts.get_wlc_text("Старт")
    bot.send_message(message.chat.id, greetings_str, reply_markup=kb)

@bot.message_handler(func=lambda message: message.text == my_config.LANG_KEYBOARD['ru'])
def greetings(message):
    bot.delete_message(message.chat.id, message.message_id)
    kb = ReplyKeyboardMarkup(resize_keyboard=True, row_width=2)
    kb.add(*my_config.START_KEYBOARD_RU.values())
    bot.send_message(message.chat.id, text='Сделайте свой выбор', reply_markup=kb)

@bot.message_handler(func=lambda message: message.text == my_config.LANG_KEYBOARD['en'])
def en_greetings(message):
    bot.delete_message(message.chat.id, message.message_id)
    kb = ReplyKeyboardMarkup(resize_keyboard=True, row_width=2)
    kb.add(*my_config.START_KEYBOARD_EN.values())
    en_greetings_str = Texts.get_en_wlc_text("Start")
    bot.send_message(message.chat.id, en_greetings_str, reply_markup=kb)

@bot.message_handler(func=lambda message: message.text == my_config.START_KEYBOARD_RU['news'])
def show_news(message):
    bot.delete_message(message.chat.id, message.message_id)
    news_str = Texts.get_news_text("Старт")
    bot.send_message(message.chat.id, news_str)

@bot.message_handler(func=lambda message: message.text == my_config.START_KEYBOARD_EN['news'])
def show_en_news(message):
    bot.delete_message(message.chat.id, message.message_id)
    en_news_str = Texts.get_en_news_text("Start")
    bot.send_message(message.chat.id, en_news_str)

@bot.message_handler(func=lambda message: message.text == my_config.START_KEYBOARD_RU['info'])
def show_info(message):
    bot.delete_message(message.chat.id, message.message_id)
    info_str = Texts.get_info_text("Старт")
    bot.send_message(message.chat.id, info_str)

@bot.message_handler(func=lambda message: message.text == my_config.START_KEYBOARD_EN['info'])
def show_en_info(message):
    bot.delete_message(message.chat.id, message.message_id)
    en_info_str = Texts.get_en_info_text("Start")
    bot.send_message(message.chat.id, en_info_str)

@bot.message_handler(func=lambda message: message.text == my_config.START_KEYBOARD_RU['categories'])
def show_cats(message):
    bot.delete_message(message.chat.id, message.message_id)
    cats_kb = InlineKeyboardMarkup()
    cats_buttons = []
    all_cats = Category.objects[:2]

    for i in all_cats:
        callback_data = 'category_' + str(i.id)
        if i.is_parent:
            callback_data = 'subcategory_' + str(i.id)
        cats_buttons.append(InlineKeyboardButton(text=(str(i.title)+' >>' if i.is_parent else str(i.title)),
                                                 callback_data=callback_data))
    cats_kb.add(*cats_buttons)
    bot.send_message(message.chat.id, text='Выберите товары в категории', reply_markup=cats_kb)

@bot.message_handler(func=lambda message: message.text == my_config.START_KEYBOARD_EN['categories'])
def en_show_cats(message):
    bot.delete_message(message.chat.id, message.message_id)
    cats_kb = InlineKeyboardMarkup()
    cats_buttons = []
    all_cats = Category.objects[2:4]

    for i in all_cats:
        callback_data = 'encategory_' + str(i.id)
        if i.is_parent:
            callback_data = 'ensubcategory_' + str(i.id)
        cats_buttons.append(InlineKeyboardButton(text=(str(i.en_title) + ' >>' if i.is_parent else str(i.en_title)),
                                                 callback_data=callback_data))

    cats_kb.add(*cats_buttons)
    bot.send_message(message.chat.id, text='Select products in category', reply_markup=cats_kb)

@bot.callback_query_handler(func=lambda call: call.data.split('_')[0] == 'subcategory')
def sub_cat(call):
    bot.edit_message_text(chat_id=call.message.chat.id, message_id=call.message.message_id,
                          text="Сделайте свой выбор")
    subcats_kb = InlineKeyboardMarkup()
    subcats_buttons = []
    category = Category.objects.get(id=call.data.split('_')[1])
    for i in category.sub_categories:
        callback_data = 'category_' + str(i.id)
        if i.is_parent:
            callback_data = 'subcategory_' + str(i.id)
        subcats_buttons.append(InlineKeyboardButton(text=i.title,
                                                    callback_data=callback_data))
    subcats_buttons.append(InlineKeyboardButton(text="Назад",
                                                    callback_data="Назад"))
    subcats_kb.add(*subcats_buttons)
    bot.send_message(call.message.chat.id, text='Товары в подкатегориях', reply_markup=subcats_kb)

@bot.callback_query_handler(func=lambda call: call.data.split('_')[0] == 'ensubcategory')
def en_sub_cat(call):
    bot.edit_message_text(chat_id=call.message.chat.id, message_id=call.message.message_id,
                          text="Make your choice")
    subcats_kb = InlineKeyboardMarkup()
    subcats_buttons = []
    category = Category.objects.get(id=call.data.split('_')[1])
    for i in category.en_sub_categories:
        callback_data = 'encategory_' + str(i.id)
        if i.is_parent:
            callback_data = 'ensubcategory_' + str(i.id)
        print(callback_data)
        subcats_buttons.append(InlineKeyboardButton(text=i.title,
                                                    callback_data=callback_data))
    subcats_buttons.append(InlineKeyboardButton(text="Back",
                                                    callback_data="Back"))
    subcats_kb.add(*subcats_buttons)
    bot.send_message(call.message.chat.id, text='Subcategories', reply_markup=subcats_kb)

@bot.callback_query_handler(func=lambda call: call.data == "Назад")
def go_back(call):
    bot.edit_message_text(chat_id=call.message.chat.id, message_id=call.message.message_id, text="Сделайте свой выбор")
    if call.data == "Назад":
        show_cats(call.message)

@bot.callback_query_handler(func=lambda call: call.data == "Back")
def go_back(call):
    bot.edit_message_text(chat_id=call.message.chat.id, message_id=call.message.message_id, text="Make your choice")
    if call.data == "Back":
        en_show_cats(call.message)

@bot.callback_query_handler(func=lambda call: call.data.split('_')[0] == 'category')
def products_by_cat(call):
    cat = Category.objects.filter(id=call.data.split('_')[1]).first()
    products = cat.category_products
    bot.send_message(call.message.chat.id, 'Продукты.')
    if not products:
        bot.send_message(call.message.chat.id, 'В данной категории пока нет продуктов.')
    for p in products:
        products_kb = InlineKeyboardMarkup(row_width=2)
        products_kb.add(InlineKeyboardButton(
            text='Корзина',
            callback_data='addtocart_' + str(p.id)

        ),
            InlineKeyboardButton(
                text='Подробно',
                callback_data='product_' + str(p.id)
            ),
            InlineKeyboardButton(
                text='Назад',
                callback_data= 'cНазад'
            )
        )
        title = f'{p.title}'
        description = f'\n\n{p.description}'
        bot.send_photo(call.message.chat.id,
                       p.image.get(),
                       caption=title + description,
                       reply_markup=products_kb,
                       parse_mode='HTML')

@bot.callback_query_handler(func=lambda call: call.data.split('_')[0] == 'encategory')
def en_products_by_cat(call):
    bot.edit_message_text(chat_id=call.message.chat.id, message_id=call.message.message_id, text="Make your choice")
    cat = Category.objects.filter(id=call.data.split('_')[1]).first()
    products = cat.category_products

    if not products:
        bot.send_message(call.message.chat.id, 'There is no products in this category.')
    for p in products:
        products_kb = InlineKeyboardMarkup(row_width=2)
        products_kb.add(InlineKeyboardButton(
            text='Cart',
            callback_data='addtocart_' + str(p.id)

        ),
            InlineKeyboardButton(
                text='Details',
                callback_data='product_' + str(p.id)
            ),
            InlineKeyboardButton(
                text='Back',
                callback_data= 'cBack'
            )
        )

        title = f'{p.en_title}'
        description = f'\n\n{p.en_description}'
        bot.send_photo(call.message.chat.id,
                       p.image.get(),
                       caption=title + description,
                       reply_markup=products_kb,
                       parse_mode='HTML')

@bot.callback_query_handler(func=lambda call: call.data =="cНазад")
def sub_go_back(call):
    if call.data == "cНазад":
        show_cats(call.message)
    bot.delete_message(call.message.chat.id, call.message.message_id)

@bot.callback_query_handler(func=lambda call: call.data =="cBack")
def en_sub_go_back(call):
    if call.data == "cBack":
        en_show_cats(call.message)
    bot.delete_message(call.message.chat.id, call.message.message_id)

@bot.callback_query_handler(func=lambda call: call.data.split('_')[0] == 'product')
def show_details(call):
    prod = Product.objects.filter(id = call.data.split('_')[1]).first()
    prod_kb = InlineKeyboardMarkup()
    prod_kb.add(InlineKeyboardButton(
        text='Назад',
        callback_data='нНазад'))
    price = f'{prod.price}'
    weight = f'\n\n{prod.weight}'
    quantity = f'\n\n{prod.quantity}'
    bot.send_photo(call.message.chat.id,
                   prod.image.get(),
                   caption=price+' UAH'+weight+quantity+' на складе',
                   reply_markup=prod_kb,
                   parse_mode='HTML')

@bot.callback_query_handler(func=lambda call: call.data =="нНазад")
def fin_go_back(call):
    if call.data == "нНазад":
        show_cats(call.message)
    bot.delete_message(call.message.chat.id, call.message.message_id)

@bot.callback_query_handler(func=lambda call: call.data.split('_')[0] == 'addtocart')
def add_to_card(call):
    Cart.create_or_append_to_cart(product_id=call.data.split('_')[1],
                                  user_id=call.message.chat.id)
    cart = Cart.objects.all().first()

@bot.message_handler(func=lambda message: message.text == my_config.START_KEYBOARD_RU['cart'] or
                                          message.text == my_config.START_KEYBOARD_EN['cart'])
def show_cart(message):
    current_user = User.objects.get(user_id=message.chat.id)
    cart = Cart.objects.filter(user=current_user, is_archived=False).first()

    if not cart:
        bot.send_message(message.chat.id, 'Корзина пустая')
        return

    if not cart.products:
        bot.send_message(message.chat.id, 'Корзина пустая')

    for product in cart.products:
        remove_kb = InlineKeyboardMarkup()
        remove_button = InlineKeyboardButton(text='Удалить продукт с корзины',
                                             callback_data='rmproduct_' + str(product.id))
        remove_kb.add(remove_button)
        bot.send_message(message.chat.id, product.title, reply_markup=remove_kb)

    submit_kb = InlineKeyboardMarkup()
    submit_button = InlineKeyboardButton(
        text='Оформить заказ',
        callback_data='submit'
    )
    submit_kb.add(submit_button)
    bot.send_message(message.chat.id, 'Подтвердите Ваш заказ', reply_markup=submit_kb)

@bot.callback_query_handler(func=lambda call: call.data.split('_')[0] == 'rmproduct')
def rm_product_from_cart(call):
    current_user = User.objects.get(user_id=call.message.chat.id)
    cart = Cart.objects.filter(user=current_user).first()
    cart.update(pull__products=ObjectId(call.data.split('_')[1]))
    bot.delete_message(call.message.chat.id, call.message.message_id)

@bot.callback_query_handler(func=lambda call: call.data.split('_')[0] == 'submit')
def submit_cart(call):
    current_user = User.objects.get(user_id=call.message.chat.id)
    cart = Cart.objects.filter(user=current_user, is_archived=False).first()
    cart.is_archived = True

    order_history = OrdersHistory.get_or_create(current_user)
    order_history.orders.append(cart)
    bot.send_message(call.message.chat.id, 'Спасибо за заказ!')
    cart.save()
    order_history.save()

@bot.message_handler(func=lambda message: message.text == my_config.START_KEYBOARD_RU['order_history'] or
                                          message.text == my_config.START_KEYBOARD_EN['order_history'])
def show_order_history(message):
    current_user = User.objects.get(user_id=message.chat.id)
    order_history=OrdersHistory.objects.filter(user=current_user).first()
    orders_id_list = []
    prod_id_list = []
    for i in order_history.orders:
        cart_data = str(i.id)
        orders_id_list.append(cart_data)
    for i in orders_id_list:
        cart = Cart.objects.filter(id=i).first()
        for j in cart.products:
            product_data = str(j.id)
            prod_id_list.append(product_data)
    print(prod_id_list)
    if OrdersHistory.objects.filter(user=current_user).first():
        bot.send_message(message.chat.id, 'Bаша история заказов')
    else:
        bot.send_message(message.chat.id, 'У Вас нет истории заказов')

    for p in prod_id_list:
        prod = Product.objects.filter(id=p).first()
        title = f'{prod.title}'
        price = f'\n\n{prod.price}'
        weight = f'\n\n{prod.weight}'

        bot.send_message(parse_mode='HTML', chat_id = message.chat.id,
                         text=title + price + ' UAH' + weight)

bot.remove_webhook()

time.sleep(1)

# Set webhook
bot.set_webhook(url=WEBHOOK_URL_BASE + WEBHOOK_URL_PATH,
                certificate=open(WEBHOOK_SSL_CERT, 'r'))

# Start flask server
app.run(host=WEBHOOK_LISTEN,
        port=WEBHOOK_PORT,
        ssl_context=(WEBHOOK_SSL_CERT, WEBHOOK_SSL_PRIV),
        debug=True)


"""This class was taken from the matplotlib site from the Lasso Selector Demo
    https://matplotlib.org/3.1.1/gallery/widgets/lasso_selector_demo_sgskip.html
"""

import numpy as np
from matplotlib.widgets import LassoSelector
from matplotlib.path import Path

class SelectFromCollection(object):
    """Select indices from a matplotlib collection using `LassoSelector`.

    Selected indices are saved in the `ind` attribute. This tool fades out the
    points that are not part of the selection (i.e., reduces their alpha
    values). If your collection has alpha < 1, this tool will permanently
    alter the alpha values.

    Note that this tool selects collection objects based on their *origins*
    (i.e., `offsets`).

    Parameters
    ----------
    ax : :class:`~matplotlib.axes.Axes`
        Axes to interact with.

    collection : :class:`matplotlib.collections.Collection` subclass
        Collection you want to select from.

    alpha_other : 0 <= float <= 1
        To highlight a selection, this tool sets all selected points to an
        alpha value of 1 and non-selected points to `alpha_other`.
    """

    def __init__(self, ax, collection, alpha_other=0.3):
        self.canvas = ax.figure.canvas
        self.collection = collection
        self.alpha_other = alpha_other

        self.xys = collection.get_offsets()
        self.Npts = len(self.xys)

        # Ensure that we have separate colors for each object
        self.fc = collection.get_facecolors()
        if len(self.fc) == 0:
            raise ValueError('Collection must have a facecolor')
        elif len(self.fc) == 1:
            self.fc = np.tile(self.fc, (self.Npts, 1))

        self.lasso = LassoSelector(ax, onselect=self.onselect)
        self.ind = []

    def onselect(self, verts):
        path = Path(verts)
        self.ind = np.nonzero(path.contains_points(self.xys))[0]
        self.fc[:, -1] = self.alpha_other
        self.fc[self.ind, -1] = 1
        self.collection.set_facecolors(self.fc)
        self.canvas.draw_idle()

    def disconnect(self):
        self.lasso.disconnect_events()
        self.fc[:, -1] = 1
        self.collection.set_facecolors(self.fc)
        self.canvas.draw_idle()


import json
import os

import pytest

import state
from protocol import CloudNodeProtocol
from new_message import process_new_message
from message import Message, ClientType, ActionType, LibraryActionType, ErrorType

@pytest.fixture(scope="module")
def dummy_client():
    return CloudNodeProtocol()

@pytest.fixture(scope="module")
def library_registration_message(repo_id, api_key):
    return Message.make({
        "type": "REGISTER",
        "node_type": "library",
        "repo_id": repo_id,
        "api_key": api_key,
        "is_demo": False
    })

@pytest.fixture(scope="module")
def bad_registration_message(repo_id):
    return Message.make({
        "type": "REGISTER",
        "node_type": "library",
        "repo_id": repo_id,
        "api_key": "bad-api-key",
        "is_demo": "False"
    })

@pytest.fixture(scope="module")
def dashboard_registration_message(repo_id, api_key):
    return Message.make({
        "type": "REGISTER",
        "node_type": "dashboard",
        "repo_id": repo_id,
        "api_key": api_key,
        "is_demo": False
    })

@pytest.fixture(scope="module")
def do_nothing():
    return {
        "action": None
    }

@pytest.fixture(scope="module")
def registration_success():
    return {
        "action": ActionType.UNICAST,
        "message": {
            "action": LibraryActionType.REGISTRATION_SUCCESS.value,
            "error": False,
        }
    }

@pytest.fixture(scope="module")
def failed_authentication_error():
    return {
        "error": True,
        "error_message": "API key provided is invalid!",
        "type": ErrorType.AUTHENTICATION.value,
    }

@pytest.fixture(scope="module")
def duplicate_client_error():
    return {
        "error": True,
        "error_message": "Client already exists!",
        "type": ErrorType.REGISTRATION.value,
    }

@pytest.fixture(scope="module")
def only_one_dashboard_client_error():
    return {
        "error": True,
        "error_message": "Only one DASHBOARD client allowed at a time!",
        "type": ErrorType.REGISTRATION.value,
    }

@pytest.fixture(scope="module")
def original_client_count(factory, repo_id):
    return _client_count(factory, repo_id)

@pytest.fixture(autouse=True)
def unregister(factory, repo_id, dummy_client):
    yield
    clients = factory.clients[repo_id][ClientType.LIBRARY]
    if dummy_client in clients:
        clients.remove(dummy_client)

def test_basic_register(library_registration_message, factory, dummy_client, \
        registration_success, original_client_count):
    """
    Test that a basic `LIBRARY` registration succeeds.
    """
    repo_id = library_registration_message.repo_id
    results = process_new_message(library_registration_message, factory, \
        dummy_client)
    new_client_count = _client_count(factory, repo_id)

    assert results == registration_success, \
        "Resulting message is incorrect!"
    assert new_client_count == original_client_count + 1, \
        "Client count is incorrect!"

def test_failed_authentication(bad_registration_message, factory, \
        dummy_client, failed_authentication_error, original_client_count):
    """
    Test that registration fails with an invalid API key
    """
    repo_id = bad_registration_message.repo_id
    bad_registration_message.api_key = "bad-api-key"
    results = process_new_message(bad_registration_message, factory, \
        dummy_client)
    new_client_count = _client_count(factory, repo_id)

    assert results.get("message") == failed_authentication_error, \
        "Resulting message is incorrect!"
    assert new_client_count == original_client_count, \
        "Client count is incorrect!"

def test_no_duplicate_client(library_registration_message, factory, \
        dummy_client, duplicate_client_error, original_client_count):
    """
    Test that a client cannot be registered twice.
    """
    repo_id = library_registration_message.repo_id
    results = process_new_message(library_registration_message, factory, \
        dummy_client)
    results = process_new_message(library_registration_message, factory, \
        dummy_client)
    new_client_count = _client_count(factory, repo_id)

    assert results.get("message") == duplicate_client_error, \
        "Resulting message is incorrect!"
    assert new_client_count == original_client_count + 1, \
        "Client count is incorrect!"

def test_only_one_dashboard_client(dashboard_registration_message, factory, \
        dummy_client, only_one_dashboard_client_error, original_client_count):
    """
    Test that more than one dashboard client cannot be registered at a time.
    """
    repo_id = dashboard_registration_message.repo_id
    assert _client_count(factory, repo_id) == original_client_count
    results = process_new_message(dashboard_registration_message, factory, \
        dummy_client)
    new_client_count = _client_count(factory, repo_id)

    assert results.get("message") == only_one_dashboard_client_error, \
        "Resulting message is incorrect!"
    assert new_client_count == original_client_count, \
        "Client count is incorrect!"

def _client_count(factory, repo_id):
    """
    Helper function to count the total number of clients in the factory.
    """
    return len(factory.clients[repo_id][ClientType.DASHBOARD]) \
        + len(factory.clients[repo_id][ClientType.LIBRARY])


# coding=utf8
from django.shortcuts import render
from django.http import JsonResponse,HttpResponseRedirect
from django.conf import settings
import json
import requests
import logging
from models import *
import datetime
from django.core.cache import cache
from functools import wraps
import sys
import os
import re
import math
import time
import xlwt
import datetime
reload(sys)
sys.setdefaultencoding('UTF-8')

def export(request):
    taskid = request.GET.get("id")
    u = User.objects.all().order_by("-like")
    fname = "data/data.xls"
    file = xlwt.Workbook()
    table = file.add_sheet('active',cell_overwrite_ok=True)
    table.write(0,0,'id')
    table.write(0,1,u'姓名')
    table.write(0,2,u'头像地址')
    table.write(0,3,u'参加理由')
    table.write(0,4,u'手机号')
    table.write(0,5,u'排名')
    table.write(0,6,u'喜欢')
    line = 1
    for i in u:
        table.write(line,0,i.id)
        table.write(line,1,i.name)
        table.write(line,2,i.avatar)
        table.write(line,3,i.desc)
        table.write(line,4,i.mobile)
        table.write(line,5,line)
        table.write(line,6,i.like)
        line += 1
    file.save(fname)
    return HttpResponseRedirect('/data/data.xls')

def getUserInformation(request):
    uid = request.GET.get("uid")
    date = request.GET.get("date")
    if uid:
        u = User.objects.get(id=uid)
        us = User.objects.all().order_by("-like")
        cnt = 0
        rank = 0
        for i in us:
            cnt += 1
            if i.id == uid:
                rank = cnt
                break
    if date:
        u = User.objects.get(dateline=date)
        us = User.objects.all().order_by("-like")
        cnt = 0
        rank = 0
        for i in us:
            cnt += 1
            if i.dateline == date:
                rank = cnt
                break
    return JsonResponse({
        "status" : "success",
        "desc" : u.desc,
        "avatar" : u.avatar,
        "like" : u.like,
        "rank" : rank,
        "date" : u.dateline
    })

def like(request):
    if request.GET.get("uid"):
        u = User.objects.get(id=request.GET.get("uid"))
    else :
        u = User.objects.get(dateline=request.GET.get("date"))
    u.like += 1
    u.save()

    return JsonResponse({
        "status": "success",
        "like" : u.like
    })

def submit(request):
    name = request.POST.get("name")
    mobile = request.POST.get("mobile")
    desc = request.POST.get("desc")
    f = request.FILES.get('file')
    date = request.POST.get("date")
    path = "data/"
    if not os.path.exists(path):
        os.mkdirs(path)
    ext = str(f).split(".")[-1]
    ext = ext.lower()
    path = 'data/' + str(time.time()) + "." + ext
    des = open(path,'wb+')
    for j in f.chunks():
        des.write(j)
    des.close()
    dateline = time.time()
    u = User.objects.filter(mobile=mobile).count()
    if not u:
        user = User(name=name,mobile=mobile,desc=desc,like=0,avatar=path,dateline=date)
        user.save()
        return JsonResponse({
            "status":"success"
        })
    else :
        return JsonResponse({
            "status":"fail",
            "reason":"您已经参加过了哦"
        })


#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.

"""Tests for _qubit_operator.py."""
import copy

import numpy
import pytest

from openfermion.ops._qubit_operator import (_PAULI_OPERATOR_PRODUCTS,
                                             QubitOperator,
                                             QubitOperatorError)

def test_pauli_operator_product_unchanged():
    correct = {('I', 'I'): (1., 'I'),
               ('I', 'X'): (1., 'X'),
               ('X', 'I'): (1., 'X'),
               ('I', 'Y'): (1., 'Y'),
               ('Y', 'I'): (1., 'Y'),
               ('I', 'Z'): (1., 'Z'),
               ('Z', 'I'): (1., 'Z'),
               ('X', 'X'): (1., 'I'),
               ('Y', 'Y'): (1., 'I'),
               ('Z', 'Z'): (1., 'I'),
               ('X', 'Y'): (1.j, 'Z'),
               ('X', 'Z'): (-1.j, 'Y'),
               ('Y', 'X'): (-1.j, 'Z'),
               ('Y', 'Z'): (1.j, 'X'),
               ('Z', 'X'): (1.j, 'Y'),
               ('Z', 'Y'): (-1.j, 'X')}
    assert _PAULI_OPERATOR_PRODUCTS == correct

def test_imul_inplace():
    qubit_op = QubitOperator("X1")
    prev_id = id(qubit_op)
    qubit_op *= 3.
    assert id(qubit_op) == prev_id

@pytest.mark.parametrize("multiplier", [0.5, 0.6j, numpy.float64(2.303),
                         numpy.complex128(-1j)])
def test_imul_scalar(multiplier):
    loc_op = ((1, 'X'), (2, 'Y'))
    qubit_op = QubitOperator(loc_op)
    qubit_op *= multiplier
    assert qubit_op.terms[loc_op] == pytest.approx(multiplier)

def test_imul_qubit_op():
    op1 = QubitOperator(((0, 'Y'), (3, 'X'), (8, 'Z'), (11, 'X')), 3.j)
    op2 = QubitOperator(((1, 'X'), (3, 'Y'), (8, 'Z')), 0.5)
    op1 *= op2
    correct_coefficient = 1.j * 3.0j * 0.5
    correct_term = ((0, 'Y'), (1, 'X'), (3, 'Z'), (11, 'X'))
    assert len(op1.terms) == 1
    assert correct_term in op1.terms

def test_imul_qubit_op_2():
    op3 = QubitOperator(((1, 'Y'), (0, 'X')), -1j)
    op4 = QubitOperator(((1, 'Y'), (0, 'X'), (2, 'Z')), -1.5)
    op3 *= op4
    op4 *= op3
    assert ((2, 'Z'),) in op3.terms
    assert op3.terms[((2, 'Z'),)] == 1.5j

def test_imul_bidir():
    op_a = QubitOperator(((1, 'Y'), (0, 'X')), -1j)
    op_b = QubitOperator(((1, 'Y'), (0, 'X'), (2, 'Z')), -1.5)
    op_a *= op_b
    op_b *= op_a
    assert ((2, 'Z'),) in op_a.terms
    assert op_a.terms[((2, 'Z'),)] == 1.5j
    assert ((0, 'X'), (1, 'Y')) in op_b.terms
    assert op_b.terms[((0, 'X'), (1, 'Y'))] == -2.25j

def test_imul_bad_multiplier():
    op = QubitOperator(((1, 'Y'), (0, 'X')), -1j)
    with pytest.raises(TypeError):
        op *= "1"

def test_mul_by_scalarzero():
    op = QubitOperator(((1, 'Y'), (0, 'X')), -1j) * 0
    assert ((0, 'X'), (1, 'Y')) in op.terms
    assert op.terms[((0, 'X'), (1, 'Y'))] == pytest.approx(0.0)

def test_mul_bad_multiplier():
    op = QubitOperator(((1, 'Y'), (0, 'X')), -1j)
    with pytest.raises(TypeError):
        op = op * "0.5"

def test_mul_out_of_place():
    op1 = QubitOperator(((0, 'Y'), (3, 'X'), (8, 'Z'), (11, 'X')), 3.j)
    op2 = QubitOperator(((1, 'X'), (3, 'Y'), (8, 'Z')), 0.5)
    op3 = op1 * op2
    correct_coefficient = 1.j * 3.0j * 0.5
    correct_term = ((0, 'Y'), (1, 'X'), (3, 'Z'), (11, 'X'))
    assert op1 == QubitOperator(((0, 'Y'), (3, 'X'), (8, 'Z'), (11, 'X')), 3.j)
    assert op2 == QubitOperator(((1, 'X'), (3, 'Y'), (8, 'Z')), 0.5)
    assert op3 == QubitOperator(correct_term, correct_coefficient)

def test_mul_npfloat64():
    op = QubitOperator(((1, 'X'), (3, 'Y')), 0.5)
    res = op * numpy.float64(0.5)
    assert res == QubitOperator(((1, 'X'), (3, 'Y')), 0.5 * 0.5)

def test_mul_multiple_terms():
    op = QubitOperator(((1, 'X'), (3, 'Y'), (8, 'Z')), 0.5)
    op += QubitOperator(((1, 'Z'), (3, 'X'), (8, 'Z')), 1.2)
    op += QubitOperator(((1, 'Z'), (3, 'Y'), (9, 'Z')), 1.4j)
    res = op * op
    correct = QubitOperator((), 0.5**2 + 1.2**2 + 1.4j**2)
    correct += QubitOperator(((1, 'Y'), (3, 'Z')),
                             2j * 1j * 0.5 * 1.2)
    assert res == correct

def test_renormalize_error():
    op = QubitOperator()
    with pytest.raises(ZeroDivisionError):
        op.renormalize()

def test_renormalize():
    op = QubitOperator(((1, 'X'), (3, 'Y'), (8, 'Z')), 1)
    op += QubitOperator(((2, 'Z'), (3, 'Y')), 1)
    op.renormalize()
    for term in op.terms:
        assert op.terms[term] == pytest.approx(1/numpy.sqrt(2.))
    assert op.induced_norm(2) == pytest.approx(1.)


import unittest
import vtk, qt, ctk, slicer
from slicer.ScriptedLoadableModule import *
import logging

import os
import ntpath
import xml.etree.ElementTree as ET
import re
import zipfile
import shutil
import struct
import numpy as np
import math

################################################################################
# EAMapReader
#

class EAMapReader(ScriptedLoadableModule):

  def __init__(self, parent):
    ScriptedLoadableModule.__init__(self, parent)
    self.parent.title = "EA Map Reader"
    self.parent.categories = ["Cardiac Electrophysiology"]
    self.parent.dependencies = []
    self.parent.contributors = ["Stephan Hohmann (Hannover Medical School)"]
    self.parent.helpText = """
This extension reads electroanatomic maps exported from various systems into 3D Slicer.
Currently supported are Ensite NavX (Abbott, St. Paul, MN), CARTO 3 (Biosense Webster, Diamond Bar, CA), and RHYTHMIA (Boston Scientific, Marlborough, MA).
For research use only. The manufacturers of the respective mapping systems are not affiliated with the development of this extension.
This software has not been approved for clinical use, and imported maps should be cross-checked with their representation on the original mapping system.
See documentation for detailed export instructions.
"""
    self.parent.helpText += self.getDefaultModuleDocumentationLink()
    self.parent.acknowledgementText = """
This extension was developed by Stephan Hohmann, Hannover Medical School.
This work was funded by the Deutsche Forschungsgemeinschaft (DFG, German Research Foundation) - project no. 380200397.
If you use EAMapReader in your own research please cite the following publication: Hohmann S, Henkenberens C, Zormpas C, Christiansen H, Bauersachs J, Duncker D, Veltmann C. A novel open-source software based high-precision workflow for target definition in cardiac radioablation. J Cardiovasc Electrophysiol 2020. doi:10.1111/jce.14660

"""

################################################################################
# EAMapReaderWidget
#

class EAMapReaderWidget(ScriptedLoadableModuleWidget):

  def setup(self):
    ScriptedLoadableModuleWidget.setup(self)

    self.logic = EAMapReaderLogic()
    self.logic.logCallback = self.addLog
    self.logic.progressCallback = self.updateProgress
    self.loadingInProgress = False

    # Instantiate and connect widgets ...
    self.buttonVelocity = qt.QPushButton("Ensite Velocity / Precision")
    self.buttonVelocity.toolTip = "Import Ensite map."
    self.buttonVelocity.enabled = True
    self.layout.addWidget(self.buttonVelocity)

    self.buttonCarto = qt.QPushButton("CARTO 3")
    self.buttonCarto.toolTip = "Import CARTO 3 map."
    self.buttonCarto.enabled = True
    self.layout.addWidget(self.buttonCarto)

    self.buttonRhythmia = qt.QPushButton("RHYTHMIA")
    self.buttonRhythmia.toolTip = "Import RHYTHMIA Map."
    self.buttonRhythmia.enabled = True
    self.layout.addWidget(self.buttonRhythmia)

    self.statusLabel = qt.QPlainTextEdit()
    self.statusLabel.setTextInteractionFlags(qt.Qt.TextSelectableByMouse)
    self.statusLabel.setCenterOnScroll(True)
    self.layout.addWidget(self.statusLabel)

    self.progressBar=qt.QProgressBar()
    self.progressBar.setRange(0, 100)
    self.progressBar.setValue(0)
    self.layout.addWidget(self.progressBar)

    # connections
    self.buttonVelocity.connect('clicked(bool)', self.onButtonVelocity)
    self.buttonCarto.connect('clicked(bool)', self.onButtonCarto)
    self.buttonRhythmia.connect('clicked(bool)', self.onButtonRhythmia)

  def cleanup(self):
    pass

  def reenableButtons(self):
    self.buttonVelocity.text = "Ensite"
    self.buttonVelocity.enabled = True
    self.buttonCarto.text = "CARTO 3"
    self.buttonCarto.enabled = True
    self.buttonRhythmia.text = "RHYTHMIA"
    self.buttonRhythmia.enabled = True

  def onButtonVelocity(self):
    if self.loadingInProgress:
      self.loadingInProgress = False
      self.logic.abortRequested = True
      self.buttonVelocity.text = "Cancelling..."
      self.buttonVelocity.enabled = False
      return
    self.clearLog()
    self.filename = qt.QFileDialog.getOpenFileName(self.parent, "Open Ensite file", "", "XML files (*.xml);;All files (*.*)", qt.QFileDialog.ExistingFile)
    if len(self.filename) > 0:
      self.loadingInProgress = True
      self.buttonVelocity.text = "Cancel Ensite import."
      if not self.logic.readVelocity(self.filename):
        self.addLog("Import failed.")
      self.loadingInProgress = False
      self.logic.abortRequested = False
      self.reenableButtons()

  def onButtonCarto(self):
    if self.loadingInProgress:
      self.loadingInProgress = False
      self.logic.abortRequested = True
      self.buttonCarto.text = "Cancelling..."
      self.buttonCarto.enabled = False
      return
    self.clearLog()
    self.filename = qt.QFileDialog.getOpenFileName(self.parent, "Open CARTO 3 file", "", "ZIP archives (*.zip);;All files (*.*)", qt.QFileDialog.ExistingFile)
    if len(self.filename) > 0:
      self.loadingInProgress = True
      self.buttonCarto.text = "Cancel CARTO 3 import."
      if not self.logic.readCarto(self.filename):
        self.addLog("Import failed.")
      self.loadingInProgress = False
      self.logic.abortRequested = False
      self.reenableButtons()

  def onButtonRhythmia(self):
    if self.loadingInProgress:
      self.loadingInProgress = False
      self.logic.abortRequested = True
      self.buttonRhythmia.text = "Cancelling..."
      self.buttonRhythmia.enabled = False
      return
    self.clearLog()
    self.filename = qt.QFileDialog.getOpenFileName(self.parent, "Open RHYTHMIA file", "", "RHYTHMIA exported archive (*.000);;All files (*.*)", qt.QFileDialog.ExistingFile)
    if len(self.filename) > 0:
      self.loadingInProgress = True
      self.buttonRhythmia.text = "Cancel RHYTHMIA import."
      if not self.logic.readRhythmia(self.filename):
        self.addLog("Import failed.")
      self.loadingInProgress = False
      self.logic.abortRequested = False
      self.reenableButtons()

  def clearLog(self):
    # Clear text in log window
    self.statusLabel.plainText = ''
    self.progressBar.setValue(0)
    slicer.app.processEvents()  # force update

  def addLog(self, text):
    # Append text to log window
    self.statusLabel.appendPlainText(text)
    slicer.app.processEvents()  # force update

  def updateProgress(self, percent):
    # Update progress bar
    self.progressBar.setValue(percent)
    slicer.app.processEvents()  # force update

################################################################################
# EAMapReaderLogic
#

class EAMapReaderLogic(ScriptedLoadableModuleLogic):
  """ Uses ScriptedLoadableModuleLogic base class, available at:
  https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
  """
  def __init__(self):
    ScriptedLoadableModuleLogic.__init__(self)
    self.logCallback = None
    self.progressCallback = None
    self.abortRequested = False
    self.progress = 0

  def addLog(self, text):
    logging.info(text)
    if self.logCallback:
      self.logCallback(text)

  def updateProgress(self):
    if self.progressCallback:
      self.progressCallback(self.progress)

  def getTempDirectoryBase(self):
    tempDir = qt.QDir(slicer.app.temporaryPath)
    fileInfo = qt.QFileInfo(qt.QDir(tempDir), "EAMapReader")
    dirPath = fileInfo.absoluteFilePath()
    qt.QDir().mkpath(dirPath)
    return dirPath

  def createTempDirectory(self):
    tempDir = qt.QDir(self.getTempDirectoryBase())
    tempDirName = qt.QDateTime().currentDateTime().toString("yyyyMMdd_hhmmss_zzz")
    fileInfo = qt.QFileInfo(qt.QDir(tempDir), tempDirName)
    dirPath = fileInfo.absoluteFilePath()
    qt.QDir().mkpath(dirPath)
    return dirPath

  ################################################################################
  # Model creation
  #

  def mkVtkIdList(self, it):
    vil = vtk.vtkIdList()
    for i in it:
      vil.InsertNextId(int(i))
    return vil

  def CreateMesh(self, modelNode, arrayVertices, arrayVertexNormals, arrayTriangles, labelsScalars, arrayScalars):
    # based on https://vtk.org/Wiki/VTK/Examples/Python/DataManipulation/Cube.py
    # modelNode : a vtkMRMLModelNode in the Slicer scene which will hold the mesh
    # arrayVertices : list of triples [[x1,y1,z2], [x2,y2,z2], ... ,[xn,yn,zn]] of vertex coordinates
    # arrayVertexNormals : list of triples [[nx1,ny1,nz2], [nx2,ny2,nz2], ... ] of vertex normals
    # arrayTriangles : list of triples of 0-based indices defining triangles
    # labelsScalars : list of strings such as ["bipolar", "unipolar"] to label the individual scalars data sets
    # arrayScalars : list of n m-tuples for n vertices and m individual scalar sets

    # create the building blocks of polydata including data attributes.
    mesh    = vtk.vtkPolyData()
    points  = vtk.vtkPoints()
    normals = vtk.vtkFloatArray()
    polys   = vtk.vtkCellArray()

    # load the array data into the respective VTK data structures
    #self.addLog("  Initializing vertices.")
    for i in range(len(arrayVertices)):
      points.InsertPoint(i, arrayVertices[i])

    if self.abortRequested:
      return False

    #self.addLog("  Initializing triangles.")
    for i in range(len(arrayTriangles)):
      polys.InsertNextCell(self.mkVtkIdList(arrayTriangles[i]))

    if self.abortRequested:
      return False

    # Normals: http://vtk.1045678.n5.nabble.com/Set-vertex-normals-td5734525.html
    # First pre-allocating memory for the vtkDataArray using vtkDataArray::SetNumberOfComponents() and vtkDataArray::SetNumberOfTuples()
    # and then setting the actual values through SetTuple() is orders of magnitude faster than inserting them one-by-one (and allocating memory dynamically)
    # with InsertTuple()
    normals.SetNumberOfComponents(3)
    normals.SetNumberOfTuples(len(arrayVertexNormals))
    #self.addLog("  Initializing normals.")
    for i in range(len(arrayVertexNormals)):
      normals.SetTuple3(i, arrayVertexNormals[i][0], arrayVertexNormals[i][1], arrayVertexNormals[i][2])
      if self.abortRequested:
        return False

    # put together the mesh object
    # self.addLog("  Building mesh.")
    mesh.SetPoints(points)
    mesh.SetPolys(polys)
    if(len(arrayVertexNormals) == len(arrayVertices)):
      mesh.GetPointData().SetNormals(normals)

    if self.abortRequested:
      return False

    # self.addLog("  Adding scalar data.")

    # Add scalars
    scalars = []
    for j in range(len(labelsScalars)):
      scalars.append(vtk.vtkFloatArray())
      scalars[j].SetNumberOfComponents(1)
      scalars[j].SetNumberOfTuples(len(arrayScalars))
      for i in range(len(arrayScalars)):
        scalars[j].SetTuple1(i,arrayScalars[i][j])
        if self.abortRequested:
          return False
      scalars[j].SetName(labelsScalars[j])
      mesh.GetPointData().AddArray(scalars[j])

    if self.abortRequested:
      return False

    modelNode.SetAndObservePolyData(mesh)
    self.addLog("Model created.")
    return True

  def transformNode(self, node, matrix):
    transform = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLinearTransformNode')
    transformMatrix = vtk.vtkMatrix4x4()
    transformMatrix.Zero()

    for row in range(4):
      for col in range(4):
        transformMatrix.SetElement(row, col, matrix[row][col])

    transform.SetMatrixTransformToParent(transformMatrix)

    # Apply transform to node...
    node.SetAndObserveTransformNodeID(transform.GetID())
    # ... and harden it
    transformLogic = slicer.vtkSlicerTransformLogic()
    transformLogic.hardenTransform(node)
    # delete transform node
    slicer.mrmlScene.RemoveNode(transform)

  ################################################################################
  # XML parsing helper functions
  #

  def findallRecursive(self, node, element):
    for item in node.findall(element):
      yield item
    for child in node:
      for item in self.findallRecursive(child, element):
        yield item

  def TextToFloat(self, text):
    # First remove any leading and trailing spaces and newlines
    leadingNonDigits = "^[^0-9.-]*"
    trailingNonDigits = "[^0-9.-]*$"
    text = re.sub(leadingNonDigits, "",text)
    text = re.sub(trailingNonDigits, "",text)
    lines = re.split("\n",text)
    numbers = []
    for i in range(len(lines)):
      lines[i] = re.sub(leadingNonDigits, "",lines[i])
      lines[i] = re.sub(trailingNonDigits, "",lines[i])
      lineSplit = re.split("[ ]*", lines[i])
      thisNumbers = []
      for j in range(len(lineSplit)):
        thisNumbers.append(float(lineSplit[j]))
      numbers.append(thisNumbers)
    return numbers

  ################################################################################
  # Ensite import
  #

  def readVelocity(self, filename):
    self.progress = 0
    self.updateProgress()
    if self.abortRequested:
      return False

    self.addLog("Importing Ensite map:")
    self.addLog("  Parsing file "+filename)
    tree = ET.parse(filename)
    root = tree.getroot()

    self.progress = 5
    self.updateProgress()
    if self.abortRequested:
      return False

    volumes = list(self.findallRecursive(root, "Volume"))

    self.addLog("  Found "+str(len(volumes))+" volumes(s) in file.")
    self.progress = 10
    self.updateProgress()
    if self.abortRequested:
      return False

    progressSteps = 6
    progressEnd = 100
    progressIncrement = ((progressEnd - self.progress)/progressSteps)/len(volumes)

    volumeCounter = 0
    for volume in volumes:
      try:
        plaintext = volume.find("Vertices").text
        vertices = self.TextToFloat(plaintext)
        self.addLog("  Reading volume "+str(volumeCounter)+".")
        self.addLog("  Read "+str(len(vertices))+" vertices.")
        self.progress = self.progress + progressIncrement
        self.updateProgress()
      except AttributeError:
        self.addLog("  ERROR: No vertices found in volume "+str(volumeCounter)+".")
        return False

      if self.abortRequested:
        return False

      try:
        plaintext = volume.find("Map_data").text
        map_data = self.TextToFloat(plaintext)

        self.addLog("  Read "+str(len(map_data))+" map data points.")
        no_scalars = False
        self.progress = self.progress + progressIncrement
        self.updateProgress()
      except AttributeError:
        self.addLog("  No map data points (scalars) found in volume "+str(volumeCounter)+".")
        no_scalars = True

      if self.abortRequested:
        return False

      try:
        plaintext = volume.find("Normals").text
        vertexnormals = self.TextToFloat(plaintext)

        self.addLog("  Read "+str(len(vertexnormals))+" vertex normals.")
        self.progress = self.progress + progressIncrement
        self.updateProgress()
      except AttributeError:
        self.addLog("  ERROR: No vertex normals found in volume "+str(volumeCounter)+".")
        return False

      if self.abortRequested:
        return False

      try:
        plaintext = volume.find("Polygons").text
        triangles_all = self.TextToFloat(plaintext)
        # Change base from 1 to 0
        for i in range(len(triangles_all)):
          for j in range(3):
            triangles_all[i][j] = int(triangles_all[i][j]-1)
      except AttributeError:
        self.addLog("  ERROR: No polygon information found in volume "+str(volumeCounter)+".")
        return False

      # Triangles are binned into different "Surfaces of origin" (i.e. models in the file),
      # according to the separate table Surface_of_origin

      try:
        plaintext = volume.find("Surface_of_origin").text
        surface_of_origin_1tuple = self.TextToFloat(plaintext) # TextToFloat returns a list of "1-tuples" (lists of length 1): [[1.0], [2.0], [1.0], [0.0], ...]
        surface_of_origin = []
        for i in range(len(surface_of_origin_1tuple)):         # Convert this into a list of lists into a list of integers: [1, 2, 1, 0, ...]
          surface_of_origin.append(surface_of_origin_1tuple[i][0])

        # Initialize triangles as list of n empty lists, with n being the maximum surface number in surface_of_origin
        triangles = [ [] for _ in range(int(max(surface_of_origin)+1))]

        for i in range(len(surface_of_origin)):
          triangles[int(surface_of_origin[i])].append(triangles_all[i])

        self.addLog("  Read "+str(len(triangles_all))+" triangles in "+str(len(triangles))+" separate meshes.")
      except:
        self.addLog("  NOTE: No \"Surface of Origin\" information in file.")
        triangles = [[]]
        triangles[0]=triangles_all

      self.progress = self.progress + progressIncrement
      self.updateProgress()
      if self.abortRequested:
        return False

      for i in range(len(triangles)):
        meshName = "Velocity_"+str(volumeCounter)+"-"+str(i)
        self.addLog("Creating model "+meshName+":")

        modelNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLModelNode')

        if not no_scalars:
          names = ["Map data"]
          scalars = map_data
        else:
          names = []
          scalars = []

        if not self.CreateMesh(modelNode, vertices, vertexnormals, triangles[i], names, scalars):
          slicer.mrmlScene.RemoveNode(modelNode)
          return False

        self.progress = self.progress + (2*progressIncrement)/len(triangles)
        self.updateProgress()
        if self.abortRequested:
          return False

        # Ensite mesh coordinates are LPS, Slicer is RAS
        matrixLPStoRAS = [[-1, 0, 0, 0],
                          [ 0,-1, 0, 0],
                          [ 0, 0, 1, 0],
                          [ 0, 0, 0, 1]]
        self.transformNode(modelNode, matrixLPStoRAS)

        modelNode.SetName(meshName)
        modelNode.CreateDefaultDisplayNodes()

    self.addLog("Done.")
    self.progress = 100
    self.updateProgress()

    return True

  ################################################################################
  # CARTO 3 import
  #

  def readCarto(self, filename):
    self.progress = 0
    self.updateProgress()
    if not zipfile.is_zipfile(filename):
      self.addLog("File is not a valid zip archive: "+filename)
      return False
    if self.abortRequested:
      return False

    self.addLog("Importing CARTO 3 map:")
    tempDir = self.createTempDirectory()
    self.addLog("  Extracting archive "+filename+" to "+tempDir)

    with zipfile.ZipFile(filename, "r") as cartoArchive:
      fileList = cartoArchive.namelist()
      for singleFile in fileList:
        if singleFile.endswith(".mesh") or singleFile.endswith("_car.txt") or singleFile == "VisiTagExport/Sites.txt":
          cartoArchive.extract(singleFile, tempDir)
        if self.abortRequested:
          shutil.rmtree(tempDir)
          return False

    self.progress = 10
    self.updateProgress()

    progressSteps = len(os.listdir(tempDir))
    progressEnd = 100
    progressIncrement = ((progressEnd - self.progress)/progressSteps)

    for filename in os.listdir(tempDir):
      if filename.endswith(".mesh"):
        if not self.readCartoMesh(os.path.join(tempDir, filename)):
          shutil.rmtree(tempDir)
          return False
      if filename.endswith("_car.txt"):
        if not self.readCartoPoints(os.path.join(tempDir, filename)):
          shutil.rmtree(tempDir)
          return False
      self.progress = self.progress + progressIncrement
      self.updateProgress()
      if self.abortRequested:
        shutil.rmtree(tempDir)
        return False

    if os.path.exists(os.path.join(tempDir, "VisiTagExport/Sites.txt")):
      if not self.readCartoAblationSites(os.path.join(tempDir, "VisiTagExport/Sites.txt")):
        shutil.rmtree(tempDir)

    #Delete temp dir
    self.addLog("Cleaning up temporary files.")
    shutil.rmtree(tempDir)

    self.progress = 100
    self.updateProgress()
    self.addLog("Done.")

    return True

  def readCartoMesh(self, filename):
    meshName = ntpath.basename(filename)
    self.addLog("Reading "+meshName+":")
    section = "none"
    verticesText = ""
    trianglesText = ""
    scalarsText = ""
    attributesText = ""
    scalarLabels = ""

    with open(filename, "r", encoding="latin-1") as filehandle:
      for line in filehandle:
        if self.abortRequested:
          return False
        # Remove trailing newline and trailing and leading spaces
        line = re.sub("[\n]$", "", line)
        line = re.sub("[ ]*$", "", line)
        line = re.sub("^[ ]*", "", line)

        if len(line) == 0: # empty line
          continue
        if line[0] == ";": # comment line
          continue
        if line.find("[GeneralAttributes]") > -1:
          section = "general"
          continue
        if line.find("[VerticesSection]") > -1:
          section = "vertices"
          continue
        if line.find("[TrianglesSection]") > -1:
          section = "triangles"
          continue
        if line.find("[VerticesColorsSection]") > -1:
          section = "scalars"
          continue
        if line.find("[VerticesAttributesSection]") > -1:
          section = "attributes"
          continue

        if section == "general":
          # Look for scalar labels
          if line.find("ColorsNames") > -1:
            line = re.sub("^ColorsNames[ ]*=[ ]*", "", line)
            scalarLabels = re.split("[ ]*",line)
        if section == "vertices":
          # remove line number ("0 =")
          line = re.sub("[0-9]*[ ]*=[ ]*", "", line)
          # add "clean" line to string
          verticesText = verticesText+line+'\n'
        if section == "triangles":
          line = re.sub("[0-9]*[ ]*=[ ]*", "", line)
          trianglesText = trianglesText+line+'\n'
        if section == "scalars":
          line = re.sub("[0-9]*[ ]*=[ ]*", "", line)
          scalarsText = scalarsText+line+'\n'
        if section == "attributes":
          line = re.sub("[0-9]*[ ]*=[ ]*", "", line)
          attributesText = attributesText+line+'\n'

    verticesLong = self.TextToFloat(verticesText)
    vertices = []
    vertexnormals = []
    for i in range(len(verticesLong)):
      vertices.append([verticesLong[i][0], verticesLong[i][1], verticesLong[i][2]])
      vertexnormals.append([verticesLong[i][3], verticesLong[i][4], verticesLong[i][5]])
      if self.abortRequested:
        return False

    trianglesLong = self.TextToFloat(trianglesText)
    triangles = []
    for i in range(len(trianglesLong)):
      triangles.append([trianglesLong[i][0], trianglesLong[i][1], trianglesLong[i][2]])
      if self.abortRequested:
        return False

    if len(scalarsText) > 0:
      scalars = self.TextToFloat(scalarsText)
    else:
      scalars = []

    if len(attributesText) > 0:
      attributes = self.TextToFloat(attributesText)   # currently not used
    else:
      attributes = []

    self.addLog("  Read "+str(len(vertices))+" vertices, "+str(len(vertexnormals))+" vertex normals, and "+str(len(triangles))+" triangles.")
    self.addLog("  Read "+str(len(scalarLabels))+" sets of scalars: "+str(scalarLabels)+".")

    meshName = "CARTOmesh_"+re.sub(".mesh$", "", meshName)
    self.addLog("Creating model "+meshName+":")

    modelNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLModelNode')
    if not self.CreateMesh(modelNode, vertices, vertexnormals, triangles, scalarLabels, scalars):
      slicer.mrmlScene.RemoveNode(modelNode)
      return False

    self.transformCarto(modelNode)

    modelNode.SetName(meshName)
    modelNode.CreateDefaultDisplayNodes()

    return True

  def readCartoPoints(self, filename):
    pointsName = ntpath.basename(filename)
    self.addLog("Reading "+pointsName+":")

    fiducialsNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLMarkupsFiducialNode')
    fiducialsNode.GetMarkupsDisplayNode().SetVisibility(0)

    with open(filename, "r") as filehandle:
      for line in filehandle:
        if self.abortRequested:
          return False
        # Remove trailing newline and trailing and leading spaces
        line = re.sub("[\n]$", "", line)
        line = re.sub("[ ]*$", "", line)
        line = re.sub("^[ ]*", "", line)
        if len(line) == 0: # empty line
          continue
        lineElements = re.split("[ \t]*", line)
        if lineElements[0] == "VERSION_5_0" or lineElements[0] == "VERSION_4_0":
          pointsName = lineElements[1]
        if lineElements[0] == "P":
            pointNr = int(lineElements[2])
            pointX = float(lineElements[4])
            pointY = float(lineElements[5])
            pointZ = float(lineElements[6])
            unipolar = float(lineElements[10])
            bipolar = float(lineElements[11])
            lat = float(lineElements[12])
            n = fiducialsNode.AddFiducial(pointX, pointY, pointZ)
            fiducialsNode.SetNthControlPointLabel(n, "Point # "+str(pointNr)+" in "+pointsName)
            fiducialsNode.SetNthControlPointDescription(n, "Bipolar "+str(bipolar)+" / Unipolar "+str(unipolar)+" / LAT "+str(lat))
            fiducialsNode.SetNthControlPointLocked(n, 1)

    self.transformCarto(fiducialsNode)

    pointsName = "CARTOpoints_"+re.sub("_car.txt$", "", pointsName)
    fiducialsNode.SetName(pointsName)
    fiducialsNode.GetMarkupsDisplayNode().SetTextScale(0)
    fiducialsNode.GetMarkupsDisplayNode().SetVisibility(1)

    self.addLog("Created markup fiducials "+pointsName+".")
    fiducialsNode.CreateDefaultDisplayNodes()

    return True

  def readCartoAblationSites(self, filename):
    pointsName = ntpath.basename(filename)
    self.addLog("Reading "+pointsName+":")

    fiducialsNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLMarkupsFiducialNode')
    fiducialsNode.GetMarkupsDisplayNode().SetVisibility(0)

    with open(filename, "r") as filehandle:
      for line in filehandle:
        if self.abortRequested:
          return False
        # Remove trailing newline and trailing and leading spaces
        line = re.sub("[\n]$", "", line)
        line = re.sub("[ ]*$", "", line)
        line = re.sub("^[ ]*", "", line)
        if len(line) == 0: # empty line
          continue
        lineElements = re.split("[ \t]*", line)
        if lineElements[0] == "Session" or lineElements[0] == "VERSION_4_0":
          continue
        pointNr = int(lineElements[2])
        pointX = float(lineElements[3])
        pointY = float(lineElements[4])
        pointZ = float(lineElements[5])
        duration = float(lineElements[6])
        avgForce = float(lineElements[7])
        power = float(lineElements[8])
        fti = float(lineElements[9])
        n = fiducialsNode.AddFiducial(pointX, pointY, pointZ)
        fiducialsNode.SetNthControlPointLabel(n, "Ablation site # "+str(pointNr))
        fiducialsNode.SetNthControlPointDescription(n, "FTI "+str(fti)+" ("+str(duration)+" sec, "+str(power)+" W, "+str(avgForce)+" g")
        fiducialsNode.SetNthControlPointLocked(n, 1)

    self.transformCarto(fiducialsNode)

    pointsName = "CARTOablationsites"
    fiducialsNode.SetName(pointsName)
    fiducialsNode.GetMarkupsDisplayNode().SetTextScale(0)
    fiducialsNode.GetMarkupsDisplayNode().SetUseGlyphScale(False)
    fiducialsNode.GetMarkupsDisplayNode().SetGlyphSize(6)
    fiducialsNode.GetMarkupsDisplayNode().SetSelectedColor(1,0,0)
    fiducialsNode.GetMarkupsDisplayNode().SetVisibility(1)

    self.addLog("Created markup fiducials "+pointsName+".")
    fiducialsNode.CreateDefaultDisplayNodes()

    return True

  def transformCarto(self, node):
    # CARTO mesh is in LPS and seems to be additionally rotated 90 deg around the LR axis
    # Transform matrix from CARTO to Slicer is
    # -1.0  0.0  0.0  0.0
    #  0.0  0.0  1.0  0.0
    #  0.0  1.0  0.0  0.0
    #  0.0  0.0  0.0  1.0

    matrix = [[-1, 0,  0,  0],
              [0,  0,  1,  0],
              [0,  1,  0,  0],
              [0,  0,  0,  1]]
    self.transformNode(node, matrix)

  ################################################################################
  # RHYTHMIA import
  #
  def readRhythmia(self, filename):

    self.progress = 0
    self.updateProgress()

    tempDir = self.createTempDirectory()
    filenameStem = os.path.splitext(os.path.basename(filename))[0]
    archiveConcatenated = os.path.join(tempDir, filenameStem+".ALL")
    print(archiveConcatenated)
    if not self.concatenateRhythmiaFiles(filename, archiveConcatenated):
      shutil.rmtree(tempDir)
      return False

    if not self.expandBinaryPayloadFromRhythmiaArchive(archiveConcatenated , os.path.join(tempDir, "archive.xml"), tempDir):
      shutil.rmtree(tempDir)
      return False

    if not self.processRhythmiaXML(os.path.join(tempDir, "archive.xml"), tempDir):
      shutil.rmtree(tempDir)
      return False

    self.progress = 100
    self.updateProgress()

    # Delete temp dir
    self.addLog("Cleaning up temporary files.")
    shutil.rmtree(tempDir)

    return True

  def concatenateRhythmiaFiles(self, startfilename, targetfilename):
    # Get all files startfilename.000 , .001, .002, ...
    filePathStem = os.path.splitext(startfilename)[0]
    filesToAdd = []
    i=0
    while(os.path.isfile(filePathStem+"."+str(i).zfill(3))):
      filesToAdd.append(filePathStem+"."+str(i).zfill(3))
      i += 1
    progressIncrement = (20-self.progress) / len(filesToAdd)
    # Merge all these files into one (in the temp dir)
    with open(targetfilename, "wb", 0) as targetfile:
      for fileToAdd in filesToAdd:
        with open(fileToAdd, "rb") as fid:
          self.addLog("Reading file "+fileToAdd+" ...")
          targetfile.write(fid.read())
        self.progress += progressIncrement
        self.updateProgress()
        if self.abortRequested:
          return False
      targetfile.flush()
      os.fsync(targetfile.fileno())
    self.progress = 20
    self.updateProgress()
    return True

  def expandBinaryPayloadFromRhythmiaArchive(self, archiveFilename, xmlFilename, folder):
    # Parse the (completed, concatenated) Rhythmia archive archiveFilename and extract the binary chunks
    # into separate files in the directory folder
    # Also, write the remaining XML (stripped from binary data) into xmlFilename

    tagBinaryStart = re.compile(b"<inlinedbin .* BIN=[0-9]*>")
    tagBinaryEnd = re.compile(b"</inlinedbin>")

    pointer = 0
    xmlBuffer = ""
    binaryFilenamePattern =  "binary"

    # We are using a rather rough method here by loading the entire (multi-GB) archive into
    # (virtual) memory as one chunk. If this should cause problems with even larger archives in
    # the future, chunk-wise processing might be an option (caution: expressions spanning chunk boundaries)
    # See e.g. https://stackoverflow.com/questions/29052510/search-string-with-regex-in-large-binary-file-2-gb-or-more
    # and https://stackoverflow.com/questions/14289421/how-to-use-mmap-in-python-when-the-whole-file-is-too-big

    readChunkSize = 500 * 1024 * 1024 # 500 MB, arbitrary
    archiveBuffer = bytes("", 'ASCII') # explicite bytes() type cast necessary for Python3 compatability, see https://stackoverflow.com/questions/21689365/python-3-typeerror-must-be-str-not-bytes-with-sys-stdout-write/21689447
    readChunk = ""
    archiveFileSize = os.stat(archiveFilename).st_size
    progressStart = self.progress
    progressEnd = 50

    self.addLog("Unpacking archive ... (This can take several minutes and Slicer might appear unresponsive)")
    with open(archiveFilename, "rb") as archiveFile:
      while True:
        readChunk = archiveFile.read(readChunkSize)   # Read in chunks of 500 MB to allow processing of cancel requests between chunks
        if len(readChunk) == 0: # End of file
          break
        archiveBuffer += readChunk
        self.progress = progressStart + ((progressEnd-progressStart)*(len(archiveBuffer)/archiveFileSize))
        self.updateProgress()
        if self.abortRequested:
          return False

    self.progress = progressEnd
    self.updateProgress()

    progressStart = self.progress
    progressEnd = 80

    binaryFileCounter = 0

    while pointer < len(archiveBuffer):
      # locate beginning and end of next opening <inlinedbin> tag
      nextInlinedbinOpening = tagBinaryStart.search(archiveBuffer, pointer)

      # next <inlinedbin> tag found
      if nextInlinedbinOpening:
        openingTagStart = nextInlinedbinOpening.start()
        openingTagEnd = nextInlinedbinOpening.end()

        # copy everything from current seek pointer till the end of the opening tag
        # to the "clean" XML
        # Note: In Python3, the archiveBuffer is (binary) bytes, but the xmlBuffer is a string --> explicite decoding necessary
        xmlBuffer += archiveBuffer[pointer:openingTagEnd].decode('ASCII')

        # locate beginning and end of next closing <inlinedbin> tag, start search at
        # end of opening tag
        nextInlinedbinClosing = tagBinaryEnd.search(archiveBuffer, openingTagEnd)
        if nextInlinedbinClosing:
          closingTagStart = nextInlinedbinClosing.start()
          closingTagEnd = nextInlinedbinClosing.end()

          # write out binary payload
          binaryFileName = binaryFilenamePattern+str(binaryFileCounter).zfill(8)+".dat"
          binaryFileCounter += 1
          with open(os.path.join(folder, binaryFileName), "wb", 0) as binaryFile:
            binaryFile.write(archiveBuffer[openingTagEnd:closingTagStart])
            binaryFile.flush()
            os.fsync(binaryFile.fileno())

          # write filename into clean XML
          xmlBuffer += binaryFileName

          # set pointer to beginning of closing tag (next search from here)
          pointer = closingTagStart
        else:
          self.addLog("Premature end of archive file (closing </inlinedbin> tag not found)")
          xmlBuffer += archiveBuffer[pointer:].decode('ASCII')
          pointer = pointer = len(archiveBuffer)
          return False

      else: # no opening <inlinedbin> found
        # copy everything from current pointer location till end of buffer into clean XML
        xmlBuffer += archiveBuffer[pointer:].decode('ASCII')
        pointer = len(archiveBuffer)

      self.progress = progressStart+((progressEnd-progressStart)*(pointer/len(archiveBuffer)))
      self.updateProgress()
      if self.abortRequested:
        return False

    # Correct one remaining non-XML-conformant statement:
    # the <inlinedbin> tags contain an attribute declaration like BIN=580 (the number of bytes)
    # However, this should read BIN="580" in order to be standard-conformant

    xmlBuffer = re.sub(r'BIN=([0-9]*)', r'BIN="\1"', xmlBuffer)

    with open(xmlFilename, "w") as xmlFile:
      xmlFile.write(xmlBuffer)

    return True

   # convert voltage values from log(uV) to mV
  def calculateRhythmiaVoltage(self, x):
    return math.exp(x) / 1000

  def processRhythmiaXML(self, xmlFilename, folder):
    calculateRhythmiaVoltageVectorized = np.vectorize(self.calculateRhythmiaVoltage)
    matrixRhythmiaToSlicer = [[ 1, 0, 0, 0],
                              [ 0, 0,-1, 0],
                              [ 0, 1, 0, 0],
                              [ 1, 0, 0, 1]]

    tree = ET.parse(xmlFilename)
    root = tree.getroot()

    patientsList = []
    patients = list(self.findallRecursive(root, "Patient"))
    for patient in patients:
      patProperties = patient.find("PatientInfo").find("Properties")
      if patProperties is not None:
        patID = patProperties.find("PatientID").text
        patName = patProperties.find("NameLast").text+", "+patProperties.find("NameFirst").text
        patientsList.append([patID, patName])

        # Create patient in subject hierarchy
        subjectHierarchyNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)
        sceneRoot = subjectHierarchyNode.GetSceneItemID()
        subjectHierarchyNode.CreateSubjectItem(sceneRoot, patID+" ("+patName+")")
        currentSubjectHierarchyPatientID = subjectHierarchyNode.GetItemChildWithName(sceneRoot, patID+" ("+patName+")")

      studies = list(self.findallRecursive(patient, "Study"))
      for study in studies:
        studyName = study.find("Properties").find("Label").text

        # Create study in subject hierarchy
        subjectHierarchyNode.CreateStudyItem(currentSubjectHierarchyPatientID, studyName)
        currentSubjectHierarchyStudyID = subjectHierarchyNode.GetItemChildWithName(currentSubjectHierarchyPatientID, studyName)

        self.addLog("Processing study \""+studyName+"\" of patient ID "+patID+" ("+patName+")...")
        anatomies = list(self.findallRecursive(study, "Anatomy"))

        for anatomyNumber, anatomy in enumerate(anatomies):
          anatomyName = anatomy.find("Properties").find("Label").text
          if anatomyName == None:
            anatomyName = "unnamed"+str(anatomyNumber)
          anatomyTransformString = anatomy.find("Transform").text
          anatomyTransformMatrix = [[1, 0, 0, 0],
                                    [0, 1, 0, 0],
                                    [0, 0, 1, 0],
                                    [0, 0, 0, 1]]
          if anatomyTransformString != None:
            anatomyTransformNumbers = re.split("[ ]*", anatomyTransformString)
            i = 0
            for row in range(4):
              for col in range(4):
                anatomyTransformMatrix[col][row] = float(anatomyTransformNumbers[i])
                i += 1
          scalarLabels = []
          scalarValues = []

          meshes = list(self.findallRecursive(anatomy, "Mesh"))
          for mesh in meshes:
            if mesh.find("vertices").find("inlinedbin") is not None:
              # Geometry data exists for this mesh -> read filenames of extracted binary files
              verticesFilename = mesh.find("vertices").find("inlinedbin").text
              verticesLengthBytes = int(mesh.find("vertices").find("inlinedbin").attrib["BIN"])
              trianglesFilename = mesh.find("triangles").find("inlinedbin").text
              trianglesLengthBytes = int(mesh.find("triangles").find("inlinedbin").attrib["BIN"])
              triangleNormalsFilename = mesh.find("triangle_normals").find("inlinedbin").text
              triangleNormalsLengthBytes = int(mesh.find("triangle_normals").find("inlinedbin").attrib["BIN"])
              triangleFlagsFilename = mesh.find("triangle_flags").find("inlinedbin").text
              triangleFlagsLengthBytes = int(mesh.find("triangle_flags").find("inlinedbin").attrib["BIN"])

              with open(os.path.join(folder, verticesFilename), "rb") as verticesFile:
                rawBytes = verticesFile.read()
                if len(rawBytes) != verticesLengthBytes:
                  self.addLog("ERROR: Binary data size for vertices does not match. ("+verticesFilename+" : "+str(len(rawBytes))+" read / "+str(verticesLengthBytes)+" Bytes expected)")
                  return False
                vertices = np.array(struct.unpack('<{0}f'.format(int(len(rawBytes)/4)), rawBytes))    # Float32
                vertices = np.reshape(vertices, (-1,6))
                anatomyVertices = np.array(vertices[:, 0:3])
                anatomyVertexnormals = np.array(vertices[:, 3:6])

              with open(os.path.join(folder, trianglesFilename), "rb") as trianglesFile:
                rawBytes = trianglesFile.read()
                if len(rawBytes) != trianglesLengthBytes:
                  self.addLog("ERROR: Binary data size for triangles does not match. ("+trianglesFilename+" : "+str(len(rawBytes))+" read / "+str(verticesLengthBytes)+" Bytes expected)")
                  return False
                anatomyTriangles = np.array(struct.unpack('<{0}i'.format(int(len(rawBytes)/4)), rawBytes))  # SignedInt32
                anatomyTriangles = np.reshape(anatomyTriangles, (-1,3))

          engineOutputs =  list(self.findallRecursive(anatomy, "EngineOutput"))
          for engineOutput in engineOutputs:
            voltages = list(self.findallRecursive(engineOutput, "Voltage"))
            activations = list(self.findallRecursive(engineOutput, "Activation"))

            for voltage in voltages:
              if voltage.find("values").find("inlinedbin") is not None:
                scalarFilename = voltage.find("values").find("inlinedbin").text
                scalarLengthBytes = int(voltage.find("values").find("inlinedbin").attrib["BIN"])

                with open(os.path.join(folder, scalarFilename), "rb") as scalarFile:
                  rawBytes = scalarFile.read()
                  if len(rawBytes) != scalarLengthBytes:
                    self.addLog("ERROR: Binary data size for electrogram data does not match. ("+scalarFilename+" : "+str(len(rawBytes))+" read / "+str(verticesLengthBytes)+" Bytes expected)")
                    return False
                  scalarLabels.append("Voltage_"+voltage.find("Properties").find("SrcEgmType").text)

                  scalars = np.array(struct.unpack('<{0}f'.format(int(len(rawBytes)/4)), rawBytes))

                  # convert values from log(uV) to mV, using a vectorized function to conveniently iterate over the entire array
                  scalars = calculateRhythmiaVoltageVectorized(scalars)
                  if scalarValues == []:
                    for i in range(len(scalars.tolist())):
                      scalarValues.append([scalars.tolist()[i]])  # Make it a list of lists of length one
                  else:
                    for i in range(len(scalars.tolist())):
                      scalarValues[i].append(scalars.tolist()[i])  # append to the existing lists

            for activation in activations:
              if activation.find("values").find("inlinedbin") is not None:
                scalarFilename = activation.find("values").find("inlinedbin").text
                scalarLengthBytes = int(activation.find("values").find("inlinedbin").attrib["BIN"])

                with open(os.path.join(folder, scalarFilename), "rb") as scalarFile:
                  rawBytes = scalarFile.read()
                  if len(rawBytes) != scalarLengthBytes:
                    self.addLog("ERROR: Binary data size for electrogram data does not match. ("+scalarFilename+" : "+str(len(rawBytes))+" read / "+str(verticesLengthBytes)+" Bytes expected)")
                    return False
                  scalarLabels.append("LAT_"+activation.find("Properties").find("SrcEgmType").text)

                  scalars = np.array(struct.unpack('<{0}f'.format(int(len(rawBytes)/4)), rawBytes))
                  if scalarValues == []:
                    for i in range(len(scalars.tolist())):
                      scalarValues.append([scalars.tolist()[i]])  # Make it a list of lists of length one
                  else:
                    for i in range(len(scalars.tolist())):
                      scalarValues[i].append(scalars.tolist()[i])  # append to the existing lists

          # Create mesh for this anatomy
          meshName = "RHYTHMIAmesh_"+anatomyName
          self.addLog("Creating model "+meshName+":")
          modelNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLModelNode')
          # Insert below correct study in subject hierarchy
          subjectHierarchyNode.CreateItem(currentSubjectHierarchyStudyID, modelNode)

          if not self.CreateMesh(modelNode, anatomyVertices.tolist(), anatomyVertexnormals.tolist(), anatomyTriangles.tolist(), scalarLabels, scalarValues):
            slicer.mrmlScene.RemoveNode(modelNode)
            return False

          # individual transform saved in the archive (e.g. when segmentations have been aligned on the workstation)
          self.transformNode(modelNode, anatomyTransformMatrix)

          # general transform from Rhythmia to Slicer coordinate systems
          self.transformNode(modelNode, matrixRhythmiaToSlicer)
          modelNode.SetName(meshName)
          modelNode.CreateDefaultDisplayNodes()

        pointSets = list(self.findallRecursive(study, "AnnotationPointSet"))
        unnamedCounter = 0
        for pointSet in pointSets:
          pointSetName = pointSet.find("Properties").find("OverrideLabel").text
          points = list(self.findallRecursive(pointSet, "AnnotationPoint"))
          pointsList = []
          for point in points:
            if point.find("Properties").find("OverrideLabel") is not None:
              pointLabel = point.find("Properties").find("OverrideLabel").text
            else:
              if point.find("Properties").find("Label") is not None:
                pointLabel = point.find("Properties").find("Label").text
              else:
                pointSetName = "unnamed_points"+str(unnamedCounter).zfill(2)
                unnamedCounter += 1

            pointXYZ = point.find("xyz").text
            pointXYZlist = re.split("[ ]*", pointXYZ)
            pointsList.append([pointLabel, float(pointXYZlist[0]), float(pointXYZlist[1]),float(pointXYZlist[2])])

          if(len(pointsList) > 0):
            # Create new Fiducial set here with name pointSetName
            self.addLog("Creating annotation points \""+pointSetName+"\".")
            fiducialNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLMarkupsFiducialNode')
            # Put it below study in subject hierarchy and name it
            subjectHierarchyNode.CreateItem(currentSubjectHierarchyStudyID, fiducialNode)
            fiducialNode.SetName(pointSetName)

            # Insert points
            for fiducial in pointsList:
              fidNumber = fiducialNode.AddFiducial(fiducial[1], fiducial[2], fiducial[3])
              fiducialNode.SetNthControlPointLabel(fidNumber, fiducial[0])
              fiducialNode.SetNthControlPointLocked(fidNumber, True)

            # match coordinate systems RHYTHMIA -> Slicer
            self.transformNode(fiducialNode, matrixRhythmiaToSlicer)

    return True