import itertools
from datetime import datetime

from flask import (
    jsonify,
    request,
    current_app,
    Blueprint
)
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm.exc import NoResultFound

from app.dao import notifications_dao
from app.dao.dao_utils import dao_rollback
from app.dao.api_key_dao import (
    save_model_api_key,
    get_model_api_keys,
    get_unsigned_secret,
    expire_api_key)
from app.dao.inbound_numbers_dao import dao_allocate_number_for_service
from app.dao.organisation_dao import dao_get_organisation_by_service_id
from app.dao.service_data_retention_dao import (
    fetch_service_data_retention,
    fetch_service_data_retention_by_id,
    fetch_service_data_retention_by_notification_type,
    insert_service_data_retention,
    update_service_data_retention,
)
from app.dao.service_sms_sender_dao import (
    dao_add_sms_sender_for_service,
    dao_update_service_sms_sender,
    archive_sms_sender,
    dao_get_service_sms_senders_by_id,
    dao_get_sms_senders_by_service_id,
    update_existing_sms_sender_with_inbound_number
)
from app.dao.templates_dao import (
    dao_get_all_templates_for_service
)
from app.dao.services_dao import (
    dao_add_user_to_service,
    dao_archive_service,
    dao_create_service,
    dao_fetch_active_users_for_service,
    dao_fetch_all_services,
    dao_fetch_all_services_by_user,
    dao_fetch_trial_services_data,
    dao_fetch_live_services_data,
    dao_fetch_monthly_historical_stats_for_service,
    dao_fetch_monthly_historical_usage_by_template_for_service,
    dao_fetch_service_by_id,
    dao_fetch_stats_for_service,
    dao_fetch_todays_stats_for_service,
    dao_fetch_todays_stats_for_all_services,
    dao_resume_service,
    dao_remove_user_from_service,
    dao_suspend_service,
    dao_update_service,
    fetch_stats_by_date_range_for_all_services
)
from app.dao.service_whitelist_dao import (
    dao_fetch_service_whitelist,
    dao_add_and_commit_whitelisted_contacts,
    dao_remove_service_whitelist
)
from app.dao.service_email_reply_to_dao import (
    add_reply_to_email_address_for_service,
    dao_get_reply_to_by_id,
    dao_get_reply_to_by_service_id,
    update_reply_to_email_address
)
from app.dao.service_letter_contact_dao import (
    dao_get_letter_contacts_by_service_id,
    dao_get_letter_contact_by_id,
    add_letter_contact_for_service,
    update_letter_contact
)
from app.dao.provider_statistics_dao import get_fragment_count
from app.dao.users_dao import get_user_by_id
from app.dao.callback_failures_dao import (
    dao_get_callback_failing_stats_for_all_services,
    dao_get_callback_failures_for_all_services,
    dao_get_failing_callback_summary,
)
from app.errors import (
    InvalidRequest,
    register_errors
)
from app.models import (
    Service,
    EmailBranding,
    SMS_TYPE,
    EMAIL_TYPE,
    LETTER_TYPE
)
from app.schema_validation import validate
from app.service import statistics
from app.service.service_data_retention_schema import (
    add_service_data_retention_request,
    update_service_data_retention_request
)
from app.service.service_senders_schema import (
    add_service_email_reply_to_request,
    add_service_letter_contact_block_request,
    add_service_sms_sender_request
)
from app.service.utils import get_whitelist_objects
from app.service.sender import send_notification_to_service_users, send_notification_to_notify_support
from app.service.send_notification import send_one_off_notification
from app.schemas import (
    service_schema,
    api_key_schema,
    permission_schema,
    notification_with_template_schema,
    notifications_filter_schema,
    detailed_service_schema
)
from app.utils import pagination_links

service_blueprint = Blueprint('service', __name__)

register_errors(service_blueprint)

@service_blueprint.errorhandler(IntegrityError)
def handle_integrity_error(exc):
    """
    Handle integrity errors caused by the unique constraint on ix_organisation_name
    """
    if 'services_name_key' or 'services_email_from_key' in str(exc):
        return jsonify(
            result='error',
            message={'name': ["Duplicate service name '{}'".format(
                exc.params.get('name', exc.params.get('email_from', ''))
            )]}
        ), 400
    current_app.logger.exception(exc)
    return jsonify(result='error', message="Internal server error"), 500

@service_blueprint.route('', methods=['GET'])
def get_services():
    only_active = request.args.get('only_active') == 'True'
    detailed = request.args.get('detailed') == 'True'
    user_id = request.args.get('user_id', None)
    include_from_test_key = request.args.get('include_from_test_key', 'True') != 'False'

    # If start and end date are not set, we are expecting today's stats.
    today = str(datetime.utcnow().date())

    start_date = datetime.strptime(request.args.get('start_date', today), '%Y-%m-%d').date()
    end_date = datetime.strptime(request.args.get('end_date', today), '%Y-%m-%d').date()

    if user_id:
        services = dao_fetch_all_services_by_user(user_id, only_active)
    elif detailed:
        result = jsonify(data=get_detailed_services(start_date=start_date, end_date=end_date,
                                                    only_active=only_active,
                                                    include_from_test_key=include_from_test_key
                                                    ))
        return result
    else:
        services = dao_fetch_all_services(only_active)
    data = service_schema.dump(services, many=True).data
    return jsonify(data=data)

@service_blueprint.route('/trial-services-data', methods=['GET'])
def get_trial_services_data():
    data = dao_fetch_trial_services_data()
    for i, row in enumerate(data):
        data[i]["domains"] = get_service_domains(row["service_id"])

    return jsonify(data=data)

@service_blueprint.route('/live-services-data', methods=['GET'])
def get_live_services_data():
    data = dao_fetch_live_services_data()
    for i, row in enumerate(data):
        data[i]["domains"] = get_service_domains(row["service_id"])
    return jsonify(data=data)

@service_blueprint.route('/<uuid:service_id>', methods=['GET'])
def get_service_by_id(service_id):
    if request.args.get('detailed') == 'True':
        data = get_detailed_service(service_id, today_only=request.args.get('today_only') == 'True')
    else:
        fetched = dao_fetch_service_by_id(service_id)

        data = service_schema.dump(fetched).data
    return jsonify(data=data)

@service_blueprint.route('', methods=['POST'])
def create_service():
    data = request.get_json()

    if not data.get('user_id'):
        errors = {'user_id': ['Missing data for required field.']}
        raise InvalidRequest(errors, status_code=400)

    # validate json with marshmallow
    service_schema.load(data)

    user = get_user_by_id(data.pop('user_id'))

    # unpack valid json into service object
    valid_service = Service.from_json(data)

    dao_create_service(valid_service, user)
    return jsonify(data=service_schema.dump(valid_service).data), 201

@service_blueprint.route('/<uuid:service_id>', methods=['POST'])
def update_service(service_id):
    req_json = request.get_json()
    fetched_service = dao_fetch_service_by_id(service_id)
    # Capture the status change here as Marshmallow changes this later
    service_going_live = fetched_service.restricted and not req_json.get('restricted', True)
    current_data = dict(service_schema.dump(fetched_service).data.items())
    current_data.update(request.get_json())

    service = service_schema.load(current_data).data
    org_type = req_json.get('organisation_type', None)
    if org_type:
        service.crown = org_type == 'central'

    if 'email_branding' in req_json:
        email_branding_id = req_json['email_branding']
        service.email_branding = None if not email_branding_id else EmailBranding.query.get(email_branding_id)

    dao_update_service(service)

    if service_going_live:
        send_notification_to_service_users(
            service_id=service_id,
            template_id=current_app.config['SERVICE_NOW_LIVE_TEMPLATE_ID'],
            personalisation={
                'service_name': current_data['name'],
                'message_limit': '{:,}'.format(current_data['message_limit'])
            },
            include_user_fields=['name']
        )

    return jsonify(data=service_schema.dump(fetched_service).data), 200

@service_blueprint.route('/<uuid:service_id>/go-live', methods=['PUT'])
def request_to_go_live(service_id):
    fetched_service = dao_fetch_service_by_id(service_id=service_id)

    if fetched_service.restricted:
        send_notification_to_notify_support(current_app.config['REQUEST_TO_GO_LIVE_TEMPLATE_ID'], request.get_json())
    return jsonify(), 200

@service_blueprint.route('/<uuid:service_id>/api-key', methods=['POST'])
def create_api_key(service_id=None):
    fetched_service = dao_fetch_service_by_id(service_id=service_id)
    valid_api_key = api_key_schema.load(request.get_json()).data
    valid_api_key.service = fetched_service
    save_model_api_key(valid_api_key)
    unsigned_api_key = get_unsigned_secret(valid_api_key.id)
    return jsonify(data=unsigned_api_key), 201

@service_blueprint.route('/<uuid:service_id>/api-key/revoke/<uuid:api_key_id>', methods=['POST'])
def revoke_api_key(service_id, api_key_id):
    expire_api_key(service_id=service_id, api_key_id=api_key_id)
    return jsonify(), 202

@service_blueprint.route('/<uuid:service_id>/api-keys', methods=['GET'])
@service_blueprint.route('/<uuid:service_id>/api-keys/<uuid:key_id>', methods=['GET'])
def get_api_keys(service_id, key_id=None):
    dao_fetch_service_by_id(service_id=service_id)

    try:
        if key_id:
            api_keys = [get_model_api_keys(service_id=service_id, id=key_id)]
        else:
            api_keys = get_model_api_keys(service_id=service_id)
    except NoResultFound:
        error = "API key not found for id: {}".format(service_id)
        raise InvalidRequest(error, status_code=404)

    return jsonify(apiKeys=api_key_schema.dump(api_keys, many=True).data), 200

@service_blueprint.route('/<uuid:service_id>/users', methods=['GET'])
def get_users_for_service(service_id):
    fetched = dao_fetch_service_by_id(service_id)
    return jsonify(data=[x.serialize() for x in fetched.users])

@service_blueprint.route('/<uuid:service_id>/users/<user_id>', methods=['POST'])
def add_user_to_service(service_id, user_id):
    service = dao_fetch_service_by_id(service_id)
    user = get_user_by_id(user_id=user_id)

    if user in service.users:
        error = 'User id: {} already part of service id: {}'.format(user_id, service_id)
        raise InvalidRequest(error, status_code=400)

    permissions = permission_schema.load(request.get_json(), many=True).data
    dao_add_user_to_service(service, user, permissions)
    data = service_schema.dump(service).data
    return jsonify(data=data), 201

@service_blueprint.route('/<uuid:service_id>/users/<user_id>', methods=['DELETE'])
def remove_user_from_service(service_id, user_id):
    service = dao_fetch_service_by_id(service_id)
    user = get_user_by_id(user_id=user_id)
    if user not in service.users:
        error = 'User not found'
        raise InvalidRequest(error, status_code=404)

    elif len(service.users) == 1:
        error = 'You cannot remove the only user for a service'
        raise InvalidRequest(error, status_code=400)

    dao_remove_user_from_service(service, user)
    return jsonify({}), 204

@service_blueprint.route('/<uuid:service_id>/fragment/aggregate_statistics')
def get_service_provider_aggregate_statistics(service_id):
    year = request.args.get('year')
    if year is not None:
        try:
            year = int(year)
        except ValueError:
            raise InvalidRequest('Year must be a number', status_code=400)
    return jsonify(data=get_fragment_count(service_id, year=year))

# This is placeholder get method until more thought
# goes into how we want to fetch and view various items in history
# tables. This is so product owner can pass stories as done
@service_blueprint.route('/<uuid:service_id>/history', methods=['GET'])
def get_service_history(service_id):
    from app.models import (Service, ApiKey, TemplateHistory)
    from app.schemas import (
        service_history_schema,
        api_key_history_schema,
        template_history_schema
    )

    service_history = Service.get_history_model().query.filter_by(id=service_id).all()
    service_data = service_history_schema.dump(service_history, many=True).data
    api_key_history = ApiKey.get_history_model().query.filter_by(service_id=service_id).all()
    api_keys_data = api_key_history_schema.dump(api_key_history, many=True).data

    template_history = TemplateHistory.query.filter_by(service_id=service_id).all()
    template_data, errors = template_history_schema.dump(template_history, many=True)

    data = {
        'service_history': service_data,
        'api_key_history': api_keys_data,
        'template_history': template_data,
        'events': []}

    return jsonify(data=data)

@service_blueprint.route('/<uuid:service_id>/notifications', methods=['GET'])
def get_all_notifications_for_service(service_id):
    data = notifications_filter_schema.load(request.args).data
    if data.get('to'):
        notification_type = data.get('template_type')[0] if data.get('template_type') else None
        return search_for_notification_by_to_field(service_id=service_id,
                                                   search_term=data['to'],
                                                   statuses=data.get('status'),
                                                   notification_type=notification_type)
    page = data['page'] if 'page' in data else 1
    page_size = data['page_size'] if 'page_size' in data else current_app.config.get('PAGE_SIZE')
    limit_days = data.get('limit_days')
    include_jobs = data.get('include_jobs', True)
    include_from_test_key = data.get('include_from_test_key', False)

    pagination = notifications_dao.get_notifications_for_service(
        service_id,
        filter_dict=data,
        page=page,
        page_size=page_size,
        limit_days=limit_days,
        include_jobs=include_jobs,
        include_from_test_key=include_from_test_key
    )
    kwargs = request.args.to_dict()
    kwargs['service_id'] = service_id
    return jsonify(
        notifications=notification_with_template_schema.dump(pagination.items, many=True).data,
        page_size=page_size,
        total=pagination.total,
        links=pagination_links(
            pagination,
            '.get_all_notifications_for_service',
            **kwargs
        )
    ), 200

@service_blueprint.route('/<uuid:service_id>/notifications/<uuid:notification_id>', methods=['GET'])
def get_notification_for_service(service_id, notification_id):

    notification = notifications_dao.get_notification_with_personalisation(
        service_id,
        notification_id,
        key_type=None,
    )
    return jsonify(
        notification_with_template_schema.dump(notification).data,
    ), 200

def search_for_notification_by_to_field(service_id, search_term, statuses, notification_type):
    results = notifications_dao.dao_get_notifications_by_to_field(
        service_id=service_id,
        search_term=search_term,
        statuses=statuses,
        notification_type=notification_type
    )
    return jsonify(
        notifications=notification_with_template_schema.dump(results, many=True).data
    ), 200

@service_blueprint.route('/<uuid:service_id>/notifications/monthly', methods=['GET'])
def get_monthly_notification_stats(service_id):
    service = dao_fetch_service_by_id(service_id)
    try:
        return jsonify(data=dao_fetch_monthly_historical_stats_for_service(
            service.id,
            int(request.args.get('year', 'NaN'))
        ))
    except ValueError:
        raise InvalidRequest('Year must be a number', status_code=400)

def get_detailed_service(service_id, today_only=False):
    service = dao_fetch_service_by_id(service_id)
    stats_fn = dao_fetch_todays_stats_for_service if today_only else dao_fetch_stats_for_service
    stats = stats_fn(service_id)

    service.statistics = statistics.format_statistics(stats)

    return detailed_service_schema.dump(service).data

def get_detailed_services(start_date, end_date, only_active=False, include_from_test_key=True):
    if start_date == datetime.utcnow().date():
        stats = dao_fetch_todays_stats_for_all_services(include_from_test_key=include_from_test_key,
                                                        only_active=only_active)
    else:

        stats = fetch_stats_by_date_range_for_all_services(start_date=start_date,
                                                           end_date=end_date,
                                                           include_from_test_key=include_from_test_key,
                                                           only_active=only_active)
    results = []
    for service_id, rows in itertools.groupby(stats, lambda x: x.service_id):
        rows = list(rows)
        if rows[0].count is None:
            s = statistics.create_zeroed_stats_dicts()
        else:
            s = statistics.format_statistics(rows)
        sid = str(rows[0].service_id)

        s[EMAIL_TYPE]['templates'] = len(dao_get_all_templates_for_service(sid, EMAIL_TYPE))
        s[SMS_TYPE]['templates'] = len(dao_get_all_templates_for_service(sid, SMS_TYPE))
        s[LETTER_TYPE]['templates'] = len(dao_get_all_templates_for_service(sid, LETTER_TYPE))

        results.append({
            'id': sid,
            'name': rows[0].name,
            'notification_type': rows[0].notification_type,
            'research_mode': rows[0].research_mode,
            'restricted': rows[0].restricted,
            'active': rows[0].active,
            'created_at': rows[0].created_at,
            'statistics': s,
            'domains': get_service_domains(sid),
            'organisation_type': rows[0].organisation_type
        })
    return results

@service_blueprint.route('/<uuid:service_id>/whitelist', methods=['GET'])
def get_whitelist(service_id):
    from app.models import (EMAIL_TYPE, MOBILE_TYPE)
    service = dao_fetch_service_by_id(service_id)

    if not service:
        raise InvalidRequest("Service does not exist", status_code=404)

    whitelist = dao_fetch_service_whitelist(service.id)
    return jsonify(
        email_addresses=[item.recipient for item in whitelist
                         if item.recipient_type == EMAIL_TYPE],
        phone_numbers=[item.recipient for item in whitelist
                       if item.recipient_type == MOBILE_TYPE]
    )

@service_blueprint.route('/<uuid:service_id>/whitelist', methods=['PUT'])
def update_whitelist(service_id):
    # doesn't commit so if there are any errors, we preserve old values in db
    dao_remove_service_whitelist(service_id)
    try:
        whitelist_objs = get_whitelist_objects(service_id, request.get_json())
    except ValueError as e:
        current_app.logger.exception(e)
        dao_rollback()
        msg = '{} is not a valid email address or phone number'.format(str(e))
        raise InvalidRequest(msg, 400)
    else:
        dao_add_and_commit_whitelisted_contacts(whitelist_objs)
        return '', 204

@service_blueprint.route('/<uuid:service_id>/archive', methods=['POST'])
def archive_service(service_id):
    """
    When a service is archived the service is made inactive, templates are archived and api keys are revoked.
    There is no coming back from this operation.
    :param service_id:
    :return:
    """
    service = dao_fetch_service_by_id(service_id)

    if service.active:
        dao_archive_service(service.id)

    return '', 204

@service_blueprint.route('/<uuid:service_id>/suspend', methods=['POST'])
def suspend_service(service_id):
    """
    Suspending a service will mark the service as inactive and revoke API keys.
    :param service_id:
    :return:
    """
    service = dao_fetch_service_by_id(service_id)

    if service.active:
        dao_suspend_service(service.id)

    return '', 204

@service_blueprint.route('/<uuid:service_id>/resume', methods=['POST'])
def resume_service(service_id):
    """
    Resuming a service that has been suspended will mark the service as active.
    The service will need to re-create API keys
    :param service_id:
    :return:
    """
    service = dao_fetch_service_by_id(service_id)

    if not service.active:
        dao_resume_service(service.id)

    return '', 204

@service_blueprint.route('/<uuid:service_id>/notifications/templates_usage/monthly', methods=['GET'])
def get_monthly_template_usage(service_id):
    try:
        data = dao_fetch_monthly_historical_usage_by_template_for_service(
            service_id,
            int(request.args.get('year', 'NaN'))
        )

        stats = list()
        for i in data:
            stats.append(
                {
                    'template_id': str(i.template_id),
                    'name': i.name,
                    'type': i.template_type,
                    'month': i.month,
                    'year': i.year,
                    'count': i.count,
                    'is_precompiled_letter': i.is_precompiled_letter
                }
            )

        return jsonify(stats=stats), 200
    except ValueError:
        raise InvalidRequest('Year must be a number', status_code=400)

@service_blueprint.route('/<uuid:service_id>/send-notification', methods=['POST'])
def create_one_off_notification(service_id):
    resp = send_one_off_notification(service_id, request.get_json())
    return jsonify(resp), 201

@service_blueprint.route('/<uuid:service_id>/email-reply-to', methods=["GET"])
def get_email_reply_to_addresses(service_id):
    result = dao_get_reply_to_by_service_id(service_id)
    return jsonify([i.serialize() for i in result]), 200

@service_blueprint.route('/<uuid:service_id>/email-reply-to/<uuid:reply_to_id>', methods=["GET"])
def get_email_reply_to_address(service_id, reply_to_id):
    result = dao_get_reply_to_by_id(service_id=service_id, reply_to_id=reply_to_id)
    return jsonify(result.serialize()), 200

@service_blueprint.route('/<uuid:service_id>/email-reply-to', methods=['POST'])
def add_service_reply_to_email_address(service_id):
    # validate the service exists, throws ResultNotFound exception.
    dao_fetch_service_by_id(service_id)
    form = validate(request.get_json(), add_service_email_reply_to_request)
    new_reply_to = add_reply_to_email_address_for_service(service_id=service_id,
                                                          email_address=form['email_address'],
                                                          is_default=form.get('is_default', True))
    return jsonify(data=new_reply_to.serialize()), 201

@service_blueprint.route('/<uuid:service_id>/email-reply-to/<uuid:reply_to_email_id>', methods=['POST'])
def update_service_reply_to_email_address(service_id, reply_to_email_id):
    # validate the service exists, throws ResultNotFound exception.
    dao_fetch_service_by_id(service_id)
    form = validate(request.get_json(), add_service_email_reply_to_request)
    new_reply_to = update_reply_to_email_address(service_id=service_id,
                                                 reply_to_id=reply_to_email_id,
                                                 email_address=form['email_address'],
                                                 is_default=form.get('is_default', True))
    return jsonify(data=new_reply_to.serialize()), 200

@service_blueprint.route('/<uuid:service_id>/letter-contact', methods=["GET"])
def get_letter_contacts(service_id):
    result = dao_get_letter_contacts_by_service_id(service_id)
    return jsonify([i.serialize() for i in result]), 200

@service_blueprint.route('/<uuid:service_id>/letter-contact/<uuid:letter_contact_id>', methods=["GET"])
def get_letter_contact_by_id(service_id, letter_contact_id):
    result = dao_get_letter_contact_by_id(service_id=service_id, letter_contact_id=letter_contact_id)
    return jsonify(result.serialize()), 200

@service_blueprint.route('/<uuid:service_id>/letter-contact', methods=['POST'])
def add_service_letter_contact(service_id):
    # validate the service exists, throws ResultNotFound exception.
    dao_fetch_service_by_id(service_id)
    form = validate(request.get_json(), add_service_letter_contact_block_request)
    new_letter_contact = add_letter_contact_for_service(service_id=service_id,
                                                        contact_block=form['contact_block'],
                                                        is_default=form.get('is_default', True))
    return jsonify(data=new_letter_contact.serialize()), 201

@service_blueprint.route('/<uuid:service_id>/letter-contact/<uuid:letter_contact_id>', methods=['POST'])
def update_service_letter_contact(service_id, letter_contact_id):
    # validate the service exists, throws ResultNotFound exception.
    dao_fetch_service_by_id(service_id)
    form = validate(request.get_json(), add_service_letter_contact_block_request)
    new_reply_to = update_letter_contact(service_id=service_id,
                                         letter_contact_id=letter_contact_id,
                                         contact_block=form['contact_block'],
                                         is_default=form.get('is_default', True))
    return jsonify(data=new_reply_to.serialize()), 200

@service_blueprint.route('/<uuid:service_id>/sms-sender', methods=['POST'])
def add_service_sms_sender(service_id):
    dao_fetch_service_by_id(service_id)
    form = validate(request.get_json(), add_service_sms_sender_request)
    inbound_number_id = form.get('inbound_number_id', None)
    sms_sender = form.get('sms_sender')

    if inbound_number_id:
        updated_number = dao_allocate_number_for_service(service_id=service_id, inbound_number_id=inbound_number_id)
        # the sms_sender in the form is not set, use the inbound number
        sms_sender = updated_number.number
        existing_sms_sender = dao_get_sms_senders_by_service_id(service_id)
        # we don't want to create a new sms sender for the service if we are allocating an inbound number.
        if len(existing_sms_sender) == 1:
            update_existing_sms_sender = existing_sms_sender[0]
            new_sms_sender = update_existing_sms_sender_with_inbound_number(
                service_sms_sender=update_existing_sms_sender,
                sms_sender=sms_sender,
                inbound_number_id=inbound_number_id)

            return jsonify(new_sms_sender.serialize()), 201

    new_sms_sender = dao_add_sms_sender_for_service(service_id=service_id,
                                                    sms_sender=sms_sender,
                                                    is_default=form['is_default'],
                                                    inbound_number_id=inbound_number_id
                                                    )
    return jsonify(new_sms_sender.serialize()), 201

@service_blueprint.route('/<uuid:service_id>/sms-sender/<uuid:sms_sender_id>', methods=['POST'])
def update_service_sms_sender(service_id, sms_sender_id):
    form = validate(request.get_json(), add_service_sms_sender_request)

    sms_sender_to_update = dao_get_service_sms_senders_by_id(service_id=service_id,
                                                             service_sms_sender_id=sms_sender_id)
    if sms_sender_to_update.inbound_number_id and form['sms_sender'] != sms_sender_to_update.sms_sender:
        raise InvalidRequest("You can not change the inbound number for service {}".format(service_id),
                             status_code=400)

    new_sms_sender = dao_update_service_sms_sender(service_id=service_id,
                                                   service_sms_sender_id=sms_sender_id,
                                                   is_default=form['is_default'],
                                                   sms_sender=form['sms_sender']
                                                   )
    return jsonify(new_sms_sender.serialize()), 200

@service_blueprint.route('/<uuid:service_id>/sms-sender/<uuid:sms_sender_id>/archive', methods=['POST'])
def delete_service_sms_sender(service_id, sms_sender_id):
    sms_sender = archive_sms_sender(service_id, sms_sender_id)

    return jsonify(data=sms_sender.serialize()), 200

@service_blueprint.route('/<uuid:service_id>/sms-sender/<uuid:sms_sender_id>', methods=['GET'])
def get_service_sms_sender_by_id(service_id, sms_sender_id):
    sms_sender = dao_get_service_sms_senders_by_id(service_id=service_id,
                                                   service_sms_sender_id=sms_sender_id)
    return jsonify(sms_sender.serialize()), 200

@service_blueprint.route('/<uuid:service_id>/sms-sender', methods=['GET'])
def get_service_sms_senders_for_service(service_id):
    sms_senders = dao_get_sms_senders_by_service_id(service_id=service_id)
    return jsonify([sms_sender.serialize() for sms_sender in sms_senders]), 200

@service_blueprint.route('/<uuid:service_id>/organisation', methods=['GET'])
def get_organisation_for_service(service_id):
    organisation = dao_get_organisation_by_service_id(service_id=service_id)
    return jsonify(organisation.serialize() if organisation else {}), 200

@service_blueprint.route('/failing-callback-stats', methods=['GET'])
def get_callback_failing_stats_for_all_services():
    stats = dao_get_callback_failing_stats_for_all_services()
    data = {
        'total_failure_count': stats.total_failure_count,
        'failed_notification_count': stats.failed_notification_count,
    }
    return jsonify(data), 200

@service_blueprint.route('/failing-callbacks', methods=['GET'])
def get_callbacks_failing_for_all_services():
    page = int(request.args.get('page', 1))
    paginated_callbacks = dao_get_callback_failures_for_all_services(page=page)
    return jsonify(
        callbacks=[callback.serialize() for callback in paginated_callbacks.items],
        links=pagination_links(
            paginated_callbacks,
            '.get_callbacks_failing_for_all_services',
            **request.args.to_dict()
        )
    ), 200

@service_blueprint.route('/failing-callback-summary', methods=['GET'])
def get_failing_callback_summary():
    data = dao_get_failing_callback_summary()
    summary = list()

    for i in data:
        summary.append(
            {
                'failure_count': i.failure_count,
                'service_id': i.service_id,
                'service_name': i.service_name,
            }
        )

    return jsonify(summary), 200

@service_blueprint.route('/unique', methods=["GET"])
def is_service_name_unique():
    service_id, name, email_from = check_request_args(request)

    name_exists = Service.query.filter_by(name=name).first()

    email_from_exists = Service.query.filter(
        Service.email_from == email_from,
        Service.id != service_id
    ).first()

    result = not (name_exists or email_from_exists)
    return jsonify(result=result), 200

@service_blueprint.route('/<uuid:service_id>/data-retention', methods=['GET'])
def get_data_retention_for_service(service_id):
    data_retention_list = fetch_service_data_retention(service_id)
    return jsonify([data_retention.serialize() for data_retention in data_retention_list]), 200

@service_blueprint.route('/<uuid:service_id>/data-retention/notification-type/<notification_type>', methods=['GET'])
def get_data_retention_for_service_notification_type(service_id, notification_type):
    data_retention = fetch_service_data_retention_by_notification_type(service_id, notification_type)
    return jsonify(data_retention.serialize() if data_retention else {}), 200

@service_blueprint.route('/<uuid:service_id>/data-retention/<uuid:data_retention_id>', methods=['GET'])
def get_data_retention_for_service_by_id(service_id, data_retention_id):
    data_retention = fetch_service_data_retention_by_id(service_id, data_retention_id)
    return jsonify(data_retention.serialize() if data_retention else {}), 200

@service_blueprint.route('/<uuid:service_id>/data-retention', methods=['POST'])
def create_service_data_retention(service_id):
    form = validate(request.get_json(), add_service_data_retention_request)
    try:
        new_data_retention = insert_service_data_retention(
            service_id=service_id,
            notification_type=form.get("notification_type"),
            days_of_retention=form.get("days_of_retention")
        )
    except IntegrityError:
        raise InvalidRequest(
            message="Service already has data retention for {} notification type".format(form.get("notification_type")),
            status_code=400
        )

    return jsonify(result=new_data_retention.serialize()), 201

@service_blueprint.route('/<uuid:service_id>/data-retention/<uuid:data_retention_id>', methods=['POST'])
def modify_service_data_retention(service_id, data_retention_id):
    form = validate(request.get_json(), update_service_data_retention_request)

    update_count = update_service_data_retention(
        service_data_retention_id=data_retention_id,
        service_id=service_id,
        days_of_retention=form.get("days_of_retention")
    )
    if update_count == 0:
        raise InvalidRequest(
            message="The service data retention for id: {} was not found for service: {}".format(data_retention_id,
                                                                                service_id),
            status_code=404)

    return '', 204

def check_request_args(request):
    service_id = request.args.get('service_id')
    name = request.args.get('name', None)
    email_from = request.args.get('email_from', None)
    errors = []
    if not service_id:
        errors.append({'service_id': ["Can't be empty"]})
    if not name:
        errors.append({'name': ["Can't be empty"]})
    if not email_from:
        errors.append({'email_from': ["Can't be empty"]})
    if errors:
        raise InvalidRequest(errors, status_code=400)
    return service_id, name, email_from

def get_service_domains(service_id):
    domains = set()
    for user in dao_fetch_active_users_for_service(service_id):
        parts = user.email_address.split('@')
        if len(parts) != 2:
            continue
        domains.add(parts[1].lower())
    return sorted(list(domains))


#1. Use getGeocodeLocation to get the latitude and longitude coordinates of the location string.

#2.  Use foursquare API to find a nearby restaurant with the latitude, longitude, and mealType strings.
#HINT: format for url will be something like https://api.foursquare.com/v2/venues/search?client_id=CLIENT_ID&client_secret=CLIENT_SECRET&v=20130815&ll=40.7,-74&query=sushi

#3. Grab the first restaurant
#4. Get a  300x300 picture of the restaurant using the venue_id (you can change this by altering the 300x300 value in the URL or replacing it with 'orginal' to get the original picture
#5. Grab the first image
#6. If no image is available, insert default a image url
#7. Return a dictionary containing the restaurant name, address, and image url

from geocode import getGeocodeLocation
import requests
from dotenv import dotenv_values

# Get enviroment variables
config = dotenv_values(".env")

def findARestaurant(mealType: str, location: str) -> dict:
	"""
    Grab information about restaurants from Foursquare API

    :param mealType: Type of food to search
    :param location: String with the place to search
	:return: dictionary containing the restaurant name, address, and image url
    """

	# Keys for Foursquare API
	CLIENT_ID = config["CLIENT_ID"]
	CLIENT_SECRET = config["CLIENT_SECRET"]

	latitude, longitude = getGeocodeLocation(location) # Function to get lat, long coordinates from google map's API
	locationString = str(latitude) + ", " + str(longitude)
	params = dict(
		client_id=CLIENT_ID,
		client_secret=CLIENT_SECRET,
		v='20180323',
		ll=locationString,
		query=mealType,
		limit=1
	)
	try:
		url = 'https://api.foursquare.com/v2/venues/search'
		resp = requests.get(url=url, params=params)
		data = resp.json()
		if data["meta"]["code"] == 200:
			# Grab main info of the first restaurant
			first_restaurant = data["response"]["venues"][0]
			info_restaurant = {}
			info_restaurant["Restaurant name"] = first_restaurant["name"]
			restaurant_address = first_restaurant["location"]["formattedAddress"]
			info_restaurant["Restaurant address"] = " ".join(restaurant_address)

			# Grab image of the restaurant
			restaurant_id = first_restaurant["id"]
			url_photos = f'https://api.foursquare.com/v2/venues/{restaurant_id}/photos'
			params = dict(
				client_id=CLIENT_ID,
				client_secret=CLIENT_SECRET,
				v='20180323',
			)
			resp = requests.get(url=url_photos, params=params)
			photos_data = resp.json()
			if photos_data["meta"]["code"] == 200:
				first_photo = photos_data.get("response").get("photos").get("items")
				if len(first_photo) != 0:
					image_url = first_photo[0]["prefix"] + "300x300" + first_photo[0]["suffix"]
				else:
					image_url = "https://st4.depositphotos.com/14953852/22772/v/600/depositphotos_227725020-stock-illustration-no-image-available-icon-flat.jpg"
				info_restaurant["Image url"] = image_url
			else:
				raise Exception(f'Error type: {photos_data["meta"]["errorType"]}, Code: {photos_data["meta"]["code"]}')
			#Print values of the first restaurant
			for key, value in info_restaurant.items():
				print(f"{key}: {value}", end="")
			print("-" * 100)
		else:
			print(f'Error type: {data["meta"]["errorType"]}, Code: {data["meta"]["code"]}')
	except requests.HTTPError as http_err:
		print(f'HTTP error occurred: {http_err}')
	except Exception as err:
		print(f'Other error occurred: {err}')

if __name__ == '__main__':
	findARestaurant("Pizza", "Tokyo, Japan")
	findARestaurant("Tacos", "Jakarta, Indonesia")
	findARestaurant("Tapas", "Maputo, Mozambique")
	findARestaurant("Falafel", "Cairo, Egypt")
	findARestaurant("Spaghetti", "New Delhi, India")
	findARestaurant("Cappuccino", "Geneva, Switzerland")
	findARestaurant("Sushi", "Los Angeles, California")
	findARestaurant("Steak", "La Paz, Bolivia")
	findARestaurant("Gyros", "Sydney, Australia")


from __future__ import annotations

import typing

import plotly

import pandas as pd

# pylint: disable=wrong-import-position
if typing.TYPE_CHECKING:
    import BaseOpts

import PlotUtilsPlotly
import Utils
import BaseDB

from BaseLog import log_error, log_info
from Plotting import plotmissionsingle

@plotmissionsingle
def mission_int_sensors(
    base_opts: BaseOpts.BaseOptions,
    mission_str: list,
    dive=None,
    generate_plots=True,
    dbcon=None,
) -> tuple[list, list]:
    """Plots internal pressure, RH, temp"""
    log_info("Starting mission_int_sensors")

    if dbcon == None:
        conn = Utils.open_mission_database(base_opts)
        log_info("mission_int_sensors db opened")
    else:
        conn = dbcon

    if not conn:
        log_error("Could not open mission database")
        return ([], [])

    if dive == None:
        clause = ""
    else:
        clause = f"WHERE dive <= {dive}"

    fig = plotly.graph_objects.Figure()
    df = None
    try:
        df = pd.read_sql_query(
            f"SELECT dive,log_HUMID,log_INTERNAL_PRESSURE from dives {clause} ORDER BY dive ASC",
            conn,
        ).sort_values("dive")
    except:
        log_error("Could not fetch needed columns", "exc")
        if dbcon == None:
            conn.close()
            log_info("mission_int_sensors db closed")
        return ([], [])

    df_int_temperature = None
    try:
        df_int_temperature = pd.read_sql_query(
            f"SELECT dive,log_TEMP from dives {clause} ORDER BY dive ASC",
            conn,
        ).sort_values("dive")
    except pd.errors.DatabaseError as e:
        if e.args[0].endswith("no such column: log_TEMP"):
            pass
        else:
            log_error("Unexpected error fetching log_TEMP", "exc")

    for v in ["log_INTERNAL_PRESSURE", "log_HUMID"]:
        if len(df["dive"].to_numpy()) > 0:
            m, b = Utils.dive_var_trend(
                base_opts, df["dive"].to_numpy(), df[v].to_numpy()
            )
            BaseDB.addValToDB(
                base_opts, df["dive"].to_numpy()[-1], f"{v}_slope", m, con=conn
            )

    if dbcon == None:
        try:
            conn.commit()
        except Exception as e:
            conn.rollback()
            log_error(f"Failed commit, MissionIntSensors {e}", "exc")

        log_info("mission_int_sensors db closed")
        conn.close()

    if not generate_plots:
        log_info("Returning")
        return ([], [])

    fig.add_trace(
        {
            "name": "Internl Pressure",
            "x": df["dive"],
            "y": df["log_INTERNAL_PRESSURE"],
            "yaxis": "y1",
            "mode": "lines",
            "line": {
                "dash": "solid",
                "color": "DarkMagenta",
                "width": 1,
            },
            "hovertemplate": "Internal Pressure
Dive %{x:.0f}
pressure %{y:.2f} psia<extra></extra>",
        }
    )

    fig.add_trace(
        {
            "name": "Relative Humidity",
            "x": df["dive"],
            "y": df["log_HUMID"],
            "yaxis": "y2",
            "mode": "lines",
            "line": {
                "dash": "solid",
                "color": "DarkBlue",
                "width": 1,
            },
            "hovertemplate": "Relative Humidity
Dive %{x:.0f}
RH %{y:.2f} percent<extra></extra>",
        }
    )
    if df_int_temperature is not None:
        fig.add_trace(
            {
                "name": "Temperature",
                "x": df_int_temperature["dive"],
                "y": df_int_temperature["log_TEMP"],
                "yaxis": "y3",
                "mode": "lines",
                "line": {
                    "dash": "solid",
                    "color": "Red",
                    "width": 1,
                },
                "hovertemplate": "Temperature
Dive %{x:.0f}
T %{y:.2f}C<extra></extra>",
            }
        )

    title_text = f"{mission_str}
Internal Sensors"

    fig.update_layout(
        {
            "xaxis": {
                "title": "Dive Number",
                "showgrid": True,
                "domain": [0.0, 0.9],
            },
            "yaxis": {
                "title": "Internal Pressure (psia)",
                "showgrid": True,
                "tickformat": ".02f",
                "position": 0.0,
            },
            "yaxis2": {
                "title": "Relative Humidity",
                "overlaying": "y1",
                "side": "right",
                "showgrid": False,
                "tickformat": ".02f",
                "position": 0.9,
            },
            "yaxis3": {
                "title": "Temperature (C)",
                "overlaying": "y1",
                "anchor": "free",
                "side": "right",
                "position": 1,
                "showgrid": False,
            },
            "legend": {
                "x": 1.05,
                "y": 1,
            },
            "title": {
                "text": title_text,
                "xanchor": "center",
                "yanchor": "top",
                "x": 0.5,
                "y": 0.95,
            },
            "margin": {
                "b": 120,
            },
        },
    )
    return (
        [fig],
        PlotUtilsPlotly.write_output_files(
            base_opts,
            "eng_mission_int_sensors",
            fig,
        ),
    )


from __future__ import division
from typing import Iterator, Optional

import torch
from torch.utils.data import Dataset
from torch.utils.data import DistributedSampler as _DistributedSampler

from mmseg.core.utils import sync_random_seed
from mmseg.utils import get_device

class DistributedSampler(_DistributedSampler):
    """DistributedSampler inheriting from
    `torch.utils.data.DistributedSampler`.

    Args:
        datasets (Dataset): the dataset will be loaded.
        num_replicas (int, optional): Number of processes participating in
            distributed training. By default, world_size is retrieved from the
            current distributed group.
        rank (int, optional):  Rank of the current process within num_replicas.
            By default, rank is retrieved from the current distributed group.
        shuffle (bool): If True (default), sampler will shuffle the indices.
        seed (int): random seed used to shuffle the sampler if
            :attr:`shuffle=True`. This number should be identical across all
            processes in the distributed group. Default: ``0``.
    """

    def __init__(self,
                 dataset: Dataset,
                 num_replicas: Optional[int] = None,
                 rank: Optional[int] = None,
                 shuffle: bool = True,
                 seed=0) -> None:
        super().__init__(
            dataset, num_replicas=num_replicas, rank=rank, shuffle=shuffle)

        # In distributed sampling, different ranks should sample
        # non-overlapped data in the dataset. Therefore, this function
        # is used to make sure that each rank shuffles the data indices
        # in the same order based on the same seed. Then different ranks
        # could use different indices to select non-overlapped data from the
        # same data list.
        device = get_device()
        self.seed = sync_random_seed(seed, device)

    def __iter__(self) -> Iterator:
        """
         Yields:
            Iterator: iterator of indices for rank.
        """
        # deterministically shuffle based on epoch
        if self.shuffle:
            g = torch.Generator()
            # When :attr:`shuffle=True`, this ensures all replicas
            # use a different random ordering for each epoch.
            # Otherwise, the next iteration of this sampler will
            # yield the same ordering.
            g.manual_seed(self.epoch + self.seed)
            indices = torch.randperm(len(self.dataset), generator=g).tolist()
        else:
            indices = torch.arange(len(self.dataset)).tolist()

        # add extra samples to make it evenly divisible
        indices += indices[:(self.total_size - len(indices))]
        assert len(indices) == self.total_size

        # subsample
        indices = indices[self.rank:self.total_size:self.num_replicas]
        assert len(indices) == self.num_samples

        return iter(indices)


from flask import Flask, request

app = Flask(__name__)

@app.route('/')
@app.route('/hello')
def hello():
    return "Hello"

if __name__ == "__main__":
    service_name = 'hello'
    service_port = 5000

    consul_service = ConsulService()
    consul_service.setup(service_name, service_port, request.host)

    app.run(host='0.0.0.0', port=service_port, debug=True)


"""
Jumbotron Calibration.
Figures out how the markers found by artoolkit map to the jumbotron screen.
"""
from __future__ import print_function
import logging
import math
import operator
from PIL import Image, ImageDraw
from PIL.ExifTags import TAGS
import artoolkit
from vectypes import Mat4, Vec3

# ----------------------------------------------------------------------

def _get_camera_xform(image):
    """Return the xform that maps world coordinates to image
    coordinates. Uses the Iphone's lens characteristics."""
    film_size    = 6.35 # Iphone sensor size, in mm
    focal_length = 3.85 # Iphone focal length, in mm
    width, height = image.size
    aspect = float(width) / height
    fov_y = 2 * math.atan2(film_size/aspect, 2 * focal_length)
    xform = Mat4.new_translate(width/2.0,  height/2.0, 0)
    xform.scale(-width/2.0, -height/2.0, 1)
    xform *= Mat4.new_perspective(fov_y, aspect, 1, 10)
    return xform

def _get_change_basis_xform(center, up, normal):
    """Return a matrix that changes from the given basis to the x-y-z
    basis. Basically, this 'straightens' out the plane described by
    the basis. The 'right' axis is calculated from the up and normal."""
    normal = normal.normalized()
    right = up.cross(normal).normalize()
    up    = normal.cross(right).normalize()

    xform = Mat4.new_translate(*center)
    xform *= Mat4.new_change_basis(right, up, normal, center).inverse()
    return xform

def _find_best_change_basis_xform(markers):
    """Find the plane that best fits the markers, and return the
    matrix that transforms this plane to the x-y-z plane."""
    centers = (marker.world_center for marker in markers)
    ups     = (marker.world_up     for marker in markers)
    normals = (marker.world_normal for marker in markers)
    avg_center = reduce(operator.add, centers) / len(markers)
    avg_up     = reduce(operator.add, ups    ) / len(markers)
    avg_normal = reduce(operator.add, normals) / len(markers)
    return _get_change_basis_xform(avg_center, avg_up, avg_normal)

def _align_markers(markers):
    """Project the markers along the camera view onto the same plane"""

    # Calculate 'average' plane defined by the markers
    centers = (marker.world_center for marker in markers)
    normals = (marker.world_normal for marker in markers)
    avg_center = reduce(operator.add, centers) / len(markers)
    avg_normal = (reduce(operator.add, normals) / len(markers)).normalize()
    planen = avg_normal
    planed = -avg_normal.dot(avg_center)  # Could use a constant as well

    # Determine where the ray from the origin (camera center) to each
    # marker vertex intersects the 'average' plane
    for marker in markers:
        ray =  marker.world_center
        norm = -planed / planen.dot(ray)
        marker.world_center = ray * norm
        marker.world_vertices = [vertex * norm for vertex in marker.world_vertices]

    centers = (marker.world_center for marker in markers)
    avg_center = reduce(operator.add, centers) / len(markers)

def _find_display_coords(marker, marker_vertices, display):
    """Calculate the display corners from the marker corners. For now,
    assume everything is orthogonal."""

    # Get bounding box
    minv = Vec3.min(*marker_vertices)
    maxv = Vec3.max(*marker_vertices)
    size = maxv - minv

    # Stretch to account for white border
    minv -= size / 8.0
    maxv += size / 8.0
    size = maxv - minv

    # Stretch to display aspect ratio
    direction = marker.world_direction
    dar = display.aspectRatio
    if direction == 1 or direction == 3 :
        dar = 1.0 / dar

    if dar > 1:
        # The display is bigger horizontally than vertically.
        width = size.y * dar
        stretch = (width - size.x) / 2.0
        # The image is centered, so stretch in both directions.
        minv.x -= stretch
        maxv.x += stretch

    else:
        # The display is bigger vertically than horizontally.
        height = size.x / dar
        stretch = (height - size.y) / 2.0
        # The image is centered, so stretch in both directions.
        minv.y -= stretch
        maxv.y += stretch

    # Done
    return [minv, maxv]

def _draw_vertices(draw, vertices, color="white", width=5):
    """Draw a polygon"""
    # Convert to 2d tuples
    vertices = [(v.x, v.y) for v in vertices]
    if width:
        vertices.append(vertices[0])
        for u, v in zip(vertices[:-1], vertices[1:]):
            draw.line((u, v), color, width)
    else:
        draw.polygon(vertices, color)

def _draw_rectangle(draw, minv, maxv, color="white", width=5):
    """Draw a rectangle"""
    # Convert to 2d tuples
    vertices = [minv, Vec3(minv.x, maxv.y), maxv, Vec3(maxv.x, minv.y)]
    _draw_vertices(draw, vertices, color, width)

def _debug(**kwargs):
    """Log a list of keyword/value pairs"""
    for key, value in kwargs.items():
        logging.debug("%s\t: %s", key, str(value))

def _debug_image(draw, coords, xform, plane_xform, cam_xform):
    """Draw debugging info on the calibration image"""
    minv, maxv = coords
    ixform = xform.inverse()
    iplane_xform = plane_xform.inverse()
    icam_xform = cam_xform.inverse()
    vertices = (Vec3(maxv.x, minv.y, minv.z),
                Vec3(maxv.x, maxv.y, minv.z),
                Vec3(minv.x, maxv.y, minv.z),
                Vec3(minv.x, minv.y, minv.z))
    _draw_vertices(draw, vertices, (255, 0, 0, 255), width=7)

    vertices = [icam_xform.transform(vertex) for vertex in vertices]
    vertices = [ixform.transform(vertex) for vertex in vertices]
    vertices1 = (cam_xform.transform(vertex) for vertex in vertices)
    _draw_vertices(draw, vertices1, (0, 255, 0, 200), width=5)

    vertices = [iplane_xform.transform(vertex) for vertex in vertices]
    vertices1 = (cam_xform.transform(vertex) for vertex in vertices)
    _draw_vertices(draw, vertices1, (0, 0, 255, 200), width=3)

def _reorient_image(image):
    """Reorient the image based on exif tags in the jpg file"""
    try:
        exif = image._getexif()
        if exif:
            for tag, value in exif.items():
                decoded = TAGS.get(tag, tag)
                if decoded == 'Orientation':
                    if   value == 3:
                        image = image.rotate(180)
                    elif value == 6:
                        image = image.rotate(270)
                    elif value == 8:
                        image = image.rotate(90)
                    break
    except KeyError as ke:
        logging.error("KeyError in reorient_image: " + str(ke))
        # The version of PIL on the server is OLD and throws key
        # errors if the exif isn't found
        pass
    except AttributeError:
        # No exif tags
        pass
    return image

def _calibrate(jumbotron, displays, image, debug=False, debug_image=False):
    """Calibrate a jumbotron with a calibration images."""
    if debug_image:
        draw = ImageDraw.Draw(image, "RGBA")

    # Initialize display viewports
    for display in displays.values():
        display.viewport = None

    # Find markers
    found_markers = artoolkit.detect(image, confidence_threshold=0.5,
                                     debug=False, debug_image=debug_image)

    # Throw out unknown markers
    markers = [marker for marker in found_markers if marker.id in displays]
    if not markers:
        return 0
    if len(markers) < len(found_markers):
        # TODO: get this info to node.js
        #logging.warn("Found unknown markers in jumbotron %s", jumbotron.name)
        pass

    # The method used here has problems when combining very small
    # displays with very large displays. The small displays will seem
    # to be very far away, so any vertical or horizontal separation
    # between them and the larger display will be amplified. An
    # alternative might be to use the screen coordinates of each
    # marker. Needs more thought if this becomes an issue.

    # Get camera xform
    cam_xform = _get_camera_xform(image)

    # Align markers as best we can
    _align_markers(markers)

    # Find best fitting plane
    plane_xform = _find_best_change_basis_xform(markers)

    # Project each marker to x-y plane and stretch to display size
    coords = []
    for marker in markers:
        idx = marker.id

        # Rotate from the best-fitting plane to the x-y plane.
        rot_vertices = [plane_xform.transform(vertex)
                        for vertex in marker.world_vertices]

        # Then rotate about the marker's center to the x-y plane
        center = plane_xform.transform(marker.world_center)
        normal = plane_xform.transform_vector(marker.world_normal).normalize()
        up     = plane_xform.transform_vector(marker.world_up).normalize()
        xform = _get_change_basis_xform(center, up, normal)
        xy_vertices = [xform.transform(vertex) for vertex in rot_vertices]

        # Use the camera transform to project to the screen
        screen_vertices = [cam_xform.transform(vertex)
                           for vertex in xy_vertices]

        # Finally, stretch to the display corners
        coord = _find_display_coords(marker, screen_vertices, displays[idx])
        coords.append(coord)

        if debug:
            _debug(Display=marker.id,
                   world=marker.world_vertices, plane=rot_vertices,
                   xyplane=xy_vertices, cam=screen_vertices, bbox=coord)
        if debug_image:
            _debug_image(draw, coords[idx], xform, plane_xform, cam_xform)

    # Find the bounding box of all the markers
    allminv = Vec3.min(*(coord[0] for coord in coords))
    allmaxv = Vec3.max(*(coord[1] for coord in coords))
    allsize = allmaxv - allminv
    if debug_image:
        _draw_rectangle(draw, allminv, allmaxv, "white", width=1)
    jumbotron.aspectRatio = allsize.x / allsize.y

    # Normalize viewports and set in displays
    for marker, coord in zip(markers, coords):
        idx = marker.id
        minv = (coord[0] - allminv)
        maxv = (coord[1] - allminv)
        size = maxv - minv

        displays[idx].viewport = dict(x=minv.x / allsize.x,
                                      y=minv.y / allsize.y,
                                      width=size.x / allsize.x,
                                      height=size.y / allsize.y,
                                      rotation=marker.world_direction)

    if debug:
        logging.debug("Final displays:")
        for marker, coord in zip(markers, coords):
            logging.debug("display {0} {1}".format(marker.id, coord))

    if debug_image:
        image.save("calibrate_out.jpg")

    return len(markers)

# ----------------------------------------------------------------------

def main(argv):
    """Perform calibration from the command line. Doesn't actually
    change the database, just prints lots of debugging info.
    Usage: python calibrate.py [db_file] <jumbotron_name> <image_name>"""

    import json

    class attrdict(dict):
        def __init__(self, *args, **kwargs):
            dict.__init__(self, *args, **kwargs)
            self.__dict__ = self

    # parse jumbotron info argv
    image_name = argv[1]
    jumbotron = attrdict(json.loads(argv[2]))

    # Convert displays to a dict indexed by display.idx
    displays = {}
    for display in jumbotron.displays:
        displays[display['idx']] = attrdict(display)
    jumbotron.displays = displays

    # Open image, rotate and convert if necessary, based on exif tags and mode
    try:
        image = Image.open(image_name)
    except IOError as ioe:
        print(str(ioe), file=sys.stderr)
        return -1
    image = _reorient_image(image)
    if image.mode != 'RGB' or image.mode != 'RGBA':
        image = image.convert('RGB')

    # Calibrate
    _calibrate(jumbotron, jumbotron.displays, image, debug=False, debug_image=False)

    # Send to stdout (to node.js)
    print(json.dumps(jumbotron), file=sys.stdout)

    # Save
    #image.save("calibrate_out.jpg")
    return 0

if __name__ == "__main__":
    import sys
    sys.exit(main(sys.argv))


import os
from flask import Flask, request, redirect, render_template
from lib.database_connection import get_flask_database_connection
from lib.album_repository import AlbumRepository
from lib.album import Album
from lib.artist_repository import ArtistRepository
from lib.artist import Artist
# Create a new Flask app
app = Flask(__name__)

# == Your Routes Here ==

#   open http://localhost:5000/albums
@app.route('/albums') # --> Defined a route (when I visit /albums URL, the below function will be executed)
def get_albums():
    connection = get_flask_database_connection(app)
    repo = AlbumRepository(connection) # --> Instance of AlbumRepository class, with database connection passed as argument
    albums = repo.all() # --> call all() method to retrieve all albums in the database
    return render_template("albums/index.html", albums=albums)
    # Returns the rendered template and passes the albums variable to the template

@app.route('/albums/<id>')
def get_album_id(id):
    connection = get_flask_database_connection(app)
    repo = AlbumRepository(connection)
    album = repo.find(id)
    return render_template("albums/show.html", album=album)

@app.route('/artists/<id>')
def get_artist_id(id):
    connection = get_flask_database_connection(app)
    repo = ArtistRepository(connection)
    artist = repo.find(id)
    return render_template("albums/show_artists.html", artist=artist)

@app.route('/artists')
def get_artists():
    connection = get_flask_database_connection(app)
    repo = ArtistRepository(connection)
    artists = repo.all()
    return render_template("albums/artists_index.html", artists = artists)

@app.route('/albums/new', methods=['GET'])
def get_new_albums():
    return render_template("albums/new.html")

@app.route ('/albums', methods=['POST'])
def create_album():
    connection = get_flask_database_connection(app)
    repository = AlbumRepository(connection)
    title = request.form['title']
    release_year = request.form['release_year']
    album = Album(None, title, release_year, 1)
    repository.create(album)
    return redirect(f"/albums/{album.id}")

# == Example Code Below ==

# GET /emoji
# Returns a smiley face in HTML
# Try it:
#   ; open http://localhost:5000/emoji
@app.route('/emoji', methods=['GET'])
def get_emoji():
    # We use `render_template` to send the user the file `emoji.html`
    # But first, it gets processed to look for placeholders like {{ emoji }}
    # These placeholders are replaced with the values we pass in as arguments
    return render_template('emoji.html', emoji=':)')

# This imports some more example routes for you to see how they work
# You can delete these lines if you don't need them.
from example_routes import apply_example_routes
apply_example_routes(app)

# == End Example Code ==

# These lines start the server if you run this file directly
# They also start the server configured to use the test database
# if started in test mode.
if __name__ == '__main__':
    app.run(debug=True, port=int(os.environ.get('PORT', 5000)))


from django.contrib.auth import login, logout
from django.contrib.auth.decorators import login_required
from django.core.exceptions import NON_FIELD_ERRORS
from django.http import HttpResponseRedirect, Http404
from django.shortcuts import render, redirect

from users.forms import RegistrationForm, LoginForm, SettingsForm
from users.models import User

def register(request):
    if request.method == 'GET':
        form = RegistrationForm()
        return render(request, 'users/register.html', context={"form": form})
    if request.method == 'POST':
        form = RegistrationForm(request.POST)
        if form.is_valid():
            form.save()
            user = form.get_user(request)
            user.send_verification_email()
            if user:
                login(request, user)
                return render(request, 'users/register_success.html')
            else:
                return render(request, 'users/register.html', context={"form": form})
        else:
            return render(request, 'users/register.html', context={"form": form})

def verify(request):
    user = request.user
    if user.is_email_verified:
        return render(request, 'users/email_verified.html')
    data = request.GET
    token = data.get('token')
    if not token:
        user.send_verification_email()
        return render(request, 'users/email_verified.html')
    else:
        if user.is_token_correct(token):
            user.verify_email()
            return render(request, 'users/email_verified.html')
        else:
            return Http404("Not Found")

def login_user(request):
    if request.method == 'GET':
        form = LoginForm()
        return render(request, 'users/login.html', context={'form': form})
    elif request.method == 'POST':
        form = LoginForm(request.POST)
        if form.is_valid():
            user = form.get_user(request)
            if user:
                login(request, user)
                return redirect('/lots/page/1')
            else:
                form.errors[NON_FIELD_ERRORS] = 'Cannot perform login with this credentials'
                return render(request, 'users/login.html', context={'form': form})
        else:
            return render(request, 'users/login.html', context={'form': form})

@login_required
def logout_user(request):
    logout(request)
    return HttpResponseRedirect('')

@login_required
def settings(request):
    user = request.user
    if request.method == 'GET':
        form = SettingsForm(instance=user)
        return render(
            request,
            'users/settings.html',
            context={
                'form': form,
                'showed_user': user,
            })
    elif request.method == 'POST':
        user = request.user
        form = SettingsForm(request.POST, request.FILES, instance=user)
        form.set_user(user)
        if form.is_valid():
            form.image = request.FILES.get('image')
            form.save()
            return redirect(f'/accounts/account/{user.username}')
        else:
            return render(
                request,
                'users/settings.html',
                context={
                    'form': form,
                    'showed_user': user,
                })

@login_required
def show_account(request, username):
    user = User.objects.get(username=username)
    if request.method == 'GET':
        return render(
            request,
            'users/account.html',
            context={
                'showed_user': user
            }
        )


from forex_python.converter import CurrencyRates
import requests

def convert_brl_btc(amount):
    c = CurrencyRates()

    bitcoin_api_url = 'https://api.coinbase.com/v2/prices/spot?currency=USD'
    response = requests.get(bitcoin_api_url)
    response_json = response.json()

    usd_blr = c.get_rate('USD', 'BRL')
    btc_usd = float(response_json['data']['amount'])
    btc_brl = ((amount - 20) / (usd_blr * btc_usd))

    return round(btc_brl, 5)

if __name__ == '__main__':
    print(convert_brl_btc(1000))


from odoo import models, fields, api

from odoo.exceptions import UserError

import logging
_logger = logging.getLogger(__name__)

class LinkPickingPurchase(models.TransientModel):
	_name = 'link.picking.purchase'

	def _default_picking(self):
		return self.env['stock.picking'].browse(self._context.get('active_id'))

	picking_id = fields.Many2one('stock.picking', string='Picking', default=_default_picking)
	purchase_ids = fields.Many2many('purchase.order', string='Purchase')

	# @api.multi
	def apply(self):
		# self.purchase_id.invoice_ids |= self.invoice_ids
		# return {}

		if not self.purchase_ids:
			raise UserError("You must select at least one purchase to link to this purchase order.")

		for purchase in self.purchase_ids:

			if purchase.partner_id != self.picking_id.partner_id:
				raise UserError("Picking and purchase vendor must be the same.")

			for purchase_lines in purchase.order_line:
				for move_lines in self.picking_id.move_lines:
					# Check if line items match
					if purchase_lines.product_id == move_lines.product_id and purchase_lines.product_uom == move_lines.product_uom:
						move_lines.write({'purchase_line_id': purchase_lines.id})


from pymongo import MongoClient
from pprint import pprint
from bson.json_util import loads
import json
import datetime

client = MongoClient()
db = client.pegasus
teachers = db.teachers
classes = db.classes
counters = db.counters

def create_teacher(name):
    teacher_ID = counters.find_one().get("teachers") # Get most recent teacher ID
    increment_counter = { "$set": {"teachers": teacher_ID + 1 }} # command to increment counter
    counters.update_many({}, increment_counter) # incrementing the counter
    new_teacher = {"name": name,
                   "ID": teacher_ID,
                   "classes": []}
    new_teacher = teachers.insert_one(new_teacher) # inserting the new teacher into collection
    return new_teacher # return _id of new teacher document in the collection

def create_class(teacher_ID, class_name, class_time, class_link):
    class_ID = counters.find_one().get("classes") # get most recent class ID
    increment_counter = { "$set": {"classes": class_ID + 1 }} # command to increment counter
    counters.update_many({}, increment_counter) # incrementing the counter

    new_class = {"name": class_name,
                 "time": class_time,
                 "link": class_link,
                 "ID"  : class_ID,
                 "teacher_ID": teacher_ID,
                 "teacher_name": db.teachers.find_one({"ID": teacher_ID})["name"]}
    new_class = classes.insert_one(new_class) # inserting new class into collection

    this_teacher_query = {"ID": teacher_ID} # query to find the teacher of this class
    this_teacher = db.teachers.find_one(this_teacher_query)

    this_teacher_classes = this_teacher["classes"] # variable to store this teachers list of classes
    this_teacher_classes.append(class_ID) # adding this class ID to this teacher's document
    class_to_append = {"$set": {"classes": this_teacher_classes}} # command to update this teachers class list
    teachers.update_one(this_teacher_query, class_to_append) # updating the teachers class list.

    return new_class


from os import path, getcwd, chdir, pardir, walk, mkdir, sep, listdir
from functools import reduce
import xml.dom.minidom as minidom
import json
import fnmatch

def findBlockId(xml_file, searchId):
    """Parsen der Database nach bestimmten Informationen"""

    # Definition WorkingDirectory = Current Directory
    workdir = getcwd()

    ## Path to  data.xml
    pathToData = path.join(workdir, 'DATABASE', xml_file)

    ## Parsing of data.xml -> DOM
    dataXml = minidom.parse(pathToData)

    ## extraction of all Blocks out of the DOM
    allBlocksInXml = dataXml.getElementsByTagName("omm:block")

    ## searching in all Blocks
    for block in allBlocksInXml:

        ## searching for the id of an block
        blockId = block.getAttribute("omm:id")

        ## Comparison of the BlockId with the searched Id
        if blockId == searchId:

            ## getting the Infos into variables
            blockLink = block.getElementsByTagName("omm:link"
            )[0].firstChild.data
            blockTitle = block.getElementsByTagName("omm:title"
            )[0].firstChild.data

            ## even if the tag is further down the DOM - just call it by name
            blockCreator = block.getElementsByTagName("omm:creator"
            )[0].firstChild.data
            blockCreationDate = block.getElementsByTagName("omm:date"
            )[0].firstChild.data
            ## Payload couild be everthing - give the type of payload
            blockPayloadType = str(type(block.getElementsByTagName("omm:payload"
            )[0].firstChild.data))
            blockPayload = block.getElementsByTagName("omm:payload"
            )[0].firstChild.data

        ## Putting the blockinfo into a dict.
        response = {"blockLink": blockLink,
                    "blockTitle": blockTitle,
                    "blockCreator": blockCreator,
                    "blockCreationDate": blockCreationDate,
                    "blockPayloadType": blockPayloadType,
                    "blockPayload": blockPayload,
                    }

        return response

    raise ValueError("No Block Found")

def tagIterator_extern(xml_file, asset_name):
    """
    Slightly modified tagIterator for parsing external DATABASEs meanwhile
    an external DATABASE is not implemented.
    """

    curDir = getcwd()
    workdir = path.abspath(path.join(curDir, pardir))

    ## intitialisting
    data_dict = {}

    if xml_file == 'add_directory.xml':

        pathToXmlFile = path.join(workdir, 'DATABASE_EXTERN', 'DATABASE',
                                    'Adds', xml_file)

        root = minidom.parse(pathToXmlFile)

        ## getting of all "over"-Blocks
        addList = root.getElementsByTagName("add")

        for add in addList:
            addIdentity = add.getAttribute("id")
            addTitle = add.getElementsByTagName("title"
            )[0].firstChild.data
            addInfo = add.getElementsByTagName("info"
            )[0].firstChild.data
            addPort = add.getElementsByTagName("port"
            )[0].firstChild.data
            addTemplate = add.getElementsByTagName("template"
            )[0].firstChild.data
            addSocket = add.getElementsByTagName("socket"
            )[0].firstChild.data

            addData = {"identity": addIdentity,
                        "title": addTitle,
                        "info": addInfo,
                        "port": addPort,
                        "template": addTemplate,
                        "socket": addSocket
                        }

            ## zuordnen der gewonnenen daten zum speziellen add
            data_dict[addIdentity] = addData

    elif xml_file == 'data.xml':
        """
        Iterates through the data.xml of the external database.
        """
        try:
            pathToXmlFile = path.join(workdir, 'DATABASE_EXTERN', 'DATABASE',
                                        'Assets', asset_name, xml_file)
            root = minidom.parse(pathToXmlFile)
            ## getting of all "over"-Blocks
            blockList = root.getElementsByTagName("omm:block")

            for block in blockList:
                ## extract single tag-info from the "over"-block
                blockTitle = block.getElementsByTagName("omm:title"
                )[0].firstChild.data
                blockCreator = block.getElementsByTagName("omm:creator"
                )[0].firstChild.data
                blockCreationDate = block.getElementsByTagName("omm:date"
                )[0].firstChild.data
                blockLink = block.getElementsByTagName("omm:link"
                )[0].firstChild.data
                blockPayload = block.getElementsByTagName("omm:payload"
                )[0].firstChild.data

                blockData = {
                            "blockTitle": blockTitle,
                            "blockCreator": blockCreator,
                            "blockCreationDate": blockCreationDate,
                            "blockLink": blockLink,
                            "blockPayload": blockPayload,}

                ## Anhaegen des assetData an die asset_list - Dictionary
                data_dict[blockTitle] = blockData
        except:
            """In case the external data.xml is  not reachable
            """
            data_dict = None
    else:
        pass

    ## Because the Dict has be delivered to JavaScript, it should be
    ## konverted in JSON because Python and JS has different
    ## Dict-Definitions - in JSON it is safe
    response = json.dumps(data_dict)

    # print(response) # DEBUGGING
    return response

def get_asset_data(asset_name):
    """Collecting of all asset-data (data.xml, html_code, images)
    and insertign it into one dict which can be transmitted"""
    ## intitialisting of an empty dict for the data
    data_dict = {}

    ## getting the add - path
    curDir = getcwd()
    exAssetDir = path.join(curDir, 'DATABASE',
                                'Assets', asset_name)

    ## assign the data.xml
    data_dict["data_xml"] = get_asset_data_xml(asset_name)

    ## assign all html files from the templates forlder to the data_dict
    data_dict["templates_data"] = get_asset_templates(asset_name = asset_name,
                                                        asset_path = exAssetDir)
    return data_dict

def get_asset_templates(asset_name, asset_path):
    templates_data = {}
    try:
        assetTemplatesDir = path.join(asset_path, 'templates', '')
        ## getting all templates from the root and from the subfolders
        for root, dirs, files in walk(assetTemplatesDir):
            for name in files:
                ## open the file and write the content to the templates data dict
                with open(path.join(root, name), "r") as file:
                    ## the key for every filevalue is the filename
                    templates_data[name] = file.read()
    except:
        templates_data = None
    return templates_data

def get_asset_data_xml(asset_name):
    """A function for getting the content of the data.xml of an specific asset
    (<asset_name>) from the actually 'inactive' DATABASE_EXTERN
    """
    curDir = getcwd()
    workdir = path.abspath(path.join(curDir, pardir))
    ## intitialisting
    data_dict = {}
    exAssetDataPath = path.join(workdir, 'DATABASE_EXTERN', 'DATABASE',
                                'Assets', asset_name, 'data.xml')
    root = minidom.parse(exAssetDataPath)
    data = root.toprettyxml(indent = "", newl = "")
    return data

def get_add_data(add_name):
    """Collecting of all add-data (configuration-data, python_code, html_code)
    """
    ## intitialisting of an empty dict for the data
    data_dict = {}

    ## getting the add - path
    curDir = getcwd()
    workdir = path.abspath(path.join(curDir, pardir))
    exAddDir = path.join(workdir, 'DATABASE_EXTERN', 'DATABASE',
                                'Adds', add_name)
    ## assign the configuration-data from the add_directory
    ## to an configuration_data - key in the dict.
    data_dict["configuration_data"] = get_add_conf(add_name)
    # ## assign all files from the add forlder to the data_dict
    data_dict["add_data"] = get_add_dir(exAddDir)
    return data_dict

def get_add_conf(add_name):
    """A function for getting the configuration data of an specific add
    (<add_name>) from the actually 'inactive' DATABASE_EXTERN
    """
    curDir = getcwd()
    workdir = path.abspath(path.join(curDir, pardir))
    ## intitialisting
    data_dict = {}
    exAddDataPath = path.join(workdir, 'DATABASE_EXTERN', 'DATABASE',
                                'Adds', 'add_directory.xml')
    root = minidom.parse(exAddDataPath)
    ## a list of all adds
    addList = root.getElementsByTagName("add")
    ## going through all the adds
    for add in addList:
        ## if the required add is found, grab the data
        if add.getAttribute("id") == add_name:
            config_data = add.toprettyxml(indent = "", newl = "")
    return config_data

def get_add_dir(add_path):
    """Function for recursive inetating through the dir-tree, reading
    all files and putting the content into a nested dictionary.

    ATTENTION!!! Images cant be tranferred this way at the moment.
    """
    ## the add_data - dict shuld be intantiated just in the first iteration
    try:
        add_data
    except:
        add_data = {}
    for content in listdir(add_path):
        if not (content == '__pycache__' or \
                content == '.DS_Store' or \
                content == 'images'):
            ChildPath = path.join(add_path, content)
            if path.isdir(ChildPath):
                add_data[content] = get_add_dir(ChildPath)
            else:
                with open(ChildPath, "r") as file:
                    add_data[content] = file.read()
    return add_data


from typing import List

class Solution:
    def criticalConnections(self, n: int, connections: List[List[int]]) -> List[List[int]]:
        graph = [[] for _ in range(n)]
        low = [0 for _ in range(n)]
        visited = [False for _ in range(n)]
        for x, y in connections:
            graph[x].append(y)
            graph[y].append(x)

        self.answer = []

        def dfs(rank, cur, prev):
            low[cur] = rank
            visited[cur] = True
            for nxt in graph[cur]:
                if nxt == prev:
                    continue
                if not visited[nxt]:
                    dfs(rank + 1, nxt, cur)
                low[cur] = min(low[cur], low[nxt])
                if low[nxt] >= rank + 1:
                    self.answer.append([cur, nxt])

        dfs(1, 0, -1)
        return self.answer


import numpy as np
import cv2
import matplotlib.pyplot as plt

# Define a class to receive the characteristics of each line detection
class Line():
    def __init__(self):
        # was the line detected in the last iteration?
        self.detected = False
        # x values of the last n fits of the line
        self.recent_xfitted = []
        #average x values of the fitted line over the last n iterations
        self.bestx = None
        #polynomial coefficients averaged over the last n iterations
        self.best_fit = None
        #polynomial coefficients for the most recent fit
        self.current_fit = [np.array([False])]
        #radius of curvature of the line in some units
        self.radius_of_curvature = None
        #distance in meters of vehicle center from the line
        self.line_base_pos = None
        #difference in fit coefficients between last and new fits
        self.diffs = np.array([0,0,0], dtype='float')
        #x values for detected line pixels
        self.allx = None
        #y values for detected line pixels
        self.ally = None

def sliding_line_finding(binary_warped):
    left_lane  = Line()
    right_lane = Line()
    # Assuming you have created a warped binary image called "binary_warped"
    # Take a histogram of the bottom half of the image
    histogram = np.sum(binary_warped[np.int(binary_warped.shape[0]/2):,:], axis=0)

    # Create an output image to draw on and  visualize the result
    out_img = np.dstack((binary_warped, binary_warped, binary_warped))*255
    # Find the peak of the left and right halves of the histogram
    # These will be the starting point for the left and right lines
    midpoint = np.int(histogram.shape[0]/2)
    leftx_base = np.argmax(histogram[:midpoint])
    rightx_base = np.argmax(histogram[midpoint:]) + midpoint

    # Choose the number of sliding windows
    nwindows = 9
    # Set height of windows
    window_height = np.int(binary_warped.shape[0]/nwindows)
    # Identify the x and y positions of all nonzero pixels in the image
    nonzero = binary_warped.nonzero()
    nonzeroy = np.array(nonzero[0])
    nonzerox = np.array(nonzero[1])
    # Current positions to be updated for each window
    leftx_current = leftx_base
    rightx_current = rightx_base
    # Set the width of the windows +/- margin
    margin = 80
    # Set minimum number of pixels found to recenter window
    minpix = 50
    # Create empty lists to receive left and right lane pixel indices
    left_lane_inds = []
    right_lane_inds = []

    # Step through the windows one by one
    for window in range(nwindows):
        # Identify window boundaries in x and y (and right and left)
        win_y_low = binary_warped.shape[0] - (window+1)*window_height
        win_y_high = binary_warped.shape[0] - window*window_height
        win_xleft_low = leftx_current - margin
        win_xleft_high = leftx_current + margin
        win_xright_low = rightx_current - margin
        win_xright_high = rightx_current + margin
        # Draw the windows on the visualization image
        cv2.rectangle(out_img,(win_xleft_low,win_y_low),(win_xleft_high,win_y_high),(0,255,0), 3)
        cv2.rectangle(out_img,(win_xright_low,win_y_low),(win_xright_high,win_y_high),(0,255,0), 3)
        # Identify the nonzero pixels in x and y within the window
        good_left_inds = ((nonzeroy >= win_y_low) & (nonzeroy < win_y_high) & (nonzerox >= win_xleft_low) & (nonzerox < win_xleft_high)).nonzero()[0]
        good_right_inds = ((nonzeroy >= win_y_low) & (nonzeroy < win_y_high) & (nonzerox >= win_xright_low) & (nonzerox < win_xright_high)).nonzero()[0]
        # Append these indices to the lists
        left_lane_inds.append(good_left_inds)
        right_lane_inds.append(good_right_inds)
        # If you found > minpix pixels, recenter next window on their mean position
        if len(good_left_inds) > minpix:
            leftx_current = np.int(np.mean(nonzerox[good_left_inds]))
        if len(good_right_inds) > minpix:
            rightx_current = np.int(np.mean(nonzerox[good_right_inds]))

    # Concatenate the arrays of indices
    left_lane_inds = np.concatenate(left_lane_inds)
    right_lane_inds = np.concatenate(right_lane_inds)

    # Extract left and right line pixel positions
    leftx = nonzerox[left_lane_inds]
    lefty = nonzeroy[left_lane_inds]
    rightx = nonzerox[right_lane_inds]
    righty = nonzeroy[right_lane_inds]

    # Fit a second order polynomial to each
    left_fit = np.polyfit(lefty, leftx, 2)
    right_fit = np.polyfit(righty, rightx, 2)

    left_lane.allx = nonzerox[left_lane_inds]
    left_lane.ally = nonzeroy[left_lane_inds]
    right_lane.allx = nonzerox[right_lane_inds]
    right_lane.ally = nonzeroy[right_lane_inds]
    left_lane.current_fit = left_fit
    right_lane.current_fit = right_fit

    return left_lane,right_lane

def line_finding_after_sliding(binary_warped,left_lane,right_lane):

    nonzero = binary_warped.nonzero()
    nonzeroy = np.array(nonzero[0])
    nonzerox = np.array(nonzero[1])
    margin = 100

    left_fit = left_lane.current_fit
    right_fit = right_lane.current_fit

    left_lane_inds = ((nonzerox > (left_fit[0]*(nonzeroy**2) + left_fit[1]*nonzeroy + left_fit[2] - margin)) & (nonzerox < (left_fit[0]*(nonzeroy**2) + left_fit[1]*nonzeroy + left_fit[2] + margin)))
    right_lane_inds = ((nonzerox > (right_fit[0]*(nonzeroy**2) + right_fit[1]*nonzeroy + right_fit[2] - margin)) & (nonzerox < (right_fit[0]*(nonzeroy**2) + right_fit[1]*nonzeroy + right_fit[2] + margin)))

    # Again, extract left and right line pixel positions
    left_lane.allx = nonzerox[left_lane_inds]
    left_lane.ally = nonzeroy[left_lane_inds]
    right_lane.allx = nonzerox[right_lane_inds]
    right_lane.ally = nonzeroy[right_lane_inds]

    # Fit a second order polynomial to each
    left_fit = np.polyfit(left_lane.ally, left_lane.allx, 2)
    right_fit = np.polyfit(right_lane.ally, right_lane.allx, 2)
    # Generate x and y values for plotting
    ploty = np.linspace(0, binary_warped.shape[0]-1, binary_warped.shape[0] )
    left_fitx = left_fit[0]*ploty**2 + left_fit[1]*ploty + left_fit[2]
    right_fitx = right_fit[0]*ploty**2 + right_fit[1]*ploty + right_fit[2]
    left_lane.current_fit = left_fit
    right_lane.current_fit = right_fit
    return left_lane,right_lane

def draw(binary_warped, left_lane, right_lane, image, Minv):

    left_fit=left_lane.current_fit
    right_fit=right_lane.current_fit
    ploty = np.linspace(0, binary_warped.shape[0]-1, binary_warped.shape[0] )
    left_fitx = left_fit[0]*ploty**2 + left_fit[1]*ploty + left_fit[2]
    right_fitx = right_fit[0]*ploty**2 + right_fit[1]*ploty + right_fit[2]

    # Create an image to draw the lines on
    warp_zero = np.zeros_like(binary_warped).astype(np.uint8)
    color_warp = np.dstack((warp_zero, warp_zero, warp_zero))

    # Recast the x and y points into usable format for cv2.fillPoly()
    pts_left = np.array([np.transpose(np.vstack([left_fitx, ploty]))])
    pts_right = np.array([np.flipud(np.transpose(np.vstack([right_fitx, ploty])))])
    pts = np.hstack((pts_left, pts_right))

    # Draw the lane onto the warped blank image
    cv2.fillPoly(color_warp, np.int_([pts]), (0,255, 0))

    # Warp the blank back to original image space using inverse perspective matrix (Minv)
    newwarp = cv2.warpPerspective(color_warp, Minv, (image.shape[1], image.shape[0]))
    # Combine the result with the original image
    result = cv2.addWeighted(image, 1, newwarp, 0.3, 0)

    return result

def measure_curvature(binary_warped, left_lane, right_lane):

    left_fit=left_lane.current_fit
    right_fit=right_lane.current_fit
    ploty = np.linspace(0, binary_warped.shape[0]-1, binary_warped.shape[0] )
    y_eval = np.max(ploty)
    ym_per_pix = 30/720 # meters per pixel in y dimension
    xm_per_pix = 3.7/700 # meters per pixel in x dimension

    left_fitx = left_fit[0]*ploty**2 + left_fit[1]*ploty + left_fit[2]
    right_fitx = right_fit[0]*ploty**2 + right_fit[1]*ploty + right_fit[2]

    # Fit new polynomials to x,y in world space
    left_fit_cr = np.polyfit(ploty*ym_per_pix, left_fitx*xm_per_pix, 2)
    right_fit_cr = np.polyfit(ploty*ym_per_pix, right_fitx*xm_per_pix, 2)

    # Calculate the new radii of curvature
    left_curveradius = ((1 + (2*left_fit_cr[0]*y_eval*ym_per_pix + left_fit_cr[1])**2)**1.5) / np.absolute(2*left_fit_cr[0])
    right_curveradius = ((1 + (2*right_fit_cr[0]*y_eval*ym_per_pix + right_fit_cr[1])**2)**1.5) / np.absolute(2*right_fit_cr[0])

    return left_curveradius,right_curveradius

def compute_offset(binary_warped, left_lane, right_lane):

    left_fit=left_lane.current_fit
    right_fit=right_lane.current_fit

    left_fitx = left_fit[0]*binary_warped.shape[0]**2 + left_fit[1]*binary_warped.shape[0] + left_fit[2]
    right_fitx = right_fit[0]*binary_warped.shape[0]**2 + right_fit[1]*binary_warped.shape[0] + right_fit[2]
    xm_per_pix = 3.7/700

    #center of the image
    center_img = binary_warped.shape[1]/2

    #cam position respect to the center
    center_cam = (left_fitx+right_fitx)/2
    center_pos = (center_img-center_cam)*xm_per_pix

    return center_pos


#!/usr/bin/env python3
# Start by getting signal metrics
import logging
import logging.handlers
import os
import platform
import sys
import tailer
from parse import *
from tmo_monitor.gateway.model import GatewayModel
from tmo_monitor.configuration import Configuration
from tmo_monitor.gateway.arcadyan import CubeController
from tmo_monitor.gateway.nokia import TrashCanController
from tmo_monitor.status import ExitStatus

# __main__
if __name__ == "__main__":

  config = Configuration()
  if config.general['print_config']:
    config.print_config()
  # Set up logging for console
  root_logger = logging.getLogger()
  root_logger.setLevel(logging.DEBUG)
  formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s', '%Y/%m/%d %H:%M:%S')
  console_logger = logging.StreamHandler()
  console_logger.setFormatter(formatter)
  root_logger.addHandler(console_logger)
  if config.general['logfile']:
    file_logger = logging.FileHandler(config.general['logfile'])
    file_logger.setLevel(logging.INFO)
    file_logger.setFormatter(formatter)
    root_logger.addHandler(file_logger)
    logging.debug('Enabled file logging to {}'.format(config.general['logfile']))
  if config.general['syslog']:
    syslog_handler_opts = {}
    syslog_logging_details = ''
    if platform.system() != 'Windows':
      for syslog_socket in ['/dev/log', '/var/run/syslog']:
        if os.path.exists(syslog_socket):
          syslog_handler_opts['address'] = syslog_socket
          syslog_logging_details = ' via {}'.format(syslog_socket)
          break
    syslog_logger = logging.handlers.SysLogHandler(**syslog_handler_opts)
    syslog_formatter = logging.Formatter('[%(levelname)s] %(message)s')
    syslog_logger.setFormatter(syslog_formatter)
    syslog_logger.setLevel(logging.INFO)
    syslog_logger.ident = 'tmo-monitor[{}]: '.format(os.getpid())
    root_logger.addHandler(syslog_logger)
    logging.debug('Enabled syslog logging{}'.format(syslog_logging_details))
  if config.reboot_now:
    logging.info('Immediate reboot requested.')
    reboot_requested = True
  else:
    reboot_requested = False

  log_all = False
  connection = dict([('4G', ''), ('5G', ''), ('enbid', ''), ('ping', '')])
  if config.general['log_all'] or config.general['log_delta']:
    log_all = True

  if config.model == GatewayModel.NOKIA:
    gw_control = TrashCanController(config.login['username'], config.login['password'])
  # The Arcadyan and Sagecom gateways appear to conform to the same API
  elif config.model in [GatewayModel.ARCADYAN, GatewayModel.SAGECOM]:
    gw_control = CubeController(config.login['username'], config.login['password'])
  else:
    raise Exception('Unsupported Gateway Model')

  if not reboot_requested:

    # Check for eNB ID if an eNB ID was supplied & reboot on eNB ID wasn't False in the .env
    if config.connection['enbid'] and config.reboot['enbid'] or log_all:
      site_meta = gw_control.get_site_info()
      connection['enbid'] = site_meta['eNBID']
      if (site_meta['eNBID'] != config.connection['enbid']) and config.reboot['enbid']:
        logging.info('Not on eNB ID ' + str(config.connection['enbid']) + ', on ' + str(site_meta['eNBID']) + '.')
        reboot_requested = True
      else:
        print('eNB ID check passed, on ' + str(site_meta['eNBID']) + '.')

    # Check for preferred bands regardless of reboot on band mismatch
    if config.reboot['4G_band'] or config.reboot['5G_band'] or log_all:
      signal_info = gw_control.get_signal_info()

      if config.connection['primary_band'] or log_all:
        primary_band = config.connection['primary_band']
        band_4g = signal_info['4G']
        connection['4G'] = band_4g
        if (primary_band and band_4g not in primary_band) and config.reboot['4G_band']:
          logging.info('Not on ' + ('one of ' if len(primary_band) > 1 else '') + ', '.join(primary_band) + '.')
          if config.reboot['4G_band']:
            reboot_requested = True
        else:
          print('Camping on ' + band_4g + '.')

      # 5G has a default value set (n41)
      secondary_band = config.connection['secondary_band']
      band_5g = signal_info['5G']
      connection['5G'] = band_5g
      if band_5g not in secondary_band and config.reboot['5G_band']:
        logging.info('Not on ' + ('one of ' if len(secondary_band) > 1 else '') + ', '.join(secondary_band) + '.')
        if config.reboot['5G_band']:
          reboot_requested = True
      else:
        print('Camping on ' + band_5g + '.')

    # Check for successful ping
    ping_ms = gw_control.ping(config.ping['ping_host'], config.ping['ping_count'], config.ping['ping_interval'], config.connectivity['interface'], config.ping['ping_6'])
    if log_all:
      connection['ping'] = ping_ms
    if ping_ms < 0 and config.connectivity['connectivity_check'] == 'ping':
      logging.error('Could not ping ' + config.ping['ping_host'] + '.')
      if config.reboot['ping']:
        reboot_requested = True

    # Check for successful http check
    if config.connectivity['connectivity_check'] == 'http':
      status_code = gw_control.http_check(config.http['http_target'])
      if status_code != config.http['status_code']:
        logging.error('Status code failed check for ' + config.http['http_target'] + ' - received status code ' + str(status_code))
        if config.reboot['http']:
          reboot_requested = True

  # Reboot if needed
  reboot_performed = False
  if (reboot_requested or log_all):
    connection['uptime'] = gw_control.get_uptime()
  if reboot_requested:
    if config.skip_reboot:
      logging.info('Not rebooting.')
    else:
      logging.info('Reboot requested.')

      if config.reboot_now or (connection['uptime'] >= config.reboot['uptime']):
        logging.info('Rebooting.')
        gw_control.reboot()
        reboot_performed = True
      else:
        logging.info('Uptime threshold not met for reboot.')
  else:
    print('No reboot necessary.')

  if log_all and config.general['log_delta'] and config.general['logfile']:
    # Tail the last 10 lines of the file (to account for logged errors) and reverse to detect the newest logline
    logline = tailer.tail(open(config.general['logfile']), 10)
    logline.reverse()
    for line in logline:
        if line.__contains__('|'):
          print(line)
          data = parse("{0} [INFO] 4G: {1} | 5G: {2} | eNB ID: {3} | Avg Ping: {4} ms | Uptime: {5} sec", line)
          if data[1] != connection['4G']:
            logging.info("4G connection is {0}, was {1}".format(connection['4G'], data[1]))
            config.general['log_all'] = True
          if data[2] != connection['5G']:
            logging.info("5G connection is {0}, was {1}".format(connection['5G'], data[2]))
            config.general['log_all'] = True
          if int(data[3]) != connection['enbid']:
            logging.info("eNB ID is {0}, was {1}".format(connection['enbid'], data[3]))
            config.general['log_all'] = True
          if int(data[4]) * 3 < connection['ping']:
            logging.info("Ping ms {0}, over 3x {1} ms".format(connection['ping'], data[4]))
            config.general['log_all'] = True
          if int(data[5]) > connection['uptime']:
            logging.info("Uptime {0} sec, less than {1} sec".format(connection['uptime'], data[5]))
            config.general['log_all'] = True
          break

  if log_all and config.general['log_all']:
    if config.general['logfile'] == '' and not config.general['syslog']:
      logging.error("Logging requested but file or syslog not specified")
    else:
      msg = "4G: {0} | 5G: {1} | eNB ID: {2} | Avg Ping: {3} ms | Uptime: {4} sec".format(
        connection['4G'], connection['5G'], connection['enbid'], connection['ping'], connection['uptime'])
      logging.info(msg)

  if reboot_performed:
    sys.exit(ExitStatus.REBOOT_PERFORMED.value)


import cv2
import librosa
import numpy as np

from moviepy.video.io.VideoFileClip import VideoFileClip
from moviepy.editor import AudioFileClip

from PIL import Image

global_save_path = '.\\Frame\\'

def saveFrames(pathToVideo, frame_number, num_frames_to_save, save_path=global_save_path):

    cap = cv2.VideoCapture(pathToVideo)

    start_frame = max(0, frame_number)
    cap.set(cv2.CAP_PROP_POS_FRAMES, start_frame)

    # достаём аудиодорожку с видеофайла
    video_clip = VideoFileClip(pathToVideo)
    audio_file = video_clip.audio
    audio_file.write_audiofile('audio.wav')

    # определяем количество FPS и соответственно длительность одного кадра
    fps = int(cap.get(cv2.CAP_PROP_FPS))
    frame_duration = 1.0 / fps

    frame_count = frame_number

    temporary_audio_path = 'test.wav'

    noise_value = []

    while frame_count < frame_number + num_frames_to_save:

        ret, frame = cap.read()

        if not ret:
            break
        #

        frame_start_time = frame_count * frame_duration
        frame_end_time = (frame_count + 1) * frame_duration

        audio_clip = AudioFileClip('audio.wav').subclip(frame_start_time, frame_end_time)
        #audio_clip.write_audiofile(temporary_audio_path)
        audio_array = audio_clip.to_soundarray()

        # с использованием библиотеки librosa
        y = audio_array.mean(axis=1)
        rms = librosa.feature.rms(y=y)
        rms_mean = np.mean(rms)
        rms_mean_round = np.round(rms_mean*10, 3)
        noise_value.append(rms_mean_round)

        img = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

        scaled = cv2.resize(img, (640,480))

        gray_frame = Image.fromarray(scaled)
        gray_frame.save("{}{}.jpg".format(save_path, frame_count))

        frame_count += 1

    #
    noise_value_length = len(noise_value)
    with open(f'.\\Frame\\resultest.txt', 'w') as file:
        for index in range(noise_value_length):
            number_for_name = frame_number + index
            file.write('Frame - {}: {}\n'.format(number_for_name, noise_value[index]))

global_save_path = '.\\Frame\\frame'

pathToVideo = "123.mp4"
pathToAudio = 'audio.wav'

saveFrames(pathToVideo, 5820, 10)