#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Author: Chris Ward <cward@redhat.com>

'''
Instructions for getting your OAuth2 client_secret and credentials
1) Go to https://console.developers.google.com
2) Open 'Credentials' page
3) Create Client ID - Type: "Other" - Name: Airtable
4) Then, on the summary overview page, download the key as JSON
5) Then the first time you send an email a browser window will
   pop open and you must agree to the request

Instructions:
    https://developers.google.com/sheets/api/quickstart/python
    https://developers.google.com/drive/v3/web/quickstart/python
'''

# FIXME: RENAME THIS TO GOOGLE since we're using contacts, calendar,
# spreadsheet....

import logging
import ipdb  # NOQA
import mimetypes
import os
import time
import sys

from comdev.lib import load_config, expand_path

# FIXME: reconcile this! use one library?
from apiclient import discovery
import base64
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
import httplib2
import oauth2client
from oauth2client import client, tools

def get_credentials(app_name, path_credentials, client_secret, scopes):
    ''' '''
    store = oauth2client.file.Storage(path_credentials)
    credentials = store.get()
    if not credentials or credentials.invalid:
        flow = client.flow_from_clientsecrets(client_secret, scopes)
        flow.user_agent = app_name
        # there is some strange error where something within tools.run_flow
        # looks for arguments in argv... and crashes since we have arbitrary
        # argv for whatever CLI ui used by caller
        _argv = sys.argv.copy()
        sys.argv = sys.argv[0:1]
        credentials = tools.run_flow(flow, store)
        sys.argv = _argv
        log.info('Storing credentials to ' + str(credentials))
    return credentials

class Gmailer(object):
    def __init__(self, app_name, sender=None):
        config = load_config(app_name)
        paths = {k: expand_path(x) for k, x in config['paths'].get().items()}

        self.path_logs = paths['logs']

        #self._init_logging()

        config = self.config = config['gmail'].get()

        self.app_name = app_name
        self.sender = sender or self.config['sender']
        self.gmail_app = config['app_name']
        self.scopes = config['scopes']
        self.path_client_secret = os.path.expanduser(
            config['client_secret'])
        self.path_credentials = os.path.expanduser(
            config['credentials'])

        self.credentials = get_credentials(
            self.app_name, self.path_credentials, self.path_client_secret,
            self.scopes)

    def _init_logging(self):
        # FIXME: This isn't working as expected; logging calls are still
        # propagating
        logfile = os.path.join(self.path_logs, 'gmail.log')
        fhandler = logging.FileHandler(logfile)
        fhandler.setLevel(logging.CRITICAL)
        fhandler.propagate = False
        log.addHandler(fhandler)

    def send(self, to, subject, body_txt, body_html=None, sleep=0,
             attachments=None):
        ''' '''
        # FIXME: accept list of sendto email addresses
        sender = self.sender
        log.critical(
            'FROM: {}\nTO: {}\nSUBJ: {}\nBODY TEXT: {}\n{}'.format(
                sender, to, subject, body_txt, '-'*70))
        if not body_html:
            body_html = body_txt.replace('\n', '<br/>')
        log.info('Sending the message to {}'.format(to))
        result = self._send_msg(to, subject, body_html, body_txt, attachments)
        time.sleep(sleep)
        return result

    def _send_msg(self, to, subject, msgHtml, msgPlain, attachments):
        ''' '''
        http = self.credentials.authorize(httplib2.Http())
        service = discovery.build('gmail', 'v1', http=http,
                                  cache_discovery=False)
        message1 = self._create_msg(to, subject, msgHtml, msgPlain, attachments)
        self._send_msg_internal(service, "me", message1)

    def _send_msg_internal(self, service, user_id, message):
        ''' '''
        try:
            message = service.users().messages().send(
                userId=user_id, body=message).execute()
            log.info('Message Id: %s' % message['id'])
            return message
        except Exception as error:
            log.error('An error occurred: %s' % error)

    def _create_msg(self, to, subject, msgHtml, msgPlain, attachments=None):
        '''
        attachments should be a list of paths
        '''
        sender = self.sender
        if attachments and isinstance(attachments, str):
            attachments = [attachments]
        else:
            attachments = list(attachments or [])

        msg = MIMEMultipart('alternative')
        msg['Subject'] = subject
        msg['From'] = sender
        msg['To'] = to
        msg.attach(MIMEText(msgPlain, 'plain'))
        msg.attach(MIMEText(msgHtml, 'html'))

        # append attachments if any
        for path in attachments:
            _attachment = self._prep_attachment(path)
            msg.attach(_attachment)

        raw = base64.urlsafe_b64encode(msg.as_bytes()).decode()
        #raw = raw.decode()
        body = {'raw': raw}
        return body

    def _prep_attachment(self, path):
        ''' '''
        if not os.path.exists(path):
            raise RuntimeError('Can not find attachment at {}'.format(path))

        content_type, encoding = mimetypes.guess_type(path)

        if content_type is None or encoding is not None:
            content_type = 'application/octet-stream'
        main_type, sub_type = content_type.split('/', 1)
        if main_type == 'text':
            fp = open(path, 'rb')
            message = MIMEText(fp.read(), _subtype=sub_type)
            fp.close()
        elif main_type == 'image':
            fp = open(path, 'rb')
            message = MIMEImage(fp.read(), _subtype=sub_type)
            fp.close()
        else:
            fp = open(path, 'rb')
            message = MIMEBase(main_type, sub_type)
            message.set_payload(fp.read())
            fp.close()
        filename = os.path.basename(path)
        message.add_header(
            'Content-Disposition', 'attachment', filename=filename)
        return message

log = logging.getLogger(__name__)

if __name__ == '__main__':
    ipdb.set_trace()


# -*- coding: utf-8 -*-
"""
   zeronimo.results
   ~~~~~~~~~~~~~~~~

   :copyright: (c) 2013-2017 by Heungsub Lee
   :license: BSD, see LICENSE for more details.

"""
from __future__ import absolute_import

from binascii import hexlify

from gevent.event import AsyncResult
from gevent.queue import Queue

from zeronimo.exceptions import TaskClosed
from zeronimo.helpers import make_repr
from zeronimo.messaging import BREAK, DONE, RAISE, RETURN, YIELD

__all__ = ['RemoteResult', 'RemoteException', 'RemoteIterator']

class RemoteResult(AsyncResult):
    """The task object.

    :param customer: the customer object.
    :param id: the task identifier.
    :param invoker_id: the identifier of the invoker which spawned this task.
    :param worker_info: the value the worker sent at accepting.
    """

    def __init__(self, collector, call_id, task_id, worker_info=None):
        super(RemoteResult, self).__init__()
        self.collector = collector
        self.call_id = call_id
        self.task_id = task_id
        self.worker_info = worker_info

    def close(self):
        """Stops to collect replies from its task."""
        self.set_exception(TaskClosed)
        self.collector.remove_result(self)

    # iterator

    _iterator = False

    def is_iterator(self):
        return self._iterator

    def set_iterator(self):
        self._iterator = True
        self.set(RemoteIterator())

    # exception

    def set_remote_exception(self, remote_exc_info):
        """Raises an exception as a :exc:`RemoteException`."""
        exc_type, exc_str, filename, lineno = remote_exc_info[:4]
        exc_type = RemoteException.compose(exc_type)
        exc = exc_type(exc_str, filename, lineno, self.worker_info)
        if len(remote_exc_info) > 4:
            state = remote_exc_info[4]
            exc.__setstate__(state)
        self.set_exception(exc)

    def set_exception(self, exc):
        if self.is_iterator():
            self.get().throw(exc)
        else:
            super(RemoteResult, self).set_exception(exc)

    # reply receivers

    def set_reply(self, method, value):
        if method == RETURN:
            self._return(value)
        elif method == YIELD:
            self._yield(value)
        elif method == RAISE:
            self._raise(value)
        elif method == BREAK:
            self._break(value)
        if method & DONE:
            self.collector.remove_result(self)

    def _return(self, value):
        self.set(value)

    def _yield(self, value):
        if not self.is_iterator():
            self.set_iterator()
        self.get().send(value)

    def _raise(self, value):
        self.set_remote_exception(value)

    def _break(self, value):
        if self.is_iterator():
            self.get().close()
        else:
            self.set(iter([]))

    def __repr__(self):
        return make_repr(self, None, ['call_id', 'task_id', 'worker_info'],
                         reprs={'call_id': hexlify, 'task_id': hexlify})

class RemoteException(BaseException):

    _composed = {}

    @classmethod
    def compose(cls, exc_type):
        try:
            return cls._composed[exc_type]
        except KeyError:
            class composed_exc_type(exc_type, cls):
                __init__ = cls.__init__
            composed_exc_type.exc_type = exc_type
            composed_exc_type.exctype = exc_type  # For backward compatibility.
            composed_exc_type.__name__ = exc_type.__name__ + '(Remote)'
            # Avoid to start with dot in traceback.
            composed_exc_type.__module__ = 'exceptions'
            cls._composed[exc_type] = composed_exc_type
            return composed_exc_type

    def __init__(self, message, filename=None, lineno=None, worker_info=None):
        super(RemoteException, self).__init__(message)
        self.filename = filename
        self.lineno = lineno
        self.worker_info = worker_info

    def __str__(self):
        string = super(RemoteException, self).__str__()
        if self.filename is not None:
            string += ' ({0}:{1})'.format(self.filename, self.lineno)
        return string

class RemoteIterator(object):

    def __init__(self):
        self.queue = Queue()

    def __iter__(self):
        return self

    def send(self, value):
        if self.queue is None:
            raise StopIteration
        self.queue.put((True, value))

    def throw(self, exc):
        if self.queue is None:
            raise StopIteration
        self.queue.put((False, exc))

    def close(self):
        self.throw(StopIteration)

    def __next__(self):
        if self.queue is None:
            raise StopIteration
        yields, value = self.queue.get()
        if yields:
            return value
        else:
            self.queue = None
            raise value

    next = __next__  # for Python 2


from zorg.driver import Driver
from PIL import Image
import pytesseract

class OCR(Driver):

    def __init__(self, options, connection):
        super(OCR, self).__init__(options, connection)

        # An attribute to hold the last text item read by the camera
        self.text_cache = ""

        self.commands = [
            "read",
            "text_visible"
        ]

    def read(self):
        """
        Returns the text from an image at a given url.
        """

        # Only download the image if it has changed
        if self.connection.has_changed():
            image_path = self.connection.download_image()
            image = Image.open(image_path)
            self.text_cache = pytesseract.image_to_string(image)
            image.close()

        return self.text_cache

    def text_visible(self):
        """
        Returns true or false based on if the OCR process has read
        actual words. This is needed to prevent non-words from being
        added to the queue since the ocr process can sometimes return
        values that are not meaningfull.
        """

        # Split the input string at points with any amount of whitespace
        words = self.read().split()

        # Light weight check to see if a word exists
        for word in words:

            # If the word is a numeric value
            if word.lstrip('-').replace('.', '', 1).isdigit():
                return True

            # If the word contains only letters with a length from 2 to 20
            if word.isalpha() and (len(word) > 1 or len(word) <= 20):
                return True

        return False


import datetime
from typing import Optional

import peewee as pw
from playhouse.shortcuts import model_to_dict

from .base import BaseModel

class Bike(BaseModel):
    """
    The class for the bike model entity.
    """
    make = pw.TextField(null=True)
    model = pw.TextField(null=True)
    colour = pw.TextField(null=True)
    latitude = pw.FloatField(null=True)
    longitude = pw.FloatField(null=True)
    frame_number = pw.TextField(null=True)
    rfid = pw.TextField(null=True)
    description = pw.TextField(null=True)
    reported_at = pw.TextField(null=True)
    cached_date = pw.DateTimeField(default=datetime.datetime.now)

    def serialize(self):
        return model_to_dict(self, exclude=[Bike.cached_date, Bike.id])

    @staticmethod
    def get_most_recent_bike() -> Optional['Bike']:
        """
        Gets the most recently cached bike from the database.
        :return: The bike that was cached most recently.
        """
        try:
            return Bike.select().order_by(Bike.cached_date.desc()).get()
        except pw.DoesNotExist:
            return None

    @staticmethod
    def from_dict(data: dict) -> 'Bike':
        return Bike(
            make=data["make"],
            model=data["model"],
            colour=data["colour"],
            latitude=data["latitude"] if not data["latitude"] == "" else None,
            longitude=data["longitude"] if not data["longitude"] == "" else None,
            frame_number=data["frame_number"],
            rfid=data["rfid"],
            description=data["description"],
            reported_at=data["reported_at"]
        )


#!/usr/bin/env python
# encoding: utf-8
# Copyright (C) 2017 John Törnblom
#
# This file is part of pyxtuml.
#
# pyxtuml is free software: you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation, either
# version 3 of the License, or (at your option) any later version.
#
# pyxtuml is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with pyxtuml. If not, see <http://www.gnu.org/licenses/>.

import sys
import logging
import optparse
import functools

import xtuml

from bridgepoint import oal
from xtuml import where_eq as where

from functools import partial

logger = logging.getLogger(__name__)

class SymtabException(Exception):
    pass

class Block(dict):
    pass

class Scope(list):

    def __init__(self):
        self.append(Block())

    @property
    def symbols(self):
        return_symbols = dict()
        for d in self:
            return_symbols.update(d)

        return return_symbols.items()

class SymbolTable(object):
    domain = None

    def __init__(self, domain):
        self.domain = domain
        self._scopes = list()

    @property
    def scope_head(self):
        if not len(self._scopes):
            raise SymtabException('Out of scope')

        return self._scopes[-1]

    def enter_scope(self):
        scope = Scope()
        self._scopes.append(scope)

    def leave_scope(self):
        if not len(self._scopes):
            raise SymtabException('Out of scope')

        d = dict()
        for block in self._scopes.pop():
            d.update(block)
        return d

    def enter_block(self):
        block = Block()
        self.scope_head.append(block)

    def leave_block(self):
        if not len(self.scope_head):
            raise SymtabException('Out of block')

        block = self.scope_head.pop()
        del block

    def install_symbol(self, name, handle):
        for block in self.scope_head:
            if name in block:
                block[name] = handle
                return

        block = self.scope_head[-1]
        block[name] = handle

    def find_symbol(self, name, default=None):
        for block in self.scope_head:
            if name in block:
                return block[name]

        if default is not None:
            self.install_symbol(name, default)
            return default

        return self.domain.find_symbol(name)

class ReturnException(Exception):
    pass

class BreakException(Exception):
    pass

class ContinueException(Exception):
    pass

class StopException(Exception):
    pass

class NodePrintVisitor(xtuml.NodePrintVisitor):

    def default_render(self, node):
        if not node.position:
            return type(node).__name__

        return '%s (%d:%d)' % (type(node).__name__,
                               node.position.start_line,
                               node.position.start_column)

class ActionWalker(xtuml.Walker):
    domain = None
    return_value = None

    def __init__(self, domain):
        self.domain = domain
        self.symtab = SymbolTable(domain)
        xtuml.Walker.__init__(self)

    def accept(self, node, **kwargs):
        try:
            return xtuml.Walker.accept(self, node, **kwargs)
        except xtuml.MetaException as e:
            logger.error('%s:%d:%s' % (node.position.label,
                                       node.position.start_line,
                                       e))

    def default_accept(self, node, **kwargs):
        logger.error("%s:%d:%s '%s'" % (node.position.label,
                                        node.position.start_line,
                                       'unsupported statement',
                                        node.character_stream.splitlines()[0]))

    def accept_BodyNode(self, node):
        self.symtab.enter_scope()

        try:
            self.accept(node.block)
        except ReturnException:
            pass
        except StopException:
            pass

        self.symtab.leave_scope()

    def accept_BlockNode(self, node):
        self.symtab.enter_block()
        self.accept(node.statement_list)
        self.symtab.leave_block()

    def accept_StatementListNode(self, node):
        for child in node.children:
            self.accept(child)

    def accept_ReturnNode(self, node):
        value = self.accept(node.expression)
        self.return_value = value.fget()
        raise ReturnException()

    def accept_BreakNode(self, node):
        raise BreakException()

    def accept_ContinueNode(self, node):
        raise ContinueException()

    def accept_ControlNode(self, node):
        raise StopException()

    def accept_CreateObjectNode(self, node):
        inst = self.domain.new(node.key_letter)
        self.symtab.install_symbol(node.variable_name, inst)

    def accept_CreateObjectNoVariableNode(self, node):
        self.domain.new(node.key_letter)

    def accept_DeleteNode(self, node):
        inst = self.symtab.find_symbol(node.variable_name)
        xtuml.delete(inst)

    def accept_RelateNode(self, node):
        inst1 = self.symtab.find_symbol(node.from_variable_name)
        inst2 = self.symtab.find_symbol(node.to_variable_name)

        xtuml.relate(inst1, inst2, node.rel_id, node.phrase.replace("'", ''))

    def accept_RelateUsingNode(self, node):
        from_inst = self.symtab.find_symbol(node.from_variable_name)
        to_inst = self.symtab.find_symbol(node.to_variable_name)
        using_inst = self.symtab.find_symbol(node.using_variable_name)

        xtuml.relate(from_inst, using_inst, node.rel_id, node.phrase.replace("'", ''))
        xtuml.relate(using_inst, to_inst, node.rel_id, node.phrase.replace("'", ''))

    def accept_UnrelateNode(self, node):
        inst1 = self.symtab.find_symbol(node.from_variable_name)
        inst2 = self.symtab.find_symbol(node.to_variable_name)

        xtuml.unrelate(inst1, inst2, node.rel_id, node.phrase.replace("'", ''))

    def accept_UnrelateUsingNode(self, node):
        from_inst = self.symtab.find_symbol(node.from_variable_name)
        to_inst = self.symtab.find_symbol(node.to_variable_name)
        using_inst = self.symtab.find_symbol(node.using_variable_name)

        xtuml.unrelate(from_inst, using_inst, node.rel_id, node.phrase.replace("'", ''))
        xtuml.unrelate(using_inst, to_inst, node.rel_id, node.phrase.replace("'", ''))

    def accept_SelectFromNode(self, node):
        if node.cardinality == 'many':
            handle = self.domain.select_many(node.key_letter)
        else:
            handle = self.domain.select_any(node.key_letter)

        self.symtab.install_symbol(node.variable_name, handle)

    def accept_SelectedAccessNode(self, node):
        selected = self.symtab.find_symbol('selected')
        return property(lambda: selected)

    def accept_SelectFromWhereNode(self, node):
        def where(selected):
            self.symtab.enter_block()
            self.symtab.install_symbol('selected', selected)
            value = self.accept(node.where_clause)
            self.symtab.leave_block()
            return value.fget()

        if node.cardinality == 'many':
            handle = self.domain.select_many(node.key_letter, where)
        else:
            handle = self.domain.select_any(node.key_letter, where)

        self.symtab.install_symbol(node.variable_name, handle)

    def accept_SelectRelatedNode(self, node):
        handle = self.accept(node.handle).fget()
        if node.cardinality == 'many':
            chain = xtuml.navigate_many(handle)
        else:
            chain = xtuml.navigate_one(handle)

        for step in self.accept(node.navigation_chain):
            chain = step(chain)

        self.symtab.install_symbol(node.variable_name, chain())

    def accept_SelectRelatedWhereNode(self, node):
        def where(selected):
            self.symtab.enter_block()
            self.symtab.install_symbol('selected', selected)
            value = self.accept(node.where_clause)
            self.symtab.leave_block()
            return value.fget()

        handle = self.accept(node.handle).fget()
        if node.cardinality == 'many':
            chain = xtuml.navigate_many(handle)
        else:
            chain = xtuml.navigate_one(handle)

        for step in self.accept(node.navigation_chain):
            chain = step(chain)

        self.symtab.install_symbol(node.variable_name, chain(where))

    def accept_NavigationListNode(self, node):
        for child in node.children:
            yield self.accept(child)

    def accept_NavigationStepNode(self, node):
        return lambda chain: chain.nav(node.key_letter, node.rel_id,
                                       node.phrase.replace("'", ''))

    def accept_ForEachNode(self, node):
        set_handle = self.symtab.find_symbol(node.set_variable_name)
        for handle in set_handle:
            self.symtab.install_symbol(node.instance_variable_name, handle)
            try:
                self.accept(node.block)
            except ContinueException:
                continue
            except BreakException:
                break

    def accept_IfNode(self, node):
        if self.accept(node.expression).fget():
            self.accept(node.block)

        elif not self.accept(node.elif_list):
            self.accept(node.else_clause)

    def accept_ElIfListNode(self, node):
        for child in node.children:
            if self.accept(child):
                return True

    def accept_ElIfNode(self, node):
        if self.accept(node.expression).fget():
            self.accept(node.block)
            return True

    def accept_ElseNode(self, node):
        self.accept(node.block)

    def accept_WhileNode(self, node):
        while self.accept(node.expression).fget():
            try:
                self.accept(node.block)
            except ContinueException:
                continue
            except BreakException:
                break

    def accept_AssignmentNode(self, node):
        value = self.accept(node.expression).fget()
        variable = self.accept(node.variable_access)
        variable.fset(value)

    def accept_FieldAccessNode(self, node):
        handle = self.accept(node.handle).fget()
        return property(fget=lambda: getattr(handle, node.name),
                        fset=lambda value: setattr(handle, node.name, value))

    def accept_IndexAccessNode(self, node, default=None):
        index = self.accept(node.expression).fget()
        handle = self.accept(node.handle, default=[default] * (index + 1)).fget()
        fget = lambda: handle[index]
        fset = lambda value: handle.__setitem__(index, value)
        return property(fget, fset)

    def accept_VariableAccessNode(self, node, default=None):
        fget = partial(self.symtab.find_symbol, node.variable_name, default=default)
        fset = partial(self.symtab.install_symbol, node.variable_name)
        return property(fget, fset)

    def accept_BinaryOperationNode(self, node):
        ops = {
            '+':   lambda lhs, rhs: (lhs + rhs),
            '-':   lambda lhs, rhs: (lhs - rhs),
            '*':   lambda lhs, rhs: (lhs * rhs),
            '/':   lambda lhs, rhs: (lhs / rhs),
            '%':   lambda lhs, rhs: (lhs % rhs),
            '<':   lambda lhs, rhs: (lhs < rhs),
            '<=':  lambda lhs, rhs: (lhs <= rhs),
            '>':   lambda lhs, rhs: (lhs > rhs),
            '>=':  lambda lhs, rhs: (lhs >= rhs),
            '!=':  lambda lhs, rhs: (lhs != rhs),
            '==':  lambda lhs, rhs: (lhs == rhs),
            'or':  lambda lhs, rhs: (lhs or  rhs),
            'and': lambda lhs, rhs: (lhs and rhs),
        }
        operator = node.operator.lower()

        left_value = self.accept(node.left).fget()
        right_value = self.accept(node.right).fget()
        value = ops[operator](left_value, right_value)

        return property(lambda: value)

    def accept_UnaryOperationNode(self, node):
        ops = {
            '-':           lambda value: -value,
            '+':           lambda value: +value,
            'not':         lambda value: not value,
            'cardinality': lambda value: xtuml.cardinality(value),
            'empty':       lambda value: not value,
            'not_empty':   lambda value: not not value,
        }

        value = self.accept(node.operand).fget()
        operator = node.operator.lower()
        value = ops[operator](value)

        return property(lambda: value)

    def accept_BooleanNode(self, node):
        value = node.value.upper() == 'TRUE'
        return property(lambda: value)

    def accept_IntegerNode(self, node):
        value = int(node.value)
        return property(lambda: value)

    def accept_RealNode(self, node):
        value = float(node.value)
        return property(lambda: value)

    def accept_StringNode(self, node):
        value = node.value[1:-1]
        return property(lambda: value)

    def accept_EnumNode(self, node):
        item = self.symtab.find_symbol(node.namespace)
        value = getattr(item, node.name)
        return property(lambda: value)

    def accept_InvocationStatementNode(self, node):
        return self.accept(node.invocation)

    def accept_ImplicitInvocationNode(self, node):
        kwargs = self.accept(node.parameter_list)
        item = self.symtab.find_symbol(node.namespace)
        fn = getattr(item, node.action_name)
        value = fn(**kwargs)
        return property(lambda: value)

    def accept_InstanceInvocationNode(self, node):
        inst = self.accept(node.handle).fget()
        op = getattr(inst.__class__, node.action_name)
        kwargs = self.accept(node.parameter_list)
        value = op(inst, **kwargs)
        return property(lambda: value)

    def accept_ClassInvocationNode(self, node):
        cls = self.symtab.find_symbol(node.key_letter)
        op = getattr(cls, node.action_name)
        kwargs = self.accept(node.parameter_list)
        value = op(**kwargs)
        return property(lambda: value)

    def accept_BridgeInvocationNode(self, node):
        kwargs = self.accept(node.parameter_list)
        ee = self.symtab.find_symbol(node.namespace)
        fn = getattr(ee, node.action_name)
        value = fn(**kwargs)
        return property(lambda: value)

    def accept_FunctionInvocationNode(self, node):
        kwargs = self.accept(node.parameter_list)
        fn = self.symtab.find_symbol(node.action_name)
        value = fn(**kwargs)
        return property(lambda: value)

    def accept_ParameterListNode(self, node):
        kwargs = dict()
        for child in node.children:
            value = self.accept(child.expression).fget()
            kwargs[child.name] = value

        return kwargs

class OperationWalker(ActionWalker):
    instance = None
    return_value = None

    def __init__(self, domain, kwargs, instance):
        self.kwargs = kwargs
        self.instance = instance
        ActionWalker.__init__(self, domain)

    def accept_ParamAccessNode(self, node):
        value = self.kwargs[node.variable_name]
        return property(lambda: value)

    def accept_SelfAccessNode(self, node):
        return property(lambda: self.instance)

def run_operation(metaclass, label, action, kwargs, inst):
    w = OperationWalker(metaclass.metamodel, kwargs, inst)
    #w.visitors.append(NodePrintVisitor())
    root = oal.parse(action, label)
    w.accept(root)
    return w.return_value

class DerivedAttributeWalker(ActionWalker):
    attribute_name = None
    instance = None
    return_value = None

    def __init__(self, domain, attribute_name, instance):
        self.attribute_name = attribute_name
        self.instance = instance
        ActionWalker.__init__(self, domain)

    def accept_SelfAccessNode(self, node):
        return property(lambda: self.instance)

    def accept_FieldAccessNode(self, node):
        inst = self.accept(node.handle).fget()
        if node.name == self.attribute_name and inst == self.instance:
            return property(fget=functools.partial(getattr, self, 'return_value'),
                            fset=functools.partial(setattr, self, 'return_value'))

        else:
            return property(fget=functools.partial(getattr, inst, node.name),
                            fset=functools.partial(setattr, inst, node.name))

def run_derived_attribute(metaclass, label, action, attribute_name, inst):
    w = DerivedAttributeWalker(metaclass.metamodel, attribute_name, inst)
    #w.visitors.append(NodePrintVisitor())
    root = oal.parse(action, label)
    w.accept(root)
    return w.return_value

class FunctionWalker(ActionWalker):
    return_value = None

    def __init__(self, domain, kwargs):
        self.kwargs = kwargs
        ActionWalker.__init__(self, domain)

    def accept_ParamAccessNode(self, node):
        value = self.kwargs[node.variable_name]
        return property(lambda: value)

def run_function(domain, label, action, kwargs):
    w = FunctionWalker(domain, kwargs)
    #w.visitors.append(NodePrintVisitor())
    root = oal.parse(action, label)
    w.accept(root)
    return w.return_value

def main():
    '''
    Parse command line options and launch the interpreter
    '''
    parser = optparse.OptionParser(usage="%prog [options] <model_path> [another_model_path..]",
                                   version=xtuml.version.complete_string,
                                   formatter=optparse.TitledHelpFormatter())

    parser.add_option("-v", "--verbosity", dest='verbosity', action="count",
                      default=1, help="increase debug logging level")

    parser.add_option("-f", "--function", dest='function', action="store",
                      help="invoke function named NAME", metavar='NAME')

    parser.add_option("-c", "--component", dest='component', action="store",
                      help="look for the function in a component named NAME",
                      metavar='NAME', default=None)

    (opts, args) = parser.parse_args()
    if len(args) == 0 or not opts.function:
        parser.print_help()
        sys.exit(1)

    levels = {
              0: logging.ERROR,
              1: logging.WARNING,
              2: logging.INFO,
              3: logging.DEBUG,
    }
    logging.basicConfig(level=levels.get(opts.verbosity, logging.DEBUG))

    from bridgepoint import ooaofooa
    mm = ooaofooa.load_metamodel(args)
    c_c = mm.select_any('C_C', where(Name=opts.component))
    domain = ooaofooa.mk_component(mm, c_c, derived_attributes=False)

    func = domain.find_symbol(opts.function)
    return func()

if __name__ == '__main__':
    rc = main()
    sys.exit(rc)


#!/usr/bin/env python
# encoding: utf-8
# Copyright (C) 2017 John Törnblom
#
# This file is part of pyxtuml.
#
# pyxtuml is free software: you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation, either
# version 3 of the License, or (at your option) any later version.
#
# pyxtuml is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with pyxtuml. If not, see <http://www.gnu.org/licenses/>.
'''
Generate an sql schema file for an xtUML model.
The arguments are either xtuml files, or folders containing *.xtuml files.
Note that some type of attributes are not supported, e.g. instance handles or
timers.
'''

import sys
import optparse
import logging

import xtuml

from . import ooaofooa

logger = logging.getLogger('gen_sql_schema')

def main():
    '''
    Parse argv for options and arguments, and start schema generation.
    '''
    parser = optparse.OptionParser(usage="%prog [options] <model_path> [another_model_path...]",
                                   version=xtuml.version.complete_string,
                                   formatter=optparse.TitledHelpFormatter())

    parser.set_description(__doc__.strip())

    parser.add_option("-c", "--component", dest="component", metavar="NAME",
                      help="export sql schema for the component named NAME",
                      action="store", default=None)

    parser.add_option("-d", "--derived-attributes", dest="derived",
                      help="include derived attributes in the schema",
                      action="store_true", default=False)

    parser.add_option("-o", "--output", dest='output', metavar="PATH",
                      help="save sql schema to PATH (required)",
                      action="store", default=None)

    parser.add_option("-v", "--verbosity", dest='verbosity', action="count",
                      help="increase debug logging level", default=2)

    (opts, args) = parser.parse_args()
    if len(args) == 0 or opts.output is None:
        parser.print_help()
        sys.exit(1)

    levels = {
              0: logging.ERROR,
              1: logging.WARNING,
              2: logging.INFO,
              3: logging.DEBUG,
    }
    logging.basicConfig(level=levels.get(opts.verbosity, logging.DEBUG))

    loader = ooaofooa.Loader()
    for filename in args:
        loader.filename_input(filename)

    c = loader.build_component(opts.component, opts.derived)
    xtuml.persist_database(c, opts.output)

if __name__ == '__main__':
    main()


#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
Notes:
    Timeformat: 2013-01-25T00:11:02+0000
"""

import ipdb  # ipdb.set_trace()  # NOQA
import logging
import requests

from facepy import GraphAPI

# Setup console logging
log = logging.getLogger(__name__)

def get_token(client_id, client_secret, client_access_token, page=None):
    """
    See: http://nodotcom.org/python-facebook-tutorial.html
    """
    payload = {
        'grant_type': 'client_credentials',
        'client_id': client_id,
        'client_secret': client_secret,
    }

    if client_access_token:
        payload['grant_type'] = 'fb_exchange_token'
        payload['fb_exchange_token'] = client_access_token

    # response {"access_token":" ... ", "token_type":"bearer", "expires_in":..}
    response = requests.post(
        'https://graph.facebook.com/oauth/access_token?',
        params=payload)
    access_token = response.json()['access_token']
    return access_token

def get_page_api(client_access_token, page_id):
    """
    You can also skip the above if you get a page token:
    http://stackoverflow.com/questions/8231877
    and make that long-lived token as in Step 3
    """
    graph = GraphAPI(client_access_token)
    # Get page token to post as the page. You can skip
    # the following if you want to post as yourself.
    resp = graph.get('me/accounts')
    page_access_token = None

    for page in resp['data']:
        if page['id'] == page_id:
            page_access_token = page['access_token']
            break

    return GraphAPI(page_access_token)

def wall_post():
    """
    https://developers.facebook.com/docs/graph-api/reference/v2.9/page/feed
    """
    pass

if __name__ == '__main__':
    ipdb.set_trace()


# encoding: utf-8
# Copyright (C) 2017 John Törnblom
#
# This file is part of pyxtuml.
#
# pyxtuml is free software: you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation, either
# version 3 of the License, or (at your option) any later version.
#
# pyxtuml is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with pyxtuml. If not, see <http://www.gnu.org/licenses/>.
'''
Serialize xtuml models and its schema to an sql-based file format and persist
to disk.
'''

import uuid
import logging

import xtuml

logger = logging.getLogger(__name__)

def serialize_value(value, ty):
    '''
    Serialize a value from an xtuml metamodel instance.
    '''
    ty = ty.upper()

    null_value = {
        'BOOLEAN'   : False,
        'INTEGER'   : 0,
        'REAL'      : 0.0,
        'STRING'    : '',
        'UNIQUE_ID' : 0
    }

    transfer_fn = {
        'BOOLEAN'     : lambda v: '%d' % int(v),
        'INTEGER'     : lambda v: '%d' % v,
        'REAL'        : lambda v: '%f' % v,
        'STRING'      : lambda v: "'%s'" % v.replace("'", "''"),
        'UNIQUE_ID'   : lambda v: '"%s"' % uuid.UUID(int=v)
    }

    if value is None:
        value = null_value[ty]

    return transfer_fn[ty](value)

def serialize_instance(instance):
    '''
    Serialize an *instance* from a metamodel.
    '''
    attr_count = 0
    metaclass = xtuml.get_metaclass(instance)
    s = 'INSERT INTO %s VALUES (' % metaclass.kind
    for name, ty in metaclass.attributes:
        value = getattr(instance, name)

        s += '\n    '
        s += serialize_value(value, ty)

        attr_count += 1
        if attr_count < len(metaclass.attributes):
            s += ', -- %s : %s' % (name, ty)
        else:
            s += ' -- %s : %s' % (name, ty)

    s += '\n);\n'

    return s

def serialize_instances(metamodel):
    '''
    Serialize all instances in a *metamodel*.
    '''
    s = ''
    for inst in metamodel.instances:
        s += serialize_instance(inst)

    return s

def serialize_association(ass):
    '''
    Serialize an xtuml metamodel association.
    '''
    s1 = '%s %s (%s)' % (ass.source_link.cardinality,
                         ass.source_link.to_metaclass.kind,
                         ', '.join(ass.source_keys))

    if ass.target_link.phrase:
        s1 += " PHRASE '%s'" % ass.target_link.phrase

    s2 = '%s %s (%s)' % (ass.target_link.cardinality,
                         ass.target_link.to_metaclass.kind,
                         ', '.join(ass.target_keys))

    if ass.source_link.phrase:
        s2 += " PHRASE '%s'" % ass.source_link.phrase

    return 'CREATE ROP REF_ID %s FROM %s TO %s;\n' % (ass.rel_id,
                                                      s1,
                                                      s2)

def serialize_class(Cls):
    '''
    Serialize an xtUML metamodel class.
    '''
    metaclass = xtuml.get_metaclass(Cls)
    attributes = ['%s %s' % (name, ty.upper()) for name, ty in metaclass.attributes]

    s = 'CREATE TABLE %s (\n    ' % metaclass.kind
    s += ',\n    '.join(attributes)
    s += '\n);\n'

    return s

def serialize_unique_identifiers(metamodel):
    s = ''

    for metaclass in metamodel.metaclasses.values():
        for index_name, attribute_names in metaclass.indices.items():
            attribute_names = ', '.join(attribute_names)
            s += 'CREATE UNIQUE INDEX %s ON %s (%s);\n' % (index_name,
                                                          metaclass.kind,
                                                          attribute_names)
    return s

def serialize_schema(metamodel):
    '''
    Serialize all class and association definitions in a *metamodel*.
    '''
    s = ''
    for kind in sorted(metamodel.metaclasses.keys()):
        s += serialize_class(metamodel.metaclasses[kind].clazz)

    for ass in sorted(metamodel.associations, key=lambda x: x.rel_id):
        s += serialize_association(ass)

    return s

def serialize_database(metamodel):
    '''
    Serialize all instances, class definitions, association definitions, and
    unique identifiers  in a *metamodel*.
    '''
    schema = serialize_schema(metamodel)
    instances = serialize_instances(metamodel)
    identifiers = serialize_unique_identifiers(metamodel)

    return ''.join([schema, instances, identifiers])

def serialize(resource):
    '''
    Serialize some xtuml *resource*, e.g. an instance or a complete metamodel.
    '''
    if isinstance(resource, xtuml.MetaModel):
        return serialize_database(resource)

    elif isinstance(resource, type) and issubclass(resource, xtuml.Class):
        return serialize_class(resource)

    elif isinstance(resource, xtuml.Association):
        return serialize_association(resource)

    elif isinstance(resource, xtuml.Class):
        return serialize_instance(resource)

def persist_instances(metamodel, path, mode='w'):
    '''
    Persist all instances in a *metamodel* by serializing them and saving to a
    *path* on disk.
    '''
    with open(path, mode) as f:
        for inst in metamodel.instances:
            s = serialize_instance(inst)
            f.write(s)

def persist_schema(metamodel, path, mode='w'):
    '''
    Persist all class and association definitions in a *metamodel* by
    serializing them and saving to a *path* on disk.
    '''
    with open(path, mode) as f:
        for kind in sorted(metamodel.metaclasses.keys()):
            s = serialize_class(metamodel.metaclasses[kind].clazz)
            f.write(s)

        for ass in sorted(metamodel.associations, key=lambda x: x.rel_id):
            s = serialize_association(ass)
            f.write(s)

def persist_unique_identifiers(metamodel, path, mode='w'):
    '''
    Persist all unique identifiers in a *metamodel* by serializing them and
    saving to a *path* on disk.
    '''
    with open(path, mode) as f:
        for metaclass in metamodel.metaclasses.values():
            for index_name, attribute_names in metaclass.indices.items():
                attribute_names = ', '.join(attribute_names)
                s = 'CREATE UNIQUE INDEX %s ON %s (%s);\n' % (index_name,
                                                              metaclass.kind,
                                                              attribute_names)
                f.write(s)

def persist_database(metamodel, path, mode='w'):
    '''
    Persist all instances, class definitions and association definitions in a
    *metamodel* by serializing them and saving to a *path* on disk.
    '''
    with open(path, mode) as f:
        for kind in sorted(metamodel.metaclasses.keys()):
            metaclass = metamodel.metaclasses[kind]
            s = serialize_class(metaclass.clazz)
            f.write(s)

            for index_name, attribute_names in metaclass.indices.items():
                attribute_names = ', '.join(attribute_names)
                s = 'CREATE UNIQUE INDEX %s ON %s (%s);\n' % (index_name,
                                                              metaclass.kind,
                                                              attribute_names)
                f.write(s)

        for ass in sorted(metamodel.associations, key=lambda x: x.rel_id):
            s = serialize_association(ass)
            f.write(s)

        for inst in metamodel.instances:
            s = serialize_instance(inst)
            f.write(s)


#!/usr/bin/env python3

#    trovotutto
#
#    ----------------------------------------------------------------------
#    Copyright © 2018  Pellegrino Prevete
#
#    All rights reserved
#    ----------------------------------------------------------------------
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see <https://www.gnu.org/licenses/>.
#

import itertools
import re
import operator
import pickle
from glob import glob
from os import listdir as ls
from os import getcwd
from os.path import expanduser
from pprint import pprint
from subprocess import check_output as sh

from argparse import ArgumentParser
from nltk import ngrams as ng
from xdg import BaseDirectory

from .color import Color

name = "trovotutto"

def save(variable, filename):
    """Save variable on given path using Pickle

    Args:
        variable: what to save
        path (str): path of the output
    """
    fileObj = open(filename, 'wb')
    pickle.dump(variable, fileObj)
    fileObj.close()

def load(filename):
    """Load variable from Pickle file

    Args:
        path (str): path of the file to load

    Returns:
        variable read from path
    """
    fileObj = open(filename, 'rb')
    variable = pickle.load(fileObj)
    fileObj.close()
    return variable

class Files:
    """Class that "documentifies" files in local filesystem

    It recursively takes every file in a location and interpret them as documents
    using the directories that form their path as words.

    It saves a list of the files founded in the index directory.

    Args:
        path (str): path to find files in (default: home directory);
        filetype (str): type of file to index; can be one of the following values:
                         - any
                         - images
                         - documents
                         - code
                         - audio
                         - video;
        exclude (list): extensions to exclude from indexing;
        update (bool): whether to rescan the file system;
        verobse(int): from 0 to 5, verbose flag.
    """
    def __init__(self, path=getcwd(), filetype="any", exclude=[], update=True, verbose=0):

        if verbose > 0:
            print("documentifiyng files' path in " + path)

        data_path = BaseDirectory.save_data_path(name)

        extensions = {"images":      ["bmp", "eps", "jpeg", "jpg", "png", "svg", "tiff", "webp","xfc"],
                     "documents":    ["doc", "docx", "txt", "rtf", "pdf", "ooxml", "docm", "odt", "xls", "ppt", "pptx", "xps"],
                     "code":         ["py", "css", "m", "c", "h", "php", "f90", "f77", "f08", "html", "tex", "php", "js","sh"],
                     "audio":        ["mp3", "wma", "ogg", "m4a", "wav", "flac", "xm"],
                     "video":        ["mp4", "avi", "mkv", "flv", "mov", "mpg", "wmv", "webm"] }
        if filetype == "any":
            selected_types = []
            for t in extensions.keys():
                selected_types = selected_types + extensions[t]
        else:
            selected_types = extensions[filetype]
        for t in exclude:
            selected_types.remove(t)

        self.elements = []
        db = ls(data_path)

        for ext in selected_types:
            if not (ext in db) or (update == True):
                files = glob(path + '/**/*.'+ext, recursive=True)
                save(files, data_path + '/' + ext)
                self.elements = self.elements + files
            else:
                self.elements = self.elements + load(data_path + "/" + ext)
        self.documents = [re.split("/|\.|-|:|;| |_", f.lower()) for f in self.elements]

class PGPgramDb:
    """Class that 'documentifies' files in PGPgram pickle database

    It takes paths of the elements in the PGPgram database and interpret
    them as documents using the directories that form their path as words.

    Args:
        db (obj): PGPgram Db object;
        filetype (str): type of file to index; can be one of the following values:
                        - any (default)
                        - images
                        - documents
                        - code
                        - audio
                        - video
        exclude (list): extensions to exclude from indexing (default: empty)
        update (bool): whether to rescan the database (default: True)
    """
    def __init__(self, db, path="/", filetype="any", exclude=[], update=True):

        extensions = {"images":      ["bmp", "eps", "jpeg", "jpg", "png", "svg", "tiff", "webp","xfc"],
                     "documents":    ["doc", "docx", "txt", "rtf", "pdf", "ooxml", "docm", "odt", "xls", "ppt", "pptx", "xps"],
                     "code":         ["py", "css", "m", "c", "h", "php", "f90", "f77", "f08", "html", "tex", "php", "js","sh"],
                     "audio":        ["mp3", "wma", "ogg", "m4a", "wav", "flac", "xm"],
                     "video":        ["mp4", "avi", "mkv", "flv", "mov", "mpg", "wmv", "webm"] }
        if filetype == "any":
            selected_types = [""]
        else:
            selected_types = extensions[filetype]
        for t in exclude:
            selected_types.remove(t)

        self.elements = []
        for ext in selected_types:
            files = [d["path"] for d in db.files if d['path'].startswith(path) and d["path"].endswith(ext)]
            self.elements = self.elements + files

        self.documents = [re.split("/|\.|-|:|;| |_", f.lower()) for f in self.elements]

class Index:
    """A simple inverted index

    It decompose given set of documents in ngrams and creates an inverted index
    having as keys the ngrams and values the documents containing it.

    Args:
        type_instance (obj): can be Files or PGPgramDb object (default: new Files instance)
        slb (int): length of the ngrams (default: 3)
        verbose(int): verbose flag from 0 to 5 (default: 2)
    """
    def __init__(self, type_instance, slb=4, verbose=0):
        self.slb = slb
        self.elements = type_instance.elements
        self.documents = type_instance.documents
        self.terms = list(set([t for e in self.documents for t in e]))
        self.d = len(self.terms)
        self.N = len(self.documents)
        self.D = {}
        if verbose >= 2:
            print ("indexing documents")
        for i,t in enumerate(self.terms):
            for j,e in enumerate(self.documents):
                occs = len([w for w in e if w == t])
                if occs != 0:
                    if not i in self.D.keys():
                        self.D[i] = {}
                    self.D[i][j] = occs
        if verbose >= 2:
            print ("creating ngrams")
        self.ngrams = {}
        for i,t in enumerate(self.terms):
            if len(t) >=  slb:
                for g in ng(t,slb):
                    if not g in self.ngrams.keys():
                        self.ngrams[g] = []
                    self.ngrams[g].append(i)

    def search(self, query, verbose=0):
        """Searches files satisfying query

        It first decompose the query in ngrams, then score each document containing
        at least one ngram with the number. The ten document having the most ngrams
        in common with the query are selected.

        Args:
             query (str): what to search;
             results_number (int): number of results to return (default: 10)
        """
        if verbose > 0:
            print("searching " + query)
        query = query.lower()
        qgram = ng(query, self.slb)
        qocument = set()
        for q in qgram:
            if q in self.ngrams.keys():
                for i in self.ngrams[q]:
                    qocument.add(i)
        self.qocument = qocument
        results = {}
        for i in qocument:
           for j in self.D[i].keys():
                if not j in results.keys():
                    results[j] = 0
                results[j] = results[j] + self.D[i][j]
        sorted_results = sorted(results.items(), key=operator.itemgetter(1), reverse=True)
        return [self.elements[f[0]] for f in sorted_results]

class Handler:
    """Display and let take action on search results

    Args:
        results (list): list of results obtained through Index.search
    """
    def __init__(self, results, results_number=10):
        color = Color()
        for i,f in enumerate(results[0:results_number]):
            g = f.split("/")
            print(color.GREEN + color.BOLD + str(i) + ". " + color.BLUE + g[-1] + color.END)
            print(color.GRAY + f + color.END + "\n")
        if results != []:
            choice = int(input("Select search result (by number): "))
            sh(['xdg-open', results[choice]])

def main():
    """Function for command line execution"""

    parser = ArgumentParser(description="search files using n-grams")
    parser.add_argument('--path', dest='path', help="where to search", nargs=1, action="store", default=getcwd())
    parser.add_argument('--update', dest='update', help="update the index", action='store_true', default=True)
    parser.add_argument('--filetype', dest='filetype', help="any, images, documents, code, audio, video", nargs=1, action="store", default=["any"])
    parser.add_argument('--verbose', dest='verbose', help="extended output", action='store_true', default=False)
    parser.add_argument('--results', dest='results', help="number of results to display", action="store", default=10)
    parser.add_argument('query', nargs='+', help="what to search", action="store")
    args = parser.parse_args()

    if args.verbose:
        verbose = 2
        pprint(args)
    else:
        verbose = 0

    query = args.query[0]
    for arg in args.query[1:]:
        query = query + " " + arg
    slb = min([len(w) for w in query.split(" ")])

    files = Files(path=args.path, filetype=args.filetype[0], exclude=[], update=args.update, verbose=verbose)
    index = Index(files, slb=slb, verbose=verbose)
    results = index.search(query, verbose=verbose)
    Handler(results, results_number=int(args.results))


"""
The cli package hosts all the functionality for the command line tools.
"""

from functools import partial
from typing import Tuple, Optional, List

from click import echo

from hyperion.cli.serializers import PostcodeSerializerHuman, PostcodeSerializerJSON
from hyperion.cli.util import get_postcode_data
from hyperion.fetch import ApiError
from hyperion.models import CachingError
from .getters import getters, PostcodeGetter

# todo write dataclass serialize function

def partition(condition, collection) -> Tuple[List, List]:
    """Partitions a list into two based on a condition."""
    succeed, fail = [], []

    for x in collection:
        if condition(x):
            succeed.append(x)
        else:
            fail.append(x)

    return succeed, fail

async def cli(location_strings: Tuple[str], random_postcodes_count: int, *,
              bikes: bool = False, crime: bool = False,
              nearby: bool = False, as_json: bool = False):
    """
    Runs the CLI app.
    Tries to execute as many steps as possible to give the user
    the best understanding of the errors (if there are any).

    :param location_strings: A list of desired postcodes or coordinates.
    :param random_postcodes_count: A number of random postcodes to fetch..
    :param bikes: A flag to include bikes.
    :param crime: A flag to include crime.
    :param nearby: A flag to include nearby.
    :param as_json: A flag to make json output.
    """

    def match_getter(location) -> Optional[PostcodeGetter]:
        for getter in getters:
            if getter.can_provide(location):
                return getter(location)
        else:
            return None

    async def handle_getter(exception_list, getter):
        try:
            return await getter.get_postcodes()
        except (CachingError, ApiError):
            exception_list.append(f"Could not get data for {getter}")

    async def handle_datas(exception_list, postcode):
        postcode_data, new_exceptions = await get_postcode_data(postcode, bikes, crime, nearby)
        exception_list += new_exceptions
        return postcode_data

    exception_list: List[Exception] = []

    handle_getter = partial(handle_getter, exception_list)
    handle_datas = partial(handle_datas, exception_list)

    postcode_getters = {location: match_getter(location) for location in
                        set(location_strings) | ({random_postcodes_count} if random_postcodes_count > 0 else set())}

    matched, unmatched = partition(lambda k_v: k_v[1] is not None, postcode_getters.items())

    for location, getter in unmatched:
        echo(f"Invalid input for {location}")

    postcodes_collection = [await handle_getter(getter) for location, getter in matched]

    if len(exception_list) > 0:
        for f in exception_list:
            echo(str(f))
        return 1

    postcode_datas = [await handle_datas(postcode) for entry in postcodes_collection for postcode in entry]
    serializer = (PostcodeSerializerJSON if as_json else PostcodeSerializerHuman)(postcode_datas)
    echo(serializer.serialize())


from datetime import timedelta
from json import JSONDecodeError
from typing import Optional, List, Dict

from aiohttp import ClientSession, ClientConnectionError
from aiobreaker import CircuitBreaker

from hyperion import logger
from . import ApiError

police_breaker = CircuitBreaker(fail_max=3, timeout_duration=timedelta(hours=1))

@police_breaker
async def fetch_neighbourhood(lat: float, long: float) -> Optional[dict]:
    """
    Gets the neighbourhood from the fetch that is associated with the given postcode.
    :return: A neighbourhood object parsed from the fetch.
    :raise ApiError: When there was an error connecting to the API.
    """

    lookup_url = f"https://data.police.uk/api/locate-neighbourhood?q={lat},{long}"

    async with ClientSession() as session:
        try:
            async with session.get(lookup_url) as request:
                if request.status == 404:
                    return None
                neighbourhood = await request.json()
        except ClientConnectionError as con_err:
            logger.debug(f"Could not connect to {con_err.host}")
            raise ApiError(f"Could not connect to {con_err.host}")
        except JSONDecodeError as dec_err:
            logger.error(f"Could not decode data: {dec_err}")
            raise ApiError(f"Could not decode data: {dec_err}")

        neighbourhood_url = f"https://data.police.uk/api/{neighbourhood['force']}/{neighbourhood['neighbourhood']}"

        try:
            async with session.get(neighbourhood_url) as request:
                neighbourhood_data = await request.json()
        except ConnectionError as con_err:
            logger.debug(f"Could not connect to {con_err.args[0].pool.host}")
            raise ApiError(f"Could not connect to {con_err.args[0].pool.host}")
        except JSONDecodeError as dec_err:
            logger.error(f"Could not decode data: {dec_err}")
            raise ApiError(f"Could not decode data: {dec_err}")

        return neighbourhood_data

@police_breaker
async def fetch_crime(lat: float, long: float) -> List[Dict]:
    """
    Gets crime for a given lat and long.
    :raise ApiError: When there was an error connecting to the API.

    todo cache
    """
    crime_lookup = f"https://data.police.uk/api/crimes-street/all-crime?lat={lat}&lng={long}"
    async with ClientSession() as session:
        try:
            async with session.get(crime_lookup) as request:
                crime_request = await request.json()
        except ClientConnectionError as con_err:
            logger.debug(f"Could not connect to {con_err.args[0].pool.host}")
            raise ApiError(f"Could not connect to {con_err.args[0].pool.host}")
        except JSONDecodeError as dec_err:
            logger.error(f"Could not decode data: {dec_err}")
            raise ApiError(f"Could not decode data: {dec_err}")
        else:
            return crime_request


"""
The main hyperion entry point. Run `hyperion --help` for more info.
"""

import logging
from asyncio import get_event_loop, CancelledError, set_event_loop_policy

import click
import uvloop
from aiohttp import web
from click import Path
from colorama import Fore

from . import logger
from .api import app
from .api.util import enable_cross_origin
from .cli import cli
from .models import util, initialize_database

set_event_loop_policy(uvloop.EventLoopPolicy())

@click.command()
@click.argument('locations', required=False, nargs=-1)
@click.option('--random', '-r', count=True)
@click.option('--bikes', '-b', is_flag=True)
@click.option('--crime', '-c', is_flag=True)
@click.option('--nearby', '-n', is_flag=True)
@click.option('--json', '-j', is_flag=True)
@click.option('--update-bikes', is_flag=True)
@click.option('--api-server', is_flag=True)
@click.option('--cross-origin', is_flag=True)
@click.option('--host', '-h', type=str, default='0.0.0.0')
@click.option('--port', '-p', type=int, default=8000)
@click.option('--db-path', type=Path(dir_okay=False))
@click.option('--verbose', '-v', count=True)
def run(locations, random, bikes, crime, nearby, json, update_bikes, api_server, cross_origin, host, port, db_path,
        verbose):
    """
    Runs the program. Takes a list of postcodes or coordinates and
    returns various information about them. If using the cli, make
    sure to update the bikes database with the -u command.

    Locations can be either a specific postcode, or a pair of coordinates.
    Coordinates are passed in the form "55.948824,-3.196425".

    :param locations: The list of postcodes or coordinates to search.
    :param random: The number of random postcodes to include.
    :param bikes: Includes a list of stolen bikes in that area.
    :param crime: Includes a list of committed crimes in that area.
    :param nearby: Includes a list of wikipedia articles in that area.
    :param json: Returns the data in json format.
    :param update_bikes: Whether to force update bikes.
    :param api_server: If given, the program will instead run a rest api.
    :param cross_origin:
    :param host:
    :param port: Defines the port to run the rest api on.
    :param db_path: The path to the sqlite db to use.
    :param verbose: The verbosity.
    """

    log_levels = [logging.WARNING, logging.INFO, logging.DEBUG]
    logging.basicConfig(level=log_levels[min(verbose, 2)])

    initialize_database(db_path)

    loop = get_event_loop()

    if update_bikes:
        logger.info("Force updating bikes.")
        loop.run_until_complete(util.update_bikes())

    if api_server:
        if cross_origin:
            enable_cross_origin(app)

        try:
            web.run_app(app, host=host, port=port)
        except CancelledError as e:
            if e.__context__ is not None:
                click.echo(Fore.RED + (
                    f"Could not bind to address {host}:{port}" if e.__context__.errno == 48 else e.__context__))
                exit(1)
            else:
                click.echo("Goodbye!")

    elif len(locations) > 0 or random > 0:
        exit(loop.run_until_complete(cli(locations, random, bikes=bikes, crime=crime, nearby=nearby, as_json=json)))
    else:
        click.echo(Fore.RED + "Either include a post code, or the --api-server flag.")

if __name__ == '__main__':
    run()


"""Context processors for BiDi"""

def bidi(request):
    """Adds to the context BiDi related variables

    LANGUAGE_DIRECTION -- Direction of current language ('ltr' or 'rtl')
    LANGUAGE_START -- Start of language layout ('right' for rtl, 'left'
                      for 'ltr')
    LANGUAGE_END -- End of language layout ('left' for rtl, 'right'
                      for 'ltr')
    LANGUAGE_MARKER -- Language marker entity ('' for rtl, '&lrm'
                       for ltr)

    """
    from django.utils import translation
    from django.utils.safestring import mark_safe

    if translation.get_language_bidi():
        extra_context = {
            'LANGUAGE_DIRECTION':'rtl',
            'LANGUAGE_START':'right',
            'LANGUAGE_END':'left',
            'LANGUAGE_MARKER': mark_safe(''),
        }
    else:
        extra_context = {
            'LANGUAGE_DIRECTION':'ltr',
            'LANGUAGE_START':'left',
            'LANGUAGE_END':'right',
            'LANGUAGE_MARKER': mark_safe(''),
        }

    return extra_context