"""
    car2.py
    Darren Strash
    A car that moves when pressing buttons!
"""

from cs110graphics import *

WIN_WIDTH = 600
WIN_HEIGHT = 600

BUTTON_RADIUS = 15
BUTTON_COLOR = "lightgray"

class Car(EventHandler):
    """ A car that moves when we click buttons. """

    def __init__(self, win, color, width, length, cen):
        EventHandler.__init__(self)

        self._win = win
        self._width = width
        self._length = length
        self._center = cen

        self._color = color
        self._distance = 30

        self._body = Rectangle(self._win, self._width, self._length,
                               self._center)
        self._body.set_fill_color(self._color)
        self._body.set_depth(6)

        right_light_center = (self._center[0] + self._width//3, \
                              self._center[1] + self._length//4)
        self._right_light = Circle(self._win, self._width//12, right_light_center)
        self._right_light.set_depth(5)
        self._right_light.set_fill_color("lightgray")
        self._win.add(self._right_light)

        left_light_center = (self._center[0] + self._width//3, \
                             self._center[1] - self._length//4)
        self._left_light = Circle(self._win, self._width//12, left_light_center)
        self._left_light.set_depth(5)
        self._left_light.set_fill_color("lightgray")
        self._win.add(self._left_light)

        self._body.add_handler(self)
        self._right_light.add_handler(self)
        self._left_light.add_handler(self)
        self._win.add(self._body)

        self._components = [self._body, self._right_light, self._left_light]

        #Give the heading / direction car is facing
        # x = 1 means moving right, x = -1 means moving left (0 means not moving in dir)
        # y = 1 means moving down, y = -1 means moving up (0 means not moving in dir)
        self._heading_x = 1
        self._heading_y = 0

    """
        A brief note on turning: when turning right, y = new

          (x,y) = (0,-1)
                ^
    (-1, 0) <       > (1,0)
                v
              (0, 1)
    """

    def move(self, direction):
        """ Move car forward/backward a custom distance."""

        change_x = 0
        change_y = 0
        if direction == "forward":
            change_x = self._heading_x * self._distance
            change_y = self._heading_y * self._distance

            for component in self._components:
                component.move(change_x, change_y)
        elif direction == "backward":
            change_x = - self._heading_x * self._distance
            change_y = - self._heading_y * self._distance

            for component in self._components:
                component.move(change_x, change_y)
        elif direction == "left":
            pivot = self._body.get_pivot()
            new_heading_x = self._heading_y
            new_heading_y = self._heading_x * -1
            self._heading_y = new_heading_y
            self._heading_x = new_heading_x

            for component in self._components:
                component.set_pivot(pivot)
                component.rotate(90)
        elif direction == "right":
            pivot = self._body.get_pivot()
            new_heading_x = -1 * self._heading_y
            new_heading_y = self._heading_x
            self._heading_y = new_heading_y
            self._heading_x = new_heading_x

            for component in self._components:
                component.set_pivot(pivot)
                component.rotate(270)

    def move_forward(self):
        self.move("forward")

    def move_backward(self):
        self.move("backward")

    def turn_left(self):
        self.move("left")

    def turn_right(self):
        self.move("right")

class Button(EventHandler):
    """ A button that when clicked will make a car move in a given direction. """

    def __init__(self, window, trigger, radius, color, center):
        """ Initialize the button's attributes. """
        EventHandler.__init__(self)
        self._win = window
        self._trigger = trigger
        self._radius = radius
        self._center = center
        self._color = color
        self._circle = Circle(self._win, self._radius, self._center)
        self._circle.set_fill_color(self._color)
        self._win.add(self._circle)
        self._circle.add_handler(self)

    def handle_mouse_press(self, event):
        """ Change the button color and move the car when mouse button pressed. """
        self._circle.set_fill_color("green")
        self._trigger()

    def handle_mouse_release(self, event):
        """ Change the button color back when releasing mouse button. """
        self._circle.set_fill_color(self._color)

def main(win):
    """The main function."""

    win.set_width(WIN_WIDTH)
    win.set_height(WIN_HEIGHT)

    car = Car(win, "yellow", 100, 40, (100, 500))

    forward_button  = Button(win, car.move_forward, BUTTON_RADIUS, BUTTON_COLOR, (100,20))
    backward_button = Button(win, car.move_backward, BUTTON_RADIUS, BUTTON_COLOR, (100,70))
    left_button     = Button(win, car.turn_left, BUTTON_RADIUS, BUTTON_COLOR, (75,45))
    right_button    = Button(win, car.turn_right, BUTTON_RADIUS, BUTTON_COLOR, (125,45))

if __name__ == '__main__':
    """When using cs110graphics, replace the usual line with this one:"""
    StartGraphicsSystem(main)


"""Climate Data Online aggregation and graphing"""

from csv import reader as csv_reader
from datetime import date
from pygal import Box, Line

__all__ = [
    'render_graphs',
    'aggregate_monthly_data',
    'monthly_total_precip_line',
    'monthly_avg_min_max_temp_line',
    'monthly_max_temps_box',
]

# 12-member list of the names of the months, i.e. ["January", "February", ...]

MONTH_NAMES = [date(2016, month, 1).strftime('%B') for month in range(1, 13)]

def render_graphs(csv_data, append_titles=""):
    """
    Convenience function. Gets the aggregated `monthlies` data from
    `aggregate_monthly_data(csv_data)` and returns a dict of graph
    titles mapped to rendered SVGs from `monthly_total_precip_line()`
    and `monthly_avg_min_max_temp_line()` using the `monthlies` data.
    """

    monthlies = aggregate_monthly_data(csv_data)

    return {
        graph.config.title: graph.render()
        for graph in [
            monthly_total_precip_line(monthlies, append_titles),
            monthly_avg_min_max_temp_line(monthlies, append_titles),
            monthly_max_temps_box(monthlies, append_titles),
        ]
    }

def aggregate_monthly_data(csv_data):
    """
    Pass your `csv_data` as an iterable whose members are individual
    lines of data (e.g. using a generator returned by the `iter_lines()`
    method of a `requests` library `Response` object) from a Climate
    Data Online (CDO)-style CSV file. Your CSV file must include the
    date (`DATE`), precipitation (`PRCP`), minimum temperature (`TMIN`),
    and maximum temperature (`TMAX`). The first line of your data file
    must be a header line.

    Returns a 12-member list of structured monthly data, each of which
    is a dict containing

    - `days_of_data`,
    - `precipitation_total`,
    - `min_temperature_total`,
    - `max_temperature_total`,
    - `all_min_temperatures`, and
    - `all_max_temperatures`.
    """

    csv_data = csv_reader(csv_data)

    header_row = next(csv_data)
    date_index = header_row.index('DATE')
    prcp_index = header_row.index('PRCP')
    tmin_index = header_row.index('TMIN')
    tmax_index = header_row.index('TMAX')

    monthlies = [dict(days_of_data=0, precipitation_total=0,
                      min_temperature_total=0, max_temperature_total=0,
                      all_min_temperatures=[], all_max_temperatures=[])
                 for _ in range(12)]

    for data_row in csv_data:
        row_month = int(data_row[date_index][4:6])
        row_prcp = int(data_row[prcp_index])
        row_tmin = int(data_row[tmin_index])
        row_tmax = int(data_row[tmax_index])

        monthly = monthlies[row_month - 1]
        monthly['days_of_data'] += 1
        monthly['precipitation_total'] += row_prcp
        monthly['min_temperature_total'] += row_tmin
        monthly['max_temperature_total'] += row_tmax
        monthly['all_min_temperatures'].append(row_tmin)
        monthly['all_max_temperatures'].append(row_tmax)

    return monthlies

def monthly_total_precip_line(monthlies, append_title=""):
    """
    Given `monthlies` data as returned by `aggregate_monthly_data()`,
    returns a Pygal line graph of precipitation totals for each month.
    """

    graph = Line(title="Precipitation" + append_title,
                 x_labels=MONTH_NAMES, x_label_rotation=90)

    graph.add("Precip(mm)", [monthly['precipitation_total'] / 10.
                             for monthly in monthlies])

    return graph

def monthly_avg_min_max_temp_line(monthlies, append_title=""):
    """
    Given `monthlies` data as returned by `aggregate_monthly_data()`,
    returns a Pygal line graph of average minimum and average maximum
    temperatures for each month.
    """

    graph = Line(title="Average High-Low Temperature" + append_title,
                 x_labels=MONTH_NAMES, x_label_rotation=90)

    graph.add("Avg High(C)", [monthly['max_temperature_total'] / 10. /
                              monthly['days_of_data']
                              for monthly in monthlies])

    graph.add("Avg Low(C)", [monthly['min_temperature_total'] / 10. /
                             monthly['days_of_data']
                             for monthly in monthlies])

    return graph

def monthly_max_temps_box(monthlies, append_title=""):
    """
    Given `monthlies` data as returned by `aggregate_monthly_data()`,
    returns a Pygal box-and-whisker plot graph of the range of high
    temperatures for each month.
    """

    graph = Box(title="High Temperature Ranges" + append_title,
                show_legend=False, x_labels=MONTH_NAMES, x_label_rotation=90,
                y_title=u"\u00b0C")

    for i, month_name in enumerate(MONTH_NAMES):
        graph.add(month_name, monthlies[i]['all_max_temperatures'])

    return graph


import json
import os
import sys
import requests

def abort(message):
    print('Error:{}'.format(message))
    sys.exit(1)

def shaping_text(text):
    # ファイル名に使えない文字を除外
    invalid_chars = u'\\/:*?"<>|'
    for invalid_char in invalid_chars:
        shaping_text = text.replace(invalid_char, u'')
    return shaping_text

def get(url, params, headers):
    get_query = requests.get(url, params=params, proxies=proxies, headers=headers)
    return get_query

def print_response(query_data, title=''):
    c = query_data.status_code
    h = query_data.headers
    print('{} Response={}, Detail={}'.format(title,c,h))

# エラーコード出たときの処理
def assert_response(query_data, title=''):
    error_code = query_data.status_code
    h = query_data.headers
    if error_code < 200 or error_code > 299:
        error_message = '{} Response={}, Detail={2}'.format(title,c,h)
        print(error_message)

class Article:
    def __init__(self, response_data):
        self._title      = response_data['title']
        self._html_body  = response_data['rendered_body']
        self._md_body    = response_data['body']
        self._tags       = response_data['tags']
        self._created_at = response_data['created_at']
        self._updated_at = response_data['updated_at']
        self._url        = response_data['url']
        user             = response_data['user']
        self._userid     = user['id']
        self._username   = user['name']

    def save_as_markdown(self):
        title = shaping_text(self._title)
        body  = self._md_body
        filename = '{}.md'.format(title)
        fullpath = os.path.join(MYDIR, filename)
        with open(fullpath, 'w') as f:
            f.write(body)

MYDIR = os.path.abspath(os.path.dirname(__file__)) + '/QiitaArticles'

headers = {
    'content-type'  : 'application/json',
    'charset'       : 'utf-8',
    'Authorization' : 'Bearer {0}'.format('token')
}

url = 'https://qiita.com/api/v2/authenticated_user/items'
params = {
    'page'     : 1,
    'per_page' : 100,
}

qiita_article_data = get(url, params, headers)
assert_response(qiita_article_data)
print_response(qiita_article_data)

items = qiita_article_data.json()
print('{} entries.'.format(len(items)))
for i, item in enumerate(items):
    print('{} saving...'.format(i+1, len(items)))
    article = Article(item)
    article.save_as_markdown()


"""App"""

import logging
import sys

from django.apps import AppConfig

logger = logging.getLogger(__name__)

class CamerasConfig(AppConfig):
    """App Config"""

    name = 'vision_on_edge.cameras'

    def ready(self):
        """App ready

        Import signals and create some demo objects.
        """

        if 'runserver' in sys.argv:
            # pylint: disable=C0415
            from .models import Camera
            from ..locations.models import Location
            # pylint: enable=C0415

            logger.info("App ready ready while running server")

            create_demo = True

            if create_demo:
                logger.info("Creating a demo camera object.")
                # Demo Location should be created already
                demo_locations = Location.objects.filter(is_demo=True)
                if not demo_locations.exists():
                    return
                Camera.objects.update_or_create(
                    name="Demo Video",
                    is_demo=True,
                    defaults={
                        'rtsp': 'sample_video/video.mp4',
                        'area': "",
                        'location': demo_locations.first()
                    })

                logger.info("Creating demo objects... end")

            logger.info("App ready end while running server")


# coding: utf-8
import skvideo.io
import numpy as np
from PIL import Image
import os
import json

def extract_video_frame(video_file_path="/disk/ZhiJiang_VideoQA_Pytorch/data/processed/video/", extract_image_path="/disk/video_qa_B_40/"):
    """
    extract video frames to images
    """
    split_dict = {}
    if not os.path.exists(extract_image_path):
        os.mkdir(extract_image_path)
    files = os.listdir(video_file_path)
    a = len(files)
    b = 1
    for f in files:
        try:
            split_dict[f.split('.mp4')[0]] = []
            video_data = skvideo.io.vread(os.path.join(video_file_path ,f))
            length = len(video_data)
            j = 1
            for i in list(list(np.linspace(0, length-1, num=40, endpoint=True, retstep=False, dtype=np.int64))):
                img = Image.fromarray(video_data[i])
                img.save(os.path.join(extract_image_path, f.split(".mp4")[0] + "_" + str(j) + ".png"), "PNG")
                split_dict[f.split('.mp4')[0]].append(f.split(".mp4")[0] + "_" + str(j) + ".png")
                j += 1
            print(str(b) + "/" + str(a))
            b += 1

        except ValueError:
            print(f)

    json.dump(split_dict, open('../data/processed/video_img_split.json', 'w'))

if __name__ == '__main__':
    extract_video_frame()


#
# [415] Add Strings
#
# https://leetcode.com/problems/add-strings/description/
#
# algorithms
# Easy (41.78%)
# Total Accepted:    61.1K
# Total Submissions: 146.4K
# Testcase Example:  '"0"\n"0"'
#
# Given two non-negative integers num1 and num2 represented as string, return
# the sum of num1 and num2.
#
# Note:
#
# The length of both num1 and num2 is < 5100.
# Both num1 and num2 contains only digits 0-9.
# Both num1 and num2 does not contain any leading zero.
# You must not use any built-in BigInteger library or convert the inputs to
# integer directly.
#

class Solution:
    def addStrings(self, num1, num2):
        """
        :type num1: str
        :type num2: str
        :rtype: str
        """
        L, R = len(num1)-1, len(num2)-1
        add, res = 0, ""
        while not (L < 0 and R < 0):
            if L < 0:
                cur = add + int(num2[R])
                R -= 1
            elif R < 0:
                cur = add + int(num1[L])
                L -= 1
            else:
                cur = add + int(num1[L]) + int(num2[R])
                L, R = L-1, R-1
            if cur >= 10:
                cur -= 10
                add = 1
            else:
                add = 0
            res += str(cur)
        if add == 1:
            res += str(add)
        return res[::-1]


"""
AoC
"""
import time
import math
import sys
from copy import copy, deepcopy

start_secs = time.time()
print('')

def get_left_num(s,i0):
  start_pos = -1
  end_pos = -1
  for i in range(i0-1,-1,-1):
    if s[i].isdigit():
      end_pos = i
      start_pos = i
      while s[i].isdigit():
        start_pos = i
        i -= 1
      return (s[start_pos:end_pos+1],start_pos,end_pos)
  return ('',-1,-1)

def get_right_num(s,i0):
  start_pos = -1
  end_pos = -1
  for i in range(i0, len(s)):
    if s[i].isdigit():
      end_pos = i
      start_pos = i
      while s[i].isdigit():
        end_pos = i
        i += 1
      return (s[start_pos:end_pos+1],start_pos,end_pos)
  return ('',-1,-1)

def explode_sn(s,ei,ej):
  exploding_pair = s[ei:ej] # e.g. [7,3]
  s = s[0:ei] + 'REPLACE' + s[ej:]

  # get left num, left num index, right num, right num index
  arr = get_list(exploding_pair)
  (left, li, lj) = get_left_num(s,ei-1)
  if len(left) > 0:
    left = int(left)
    s = s[0:li] + str(int(left+arr[0])) + s[lj+1:]

  (right, ri, rj) = get_right_num(s,ei+5)
  if len(right) > 0:
    right = int(right)
    s = s[0:ri] + str(int(right+arr[1])) + s[rj+1:]

  s = s.replace('REPLACE','0')

  return s

def one_split(s):
  # stop after doing one split!
  curr = 0
  while curr < len(s):
    starti = -1
    endi = -1
    for i in range(curr,len(s)):
      if s[i].isdigit():
        starti = i
        while s[i].isdigit():
          endi = i
          i += 1
        endi += 1
        break
    if s[starti:endi] == '':
      break
    num = int(s[starti:endi])
    if num >= 10:
      s1 = '[' + str(math.floor(num/2)) + ',' + str(math.ceil(num/2)) + ']'
      s = s[0:starti] + s1 + s[endi:]
      break
    curr = i
  return s

def reduce_sn(s):
  while True:
    len1 = len(s)
    # explode any?
    exploded = False
    left_parend_count = 0
    for i in range(len(s)):
      if s[i] == '[':
        left_parend_count += 1
        if left_parend_count == 5 and s[i+1] != '[':
          # assume that this is a plain [x,y] pair. okay?
          ei = i
          ej = i
          while s[ej] != ']':
            ej += 1
          ej += 1
          s = explode_sn(s,ei,ej)
          exploded = True
          break
      elif s[i] == ']':
        left_parend_count -= 1

    # split any if nothing exploded
    if not exploded:
      # try finding a split, but exit after one split, to let explode have priority
      s = one_split(s)

    len2 = len(s)
    if len1 == len2:
      break
  return s

def all_nums_in_order(arr):
  if isinstance(arr, list):
    all_nums_in_order(arr[0])
    all_nums_in_order(arr[1])
  else:
    print(arr)

def get_magnitude(arr):
  # The magnitude of a pair is 3 times the magnitude of its left element
  # plus 2 times the magnitude of its right element.
  # The magnitude of a regular number is just that number.
  if not isinstance(arr, list):
    return arr
  return get_magnitude(arr[0]) * 3 + get_magnitude(arr[1]) * 2

def level_four_elems(arr, i):
  if i == 4:
    print(arr)
    return
  if isinstance(arr, list):
    level_four_elems(arr[0], i+1)
    level_four_elems(arr[1], i+1)

def get_list(sn):
  if sn.isdigit():
    return int(sn)
  sn = sn[1:-1]
  comma_index = 0
  skips = 0
  for i in range(len(sn)):
    if sn[i] == '[':
      skips += 1
      continue

    if sn[i] == ',' and skips == 0:
      comma_index = i
      break

    if sn[i] == ',' and skips != 0:
      skips -= 1

  left = sn[0:comma_index]
  right = sn[comma_index+1:]

  return [ get_list(left) , get_list(right) ]

# read in input file
l=[]
my_file = open("inp.txt", "r", encoding='utf-8')
lines = my_file.readlines()
for line in lines:
  l.append(line.strip())

max_mag = -1
for i in range(0, len(l)-1):
  for j in range(i+1,len(l)):

    # try x + y
    stree = [ get_list(l[i]) , get_list(l[j]) ]
    stree_s = str(stree).replace(' ','')
    stree_s = reduce_sn(stree_s)
    stree = get_list(stree_s)
    mag = get_magnitude(stree)
    if mag > max_mag:
      max_mag = mag

    # try y + x
    stree = [  get_list(l[j]) , get_list(l[i]) ]
    stree_s = str(stree).replace(' ','')
    stree_s = reduce_sn(stree_s)
    stree = get_list(stree_s)
    mag = get_magnitude(stree)
    if mag > max_mag:
      max_mag = mag

print(max_mag)

#print( get_left_num('[[3,[2,[1,[7,3]]]],[6,[5,[4,[3,2]]]]]',10) ) # from [7,3]
#print( get_right_num('[[3,[2,[1,[7,3]]]],[6,[5,[4,[3,2]]]]]',14) ) # from [7,3]

print('')
end_secs = time.time()
print('--- ' + str(end_secs-start_secs) + ' secs ---')


import logging
import re
from pathlib import Path

from sqlalchemy.orm import sessionmaker

from src.bdd.Config import Config
from src.bdd.Project import Project, ProjectManager
from src.bdd.bdd import engine, Session

from src.bdd.Module import Module
from src.bdd.Import import Import

def index(project_dir, ensure_package=False, onedir=False):
    """Return a list that contains all the .py files
    part of the project (ie all subdirectory)

        :param project_dir: The root directory of the project.
    """

    if ensure_package and not project_dir.joinpath('__init__.py').exists():
        return []

    path_list = []

    for child in project_dir.iterdir():
        if child.suffix == '.py':
            path_list.append(child)
        if child.is_dir() and not onedir:
            path_list += index(child, True)

    return path_list

IGNORE_DIRS = ['site-packages', '__pycache__']

def create_project(project):
    session = Session()

    # check if project exists.
    query_result = session.query(Project.path).filter_by(name=project.name).first()
    if query_result:
        print(f"{project.name} already exists at {query_result[0]}")
        return

    project_path = Path(project.path).resolve()

    # index external modules as project
    # Way too heavy, we are gonna change that!
    if not project.external:
        search_paths = project.config.get_python_module_search_path()
        for string_path in search_paths:
            path = Path(string_path).resolve()
            match1, match2 = re.search(str(path), str(project_path)), re.search(str(project_path), str(path))
            if match1 or match2:
                continue

            if path.is_dir():
                results = list(path.glob('*.py'))
                if results:
                    print(f"Indexing {path.name} as single directory.")
                    create_project(Project(name=path.name,
                                           path=str(path.resolve()),
                                           external=True,
                                           onedir=True,
                                           config=Config(python_home=project.config.python_home)))
                for child in path.iterdir():
                    if child.joinpath('__init__.py').exists():
                        version = project.config.get_version(child)
                        create_project(Project(name=child.name,
                                               path=str(child.resolve()),
                                               external=True,
                                               version=version,
                                               config=Config(python_home=project.config.python_home)))

    # index files

    # index modules
    print(f"Indexing project {project.name}...")
    modules_path = index(Path(project.path), onedir=project.onedir)

    if not modules_path:
        print(f"{project.name} is not a python project.")
        return

    session.add(project)

    for module_path in modules_path:
        project.add_module(module_path)

    # Update imports
    project.bind_imports(session)

    # add project to database
    session.commit()

def remove_project(project_name):

    session = Session()

    # check if project exists.
    query_result = session.query(Project).filter_by(name=project_name).first()
    if not query_result:
        print(f"{project_name} doesn't not exist.")
        return

    session.delete(query_result)
    session.commit()

def purge():
    """Empty database. CAN'T BE UNDONE!"""

    print("Do you really want to purge? (y/n)")
    answer = input()

    if answer == 'y':
        print("Purging...")
        session = Session()

        query_results = session.query(Project).all()

        for project in query_results:
            print(f"Removing {project.name}")
            session.delete(project)

        session.commit()

def bind_import(project_name):

    session = Session()

    # check if project exists.
    query_result = session.query(Project).filter_by(name=project_name).first()
    if not query_result:
        print(f"{project_name} doesn't not exist.")
        return

    query_result.bind_imports(session)

if __name__ == '__main__':
    create_project(Project(name="AutoComplete", path="/home/pglandon/PycharmProjects/AutoComplete/src"))
    # remove_project("AutoComplete")
    purge()
    # create_project(Project(name="AutoComplete", path="/home/pglandon/PycharmProjects/AutoComplete/src",
    #                        config=Config(python_home="/home/pglandon/PycharmProjects/AutoComplete/venv")))
    # bind_import("AutoComplete")

    logging.basicConfig(level=logging.INFO)
    ProjectManager.initialize(Session)

    logging.basicConfig(level=logging.DEBUG)
    remove_project('AutoComplete')

    ProjectManager().register_project('AutoComplete', "/home/pglandon/PycharmProjects/AutoComplete/",
                                      external=False, fast=True,
                                      from_path="/home/pglandon/PycharmProjects/AutoComplete/src")


#!/usr/bin/env python

'''
This small module handles some error reporting for several
python classes.

The module assumes that the callingClass provides these methods:

    o callingClass.name()       -- returns the string name of the class
    o callingClass.log()        -- returns a Message() object member that
                                   is used a 'log' destination

Also, the callingClass must have a class dictionary, _dictErr, in which each
key has a dictionary of 'action', 'error', 'exitCode':

    callingClass._dictErr = {
        'someKey':      {
            'action':   'some action being performed,',
            'error':    'the error that occurred',
            'exitCode': <int>
        }
    }

'''

import inspect
import sys

from pfmisc._colors import Colors
from pfmisc.message import Message

class slog(object):
    """
    A simple class that simply appends to an internal
    'payload' each time it is called.
    """

    def syslog(self, *args):
        if len(args):
            self.b_syslog = args[0]
        else:
            return self.b_syslog

    def __init__(self, *args, **kwargs):
        self.str_payload    = ""
        self.b_syslog       = False

    def clear(self):
        self.str_payload    = ""

    def __call__(self, astr):
        self.str_payload += str(astr)

    def border_draw(self):
        l_padding = 2
        r_padding = 4

        msg_list    = self.str_payload.split('\n')
        msg_list    = [ x.replace('\t', '       ') for x in msg_list]
        l_c0        = [x.count('\x1b[0;')*7 for x in msg_list]
        l_c1        = [x.count('\x1b[1;')*7 for x in msg_list]
        l_nc        = [x.count('\x1b[0m')*4 for x in msg_list]
        l_offset    = [x + y + z for x, y, z in zip(l_c0, l_c1, l_nc)]
        msg_listNoEsc   = [(len(x) - w) for x, w in zip(msg_list, l_offset)]
        width       = max(msg_listNoEsc)
        h_len       = width + l_padding + r_padding
        top_bottom  = ''.join(['+'] + ['-' * h_len] + ['+']) + '\n'
        top         = ''.join(['┌'] + ['─' * h_len] + ['┐']) + '\n'
        bottom      = ''.join(['└'] + ['─' * h_len] + ['┘']) + '\n'

        result      = top

        for m, l in zip(msg_list, msg_listNoEsc):
            spaces   = h_len - l
            l_spaces = ' ' * l_padding
            r_spaces = ' ' * (spaces - l_padding)
            result += '│' + l_spaces + m + r_spaces + '│\n'

        result += bottom
        return result

    def __repr__(self):
        return self.str_payload

def report(     callingClass,
                astr_key,
                ab_exitToOs     = 1,
                astr_header     = "",
                **kwargs
                ):
    '''
    Error handling.

    Based on the <astr_key>, error information is extracted from
    _dictErr and sent to log object.

    If <ab_exitToOs> is False, error is considered non-fatal and
    processing can continue, otherwise processing terminates.

    '''

    astr_header         = ""
    ab_exitToOS         = False
    ab_drawBox          = False
    for k, v in kwargs.items():
        if k == 'header':   astr_header     = v
        if k == 'exitToOS': ab_exitToOS     = v
        if k == 'drawBox':  ab_drawBox      = v

    if ab_drawBox:
        log     = slog()
    else:
        log     = Message()

    b_syslog    = log.syslog()
    log.syslog(False)
    if ab_exitToOS: log( Colors.RED +    ":: FATAL ERROR :: " + Colors.NO_COLOUR )
    else:           log( Colors.YELLOW + "::   WARNING   :: " + Colors.NO_COLOUR )
    if len(astr_header): log( Colors.BROWN + astr_header + Colors.NO_COLOUR )
    log( "\n" )
    log( "\tSorry, some error seems to have occurred in:\n\n\t\t<" )
    try:
        caller = inspect.stack()[3][4][0].strip()
    except:
        caller = '__main__'
    log( Colors.LIGHT_GREEN + ("%s" % callingClass.__class__.__name__) + Colors.NO_COLOUR + "::")
    log( Colors.LIGHT_CYAN + ("%s" % caller) + Colors.NO_COLOUR)
    log( ">\n\n")

    log( "\tWhile %s\n" % callingClass._dictErr[astr_key]['action'] )
    log( "\t%s\n" % callingClass._dictErr[astr_key]['error'] )
    log( "\n" )
    log( "Returning with error code %s%d%s" % (
                    Colors.YELLOW,
                    callingClass._dictErr[astr_key]['exitCode'],
                    Colors.NO_COLOUR
                    )
    )

    if ab_drawBox:
        print(log.border_draw())
    if ab_exitToOS:
        sys.exit( callingClass._dictErr[astr_key]['exitCode'] )
    log.syslog(b_syslog)
    return callingClass._dictErr[astr_key]['exitCode']

def fatal( callingClass, astr_key, **kwargs ):
    '''
    Convenience dispatcher to the error_exit() method.

    Will raise "fatal" error, i.e. terminate script.
    '''
    kwargs['exitToOS']  = True
    report( callingClass, astr_key, **kwargs )

def warn( callingClass, astr_key, **kwargs ):
    '''
    Convenience dispatcher to the error_exit() method.

    Will raise "warning" error, i.e. script processing continues.
    '''
    kwargs['exitToOS']  = False
    report( callingClass, astr_key, **kwargs )