import os
import sys
import itertools
import sgf
import random
import numpy as np
import math
import tensorflow as tf
import matplotlib.pyplot as plt
import functools, operator

def givemelocation(values):
    indes = {'a':0,'b':1,'c':2,'d':3,'e':4,'f':5,'g':6,'h':7,'i':8,'j':9,'k':10,'l':11,'m':12,'n':13,'o':14,'p':15,'q':16,'r':17,'s':18}
    output=[]
    if len(values)==2 and type(values) == str:
        for i in values:
            output.append(indes[i])
        print(output)
    else:
        print("Invalid input value")
    return output

def product(numbers):
    return functools.reduce(operator.mul, numbers)

def init(shape):
    number_inputs_added = product(shape[:-1])
    stddev = 1 / math.sqrt(number_inputs_added)
    return stddev

def fanyi(number):
    a = int(number%19)
    b = int(np.floor(number/19))
    zidian = ["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s"]
    c = zidian[a]
    d = zidian[b]
    output = c + d
    return output

def generator(tmp,loc):
    tmp=-tmp
    tmp[:,:,:,-5]=tmp[:,:,:,-4]
    tmp[:,:,:,-4]=tmp[:,:,:,-3]
    tmp[:,:,:,-3]=tmp[:,:,:,-2]
    tmp[:,:,:,-2]=tmp[:,:,:,-1]
    tmp[:,:,:,-1][:,loc] = 1
    return tmp

stddev1 = init([5,5,5,40])
stddev2 = init([3,3,40,40])
stddev3 = init([1,1,40,1])

X = tf.placeholder(tf.float32,shape = [None,19,19,5],name = 'Datasetinput')
Y = tf.placeholder(tf.float32,shape = [None,361],name='Output')

W1 = tf.Variable(tf.truncated_normal([5, 5, 5, 40],stddev = stddev1),name = "W1")
W2 = tf.Variable(tf.truncated_normal([3, 3, 40, 40],stddev = stddev2),name = "W2")
W3 = tf.Variable(tf.truncated_normal([3, 3, 40, 40],stddev = stddev2),name = "W3")
W4 = tf.Variable(tf.truncated_normal([3, 3, 40, 40],stddev = stddev2),name = "W4")
W5 = tf.Variable(tf.truncated_normal([3, 3, 40, 40],stddev = stddev2),name = "W5")
W6 = tf.Variable(tf.truncated_normal([3, 3, 40, 40],stddev = stddev2),name = "W6")
W7 = tf.Variable(tf.truncated_normal([3, 3, 40, 40],stddev = stddev2),name = "W7")
W8 = tf.Variable(tf.truncated_normal([3, 3, 40, 40],stddev = stddev2),name = "W8")
W9 = tf.Variable(tf.truncated_normal([3, 3, 40, 40],stddev = stddev2),name = "W9")
W10 = tf.Variable(tf.truncated_normal([3, 3, 40, 40],stddev = stddev2),name = "W10")
W11 = tf.Variable(tf.truncated_normal([3, 3, 40, 40],stddev = stddev2),name = "W11")
W12 = tf.Variable(tf.truncated_normal([1, 1, 40, 1],stddev = stddev3),name = "W12")
bias = tf.Variable(tf.constant(0, shape=[361], dtype=tf.float32), name="bias")

Z1 = tf.nn.conv2d(X,W1,strides = [1,1,1,1],padding='SAME')
A1 = tf.nn.relu(Z1)
Z2 = tf.nn.conv2d(A1,W2,strides = [1,1,1,1],padding='SAME')
A2 = tf.nn.relu(Z2)
Z3 = tf.nn.conv2d(A2,W3,strides = [1,1,1,1],padding='SAME')
A3 = tf.nn.relu(Z3)
Z4 = tf.nn.conv2d(A3,W4,strides = [1,1,1,1],padding='SAME')
A4 = tf.nn.relu(Z4)
Z5 = tf.nn.conv2d(A4,W5,strides = [1,1,1,1],padding='SAME')
A5 = tf.nn.relu(Z5)
Z6 = tf.nn.conv2d(A5,W6,strides = [1,1,1,1],padding='SAME')
A6 = tf.nn.relu(Z6)
Z7 = tf.nn.conv2d(A6,W7,strides = [1,1,1,1],padding='SAME')
A7 = tf.nn.relu(Z7)
Z8 = tf.nn.conv2d(A7,W8,strides = [1,1,1,1],padding='SAME')
A8 = tf.nn.relu(Z8)
Z9 = tf.nn.conv2d(A8,W9,strides = [1,1,1,1],padding='SAME')
A9 = tf.nn.relu(Z9)
Z10 = tf.nn.conv2d(A9,W10,strides = [1,1,1,1],padding='SAME')
A10 = tf.nn.relu(Z10)
Z11 = tf.nn.conv2d(A10,W11,strides = [1,1,1,1],padding='SAME')
A11 = tf.nn.relu(Z11)
Z12 = tf.nn.conv2d(A11,W12,strides = [1,1,1,1],padding='SAME')
A12 = tf.contrib.layers.flatten(Z12)
output = tf.nn.bias_add(A12,bias,name=None)
tmp = tf.nn.softmax_cross_entropy_with_logits(labels=Y,logits=output)
prediction = tf.nn.softmax(output)
cost = tf.reduce_mean(tmp)

tmp = tf.argmax(prediction,output_type=tf.int64)
tmp1 = tf.argmax(Y,output_type=tf.int64)

correct_prediction = tf.equal(tmp, tmp1)
accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))

saver = tf.train.Saver()

input_loc = []
X_tmp = np.zeros([1,19,19,5])
with tf.Session() as sess:
    while not input_loc == "quit":
        if input_loc == "quit":
            break
        input_loc = input("if you want to quit, type in 'quit', or Input your stone's move:")
        X_loc = givemelocation(input_loc)
        X_tmp = generator(X_tmp,X_loc)
        saver.restore(sess,"model.ckpt")
        output = sess.run(tmp, feed_dict={X:X_tmp})
        output1 = sess.run(prediction, feed_dict={X:X_tmp})
        output2 = np.argmax(output1)
        print(fanyi(output2))


from __future__ import print_function

import logging

import pytest
import requests
import voeventparse as vp

import voeventdb.remote as vr
import voeventdb.remote.apiv1 as apiv1
import voeventdb.remote.apiv1.definitions
import voeventdb.server.restapi.inspection_utils as iu
from voeventdb.remote.helpers import Synopsis
from voeventdb.server.restapi.v1.definitions import _list_class_vars
from voeventdb.server.tests.resources import swift_bat_grb_655721

logger = logging.getLogger(__name__)

def test_endpoint_urls_in_sync():
    v1_ep_upstream = iu.apiv1_endpoints().values()
    v1_ep_remote = _list_class_vars(vr.apiv1.definitions.Endpoints).values()
    assert  sorted(v1_ep_upstream) == sorted(v1_ep_remote)

@pytest.mark.usefixtures('mock_requests')
class TestFunctionCalls():
    def test_ordering_param(self, simple_populated_db):
        """Just a sanity check that ordering syntax is correct"""
        dbinf = simple_populated_db
        all_ivorns = apiv1.list_ivorn(order=apiv1.OrderValues.id)
        all_ivorns_rev_order = apiv1.list_ivorn(order=apiv1.OrderValues.id_desc)
        assert all_ivorns == dbinf.inserted_ivorns
        assert all_ivorns_rev_order == all_ivorns[::-1]

    def test_list_pagination(self, simple_populated_db,
                            reset_globals_to_defaults):
        """
        Sanity check that pagination params passed correctly to wrapper func.

        (NB wrapper tested independently elsewhere).
        """
        vr.default_list_n_max = 30
        vr.default_pagesize = 5
        dbinf = simple_populated_db
        count = apiv1.count()
        assert count > vr.default_list_n_max
        ivorns = apiv1.list_ivorn()
        assert len(ivorns) == vr.default_list_n_max
        #This time we override the max:
        ivorns = apiv1.list_ivorn(n_max=0)
        assert len(ivorns) == dbinf.n_inserts

    def test_xml_retrieval(self, simple_populated_db):
        dbinf = simple_populated_db
        all_ivorns = apiv1.list_ivorn(order=apiv1.OrderValues.id)
        xml = apiv1.packet_xml(all_ivorns[-1])
        assert xml == vp.dumps(dbinf.insert_packets[-1])

        #Now try a non-existent ivorn:
        with pytest.raises(requests.HTTPError):
            apiv1.packet_xml('ivo://foo/bar')

    def test_synopsis(self, simple_populated_db):
        """Check synopsis wrapper function, helper class."""
        dbinf = simple_populated_db

        # Try an packet with references
        ivorns_w_refs = apiv1.list_ivorn(
            filters={apiv1.FilterKeys.ref_any: True},
            order = apiv1.OrderValues.id,
        )
        s = Synopsis(apiv1.packet_synopsis(ivorns_w_refs[0]))
        assert s.references
        assert len(s.sky_events) == 0

        #Now try a non-existent ivorn:
        with pytest.raises(requests.HTTPError):
            apiv1.packet_synopsis('ivo://foo/bar')

        # Now try a packet with co-ords
        sb_ivorn = swift_bat_grb_655721.attrib['ivorn']
        assert sb_ivorn in dbinf.inserted_ivorns
        s = Synopsis(apiv1.packet_synopsis(sb_ivorn))
        assert s.coords
        assert len(s.coords) == 1
        skyevent = s.sky_events[0]
        c_pkt = vp.get_event_position(swift_bat_grb_655721)
        assert skyevent.position.ra.value == c_pkt.ra
        assert skyevent.position.dec.value == c_pkt.dec
        assert skyevent.position_error.value == c_pkt.err


from django.conf import settings
from django.core.urlresolvers import reverse
from django.shortcuts import render
from django.template.loader import render_to_string
from django.views.generic import ListView, DetailView, View
from django.views.generic.edit import FormView

from .forms import SuggestGuideForm
from .models import Guide
from source.utils.email import send_multipart_email

class GuideList(ListView):
    model = Guide
    template_name = "guides/_v2/guide_list.html"

    def dispatch(self, *args, **kwargs):
        self.render_json = kwargs.get('render_json', False)
        return super(GuideList, self).dispatch(*args, **kwargs)

    def get_queryset(self):
        queryset = Guide.live_objects.all()

        return queryset

    def get_context_data(self, **kwargs):
        context = super(GuideList, self).get_context_data(**kwargs)
        context['active_nav'] = 'Guides'
        context['rss_link'] = reverse('guide_list_feed')

        return context

class GuideDetail(DetailView):
    model = Guide
    template_name = "guides/_v2/guide_detail.html"

    def get_queryset(self):
        if self.request.user.is_staff:
            # allow preview for logged-in editors
            queryset = Guide.objects.prefetch_related('guidearticle_set')
        else:
            queryset = Guide.live_objects.prefetch_related('guidearticle_set')

        return queryset

class GuideSuggestGuide(FormView):
    '''
    Readers can suggest a Guide to add to the Guide list, generating
    an email to the editorial team.
    '''
    template_name = "guides/_v2/suggest_guide.html"
    form_class = SuggestGuideForm

    def form_valid(self, form, **kwargs):
        context = self.get_context_data(**kwargs)

        data=form.cleaned_data

        email_context = {
            'data': data
        }

        # render text and html versions of email body
        # both plain txt for now
        text_content = render_to_string(
            'guides/_v2/emails/suggest_guide.txt',
            email_context,
        )
        html_content = render_to_string(
            'guides/_v2/emails/suggest_guide.html',
            email_context
        )

        send_multipart_email(
            subject = 'Source: Guide suggestion from a reader',
            from_email = settings.DEFAULT_FROM_EMAIL,
            to = settings.EDITORIAL_EMAIL,
            text_content = text_content,
            html_content = html_content
        )

        context.update({'success': 'True'})

        return render(self.request, self.template_name, context)


from flask import Blueprint, request, jsonify
from google.cloud import datastore
import json
import verificationHelper
import constants

client = datastore.Client()

bp = Blueprint('game', __name__, url_prefix='/games')

# create and read all routes from url/games
@bp.route('/', methods=['POST','GET'])
def games_get_post():
    if request.method == 'POST':
        # authenticate user and prepare to set as game owner
        payload = verificationHelper.verify_jwt(request)
        if payload == 0:
            return ({"Error": "INVALID JWT"}, 401)
        content = request.get_json()
        # validate input
        if 'home' not in content.keys():
            return("need home team", 400)
        if 'away' not in content.keys():
            return("need away team", 400)
        if 'odds' not in content.keys() or type(content['odds']) != list:
            return("please give home then away odds as decimal in a list", 400)
        if 'maxLoss' not in content.keys() or content['maxLoss'] < 100:
            return("Need to know the potential loss for the game", 400)
        if 'description' not in content.keys():
            content['description'] = ''
        if 'vig' not in content.keys() or content['vig'] < 0.05 or content['vig'] > 0.4:    # can change house cut if wanted, but defaults to industry standard 10pct
            content['vig'] = 0.1
        decOdds = content['odds']
        if decOdds[0] == 0 or decOdds[1] == 0 or decOdds[0] + decOdds[1] != 1:
            return("The decimal odds must add up to 1 and cannot be 0", 400)
        # set public facing odds
        homeOdds = (1 / decOdds[0]) * (1 - content['vig'])
        awayOdds = (1 / decOdds[1]) * (1 - content['vig'])
        # create and populate new entity
        new_game = datastore.entity.Entity(key=client.key(constants.games))
        new_game.update({'home': content['home'], 'away': content['away'],
          'homeOdds': homeOdds, 'awayOdds': awayOdds, 'totalPool': 0,
          'maxLoss': content['maxLoss'], 'wagers': [],
          'homeLiability': 0, 'awayLiability': 0, 'vig': content['vig'], 'completed': False,
          'description': content['description'], 'owner': payload})
        client.put(new_game)
        gameInfo = {"gameID": str(new_game.key.id), "self": request.host_url + 'games/' + str(new_game.key.id)}
        return (gameInfo, 201)
    elif request.method == 'GET':
        query = client.query(kind=constants.games)
        q_limit = int(request.args.get('limit', '5'))
        q_offset = int(request.args.get('offset', '0'))
        l_iterator = query.fetch(limit= q_limit, offset=q_offset)
        pages = l_iterator.pages
        results = list(next(pages))
        gameBlurbs = []
        if l_iterator.next_page_token:
            next_offset = q_offset + q_limit
            next_url = request.base_url + "?limit=" + str(q_limit) + "&offset=" + str(next_offset)
        else:
            next_url = None
        for e in results:
            selfLink = request.host_url + 'games/' + str(e.key.id)
            gameBlurb = {'home': e['home']}
            gameBlurb['homeOdds'] = e['homeOdds']
            gameBlurb['away'] = e['away']
            gameBlurb['awayOdds'] = e['awayOdds']
            gameBlurb['description'] = e['description']
            gameBlurb['self'] = selfLink
            gameBlurbs.append(gameBlurb)
        output = {"games": gameBlurbs}
        if next_url:
            output["next"] = next_url
        return json.dumps(output)
    else:
        return ({"Error": "Method not recogonized"}, 405)

# from the individual game route, can read, "cancel", declare winner, or place wager
@bp.route('/<id>', methods=['DELETE', 'GET', 'POST', 'PUT'])
def games_post_put_delete_get(id):
    # deleting acts as cancelling a game, refunding amount bet but keeping ticket record for receipt
    if request.method == 'DELETE':
        # authenticate user
        payload = verificationHelper.verify_jwt(request)
        if payload == 0:
            return ({"Error": "INVALID JWT"}, 401)
        game_key = client.key(constants.games, int(id))
        game = client.get(key=game_key)
        if game is None:
            return({"Error": "Invalid game ID"}, 404)
        elif payload != game['owner']:                          # authorize user
            return({"Error": "Not your game to delete"}, 403)
        elif len(game['wagers']) == 0:
            client.delete(game_key)
            return ('', 204)
        else:
            for x in game['wagers']:
                wager_key = client.key(constants.wagers, int(x))
                wager = client.get(key=wager_key)
                wager['status'] = 'REFUND'
                client.put(wager)
                query = client.query(kind=constants.users)
                query.add_filter('name', '=', wager['owner'])
                betPlacer = list(query.fetch())
                refund = betPlacer[0]
                refund['balance'] += wager['betSize']
                client.put(refund)
            client.delete(game_key)
            return ('', 204)
    elif request.method == 'GET':
        game_key = client.key(constants.games, int(id))
        game = client.get(key=game_key)
        if game is None:
            return({"Error": "Invalid game ID"}, 404)
        else:
            return (game, 200)
    elif request.method == 'POST':
        # authenticate user to place bet
        payload = verificationHelper.verify_jwt(request)
        if payload == 0:
            return ({"Error": "INVALID JWT"}, 401)
        content = request.get_json()
        # validate input
        if 'betSize' not in content.keys() or content['betSize'] < 1:
            return("must bet at least one unit", 400)
        if 'betTeam' not in content.keys():
            return("need a team to bet on", 400)
        game_key = client.key(constants.games, int(id))
        game = client.get(key=game_key)
        if game is None:
            return({"Error": "Invalid game ID"}, 404)
        oddMulti = 0
        curLiability = 0
        betTeam = ''
        if content['betTeam'] == 'HOME':
            oddMulti = game['homeOdds']
            curLiability = game['homeLiability']
            betTeam = game['home']
        if content['betTeam'] == 'AWAY':
            oddMulti = game['awayOdds']
            curLiability = game['awayLiability']
            betTeam = game['away']
        propLiability = round((content['betSize'] * oddMulti), 2)
        # do not allow a single bet to eat up too much of the books risk
        if propLiability > ((game['totalPool'] + game['maxLoss'] - curLiability) / 4):
            return ({"Error": "The bet you placed was too large for the current pool"}, 400)
        # update the odds for future bets
        prevHomeOdds = (1 / game['homeOdds']) * (1 - game['vig'])
        prevAwayOdds = (1 / game['awayOdds']) * (1 - game['vig'])
        homePool = (game['totalPool'] + game['maxLoss']) * prevHomeOdds
        awayPool = (game['totalPool'] + game['maxLoss']) * prevAwayOdds
        if content['betTeam'] == 'HOME':
            game['homeLiability'] += propLiability
            homePool += propLiability
            if game['homeOdds'] <= 1.05:
                return ({"Error": "Action is not currently being taken on this side of the line"}, 400)
        if content['betTeam'] == 'AWAY':
            game['awayLiability'] += propLiability
            awayPool += propLiability
            if game['awayOdds'] <= 1.05:
                return ({"Error": "Action is not currently being taken on this side of the line"}, 400)
        game['totalPool'] += content['betSize']
        homePct = (homePool / (homePool + awayPool))
        awayPct = (awayPool / (homePool + awayPool))
        game['homeOdds'] = round((1 / homePct) * (1 - game['vig']), 2)
        game['awayOdds'] = round((1 / awayPct) * (1 - game['vig']), 2)
        if game['homeOdds'] <= 1.05 or game['awayOdds'] <= 1.05:
            return ({"Error": "Action is not currently being taken on this side of the line"})
        # create and populate new entity
        new_wager = datastore.entity.Entity(key=client.key(constants.wagers))
        new_wager.update({'betTeam': betTeam, 'betWin': propLiability,
          'betSize': content['betSize'], 'game': str(id),
          'status': 'OPEN', 'bookie': game['owner'], 'owner': payload})
        client.put(new_wager)
        wagerInfo = {"wagerID": str(new_wager.key.id), "self": request.host_url + 'wagers/' + str(new_wager.key.id), "homeChance": homePct, "awayChance": awayPct}
        # update the game odds, pools, and wager list
        game['wagers'].append(new_wager.key.id)
        client.put(game)
        #update betPlacers balance
        query = client.query(kind=constants.users)
        query.add_filter('name', '=', payload)
        betPlacer = list(query.fetch())
        refund = betPlacer[0]
        refund['balance'] -= content['betSize']
        client.put(refund)
        return (wagerInfo, 201)
    elif request.method == 'PUT':
        # authenticate user
        payload = verificationHelper.verify_jwt(request)
        if payload == 0:
            return ({"Error": "INVALID JWT"}, 401)
        game_key = client.key(constants.games, int(id))
        game = client.get(key=game_key)
        if game is None:
            return({"Error": "Invalid game ID"}, 404)
        elif payload != game['owner']:                          # authorize user
            return({"Error": "This is not your pool"}, 403)
        content = request.get_json()
        # validate input
        if 'winner' not in content.keys():
            return({"Error": "A winner must be declared"}, 400)
        elif content['winner'] != game['homeTeam'] or content['winner'] != game['awayTeam']:
            return({"Error": "A winner must be declared"}, 400)
        elif len(game['wagers']) == 0:
            client.delete(game_key)
            return ('', 200)
        else:
            for x in game['wagers']:
                wager_key = client.key(constants.wagers, int(x))
                wager = client.get(key=wager_key)
                if wager['betTeam'] == content['winner']:
                    wager['status'] = 'WON'
                    client.put(wager)
                    query = client.query(kind=constants.users)
                    query.add_filter('name', '=', wager['owner'])
                    betPlacer = list(query.fetch())
                    bettor = betPlacer[0]
                    bettor['balance'] += wager['betWin']
                    client.put(refund)
                else:
                    wager['status'] = 'LOST'
                    client.put(wager)
                    query = client.query(kind=constants.users)
                    query.add_filter('name', '=', game['owner'])
                    bookie = list(query.fetch())
                    winnerBookie = bookie[0]
                    winnerBookie['balance'] += wager['betSize']
                    client.put(winnerBookie)
            client.delete(game_key)
            return ('', 200)
    else:
        return ({"Error": "Method not recogonized"}, 405)


from django.shortcuts import render, redirect
from .forms import TimeEntryForm
from .models import TimeEntry

def index(request):
    time_entries = TimeEntry.objects.all()
    context = {'time_entries': time_entries}
    return render(request, 'index.html', context)

def create_time_entry(request):
    if request.method == 'POST':
        form = TimeEntryForm(request.POST)
        if form.is_valid():
            form.save()
            # Redirect to success page or display success message
            return redirect('/')

    else:
        form = TimeEntryForm()
    return render(request, 'create_time_entry.html', {'form': form})


import json
import argparse
import os
import numpy as np
from parse import parse
from glob import glob
from typing import List, Dict
from progressbar import ProgressBar
import pandas as pd
import json
import re
import math
import matplotlib.pyplot as plt
import time

"""Sets the sender pseudonym as sender ID for sybil attack scenarios and protocol level attacks"""

INITIAL_TRUST = 0.5
AGING_FACTOR=0.5

def vehicleParser(json_file, path_to_json,attacker_dict_ground_truth):
    log_name_tokens = os.path.splitext(json_file)[0].split("-")
    vehicle_id = log_name_tokens[1]  # [0]:= 'JSONlog' [1]:=vehicle instance count [2]:=vehicle id [1]:=vehicle attack type
    vehicle_OMNET_ID = log_name_tokens[2]  # [0]:= 'JSONlog' [1]:=vehicle instance count [2]:=vehicle id [1]:=vehicle attack type

    attack_type = log_name_tokens[3]
    if attack_type == 'A0':
        is_vehicle_attacker = 0
    else:
        is_vehicle_attacker = 1

    # start parsing the file
    BSMList = [] # received BSM messages
    GPSList=[] # own gps messages

    file_path = os.path.join(path_to_json, json_file)
    with open(file_path, "r") as f:
        log_rows = f.readlines()

    for row in log_rows:
        json_row = json.loads(row)
        json_row['vehicle_ID'] = vehicle_id  # set here, change later
        # json_row['vehicle_OMNET_ID'] = vehicle_OMNET_ID
        json_row['is_attacker'] = is_vehicle_attacker
        if json_row['type'] == 2: # discard the self GPS messages
            # gps_row = json_row

            # extract position
            json_row['pos_x'] = json_row['pos'][0]
            json_row['pos_y'] = json_row['pos'][1]
            #json_row['pos_z'] = json_row['pos'][2]
            del json_row['pos']

            # extract speed
            json_row['spd_x'] = json_row['spd'][0]
            json_row['spd_y'] = json_row['spd'][1]
            #json_row['spd_z'] = json_row['spd'][2]
            del json_row['spd']

            # extract position noise
            json_row['pos_x_noise'] = json_row['pos_noise'][0]
            json_row['pos_y_noise'] = json_row['pos_noise'][1]
            #json_row['pos_z_noise'] = json_row['pos_noise'][2]
            del json_row['pos_noise']

            # extract spd noise
            json_row['spd_x_noise'] = json_row['spd_noise'][0]
            json_row['spd_y_noise'] = json_row['spd_noise'][1]
            #json_row['spd_z_noise'] = json_row['spd_noise'][2]
            del json_row['spd_noise']

            # Acceleration
            json_row['accel_x'] = json_row['acl'][0]
            json_row['accel_y'] = json_row['acl'][1]
            #json_row['accel_z'] = json_row['acl'][2]
            del json_row['acl']

            # Heading
            json_row['heading_x'] = json_row['hed'][0]
            json_row['heading_y'] = json_row['hed'][1]
            #json_row['heading_z'] = json_row['hed'][2]
            del json_row['hed']

            # heading noise
            json_row['heading_x_noise'] = json_row['hed_noise'][0]
            json_row['heading_y_noise'] = json_row['hed_noise'][1]
            # json_row['heading_z'] = json_row['hed'][2]
            del json_row['hed_noise']

            # remove unnecessary columns
            del json_row['acl_noise']
            del json_row['type']
            #del json_row['hed_noise']

            # add new dict to list
            GPSList.append(json_row)

        else:

            # extract position
            json_row['pos_x'] = json_row['pos'][0]
            json_row['pos_y'] = json_row['pos'][1]
            #json_row['pos_z'] = json_row['pos'][2]
            del json_row['pos']

            # extract speed
            json_row['spd_x'] = json_row['spd'][0]
            json_row['spd_y'] = json_row['spd'][1]
            #json_row['spd_z'] = json_row['spd'][2]
            del json_row['spd']

            # extract position noise
            json_row['pos_x_noise'] = json_row['pos_noise'][0]
            json_row['pos_y_noise'] = json_row['pos_noise'][1]
            #json_row['pos_z_noise'] = json_row['pos_noise'][2]
            del json_row['pos_noise']

            # extract spd noise
            json_row['spd_x_noise'] = json_row['spd_noise'][0]
            json_row['spd_y_noise'] = json_row['spd_noise'][1]
            #json_row['spd_z_noise'] = json_row['spd_noise'][2]
            del json_row['spd_noise']

            # Acceleration
            json_row['accel_x'] = json_row['acl'][0]
            json_row['accel_y'] = json_row['acl'][1]
            #json_row['accel_z'] = json_row['acl'][2]
            del json_row['acl']

            # Heading
            json_row['heading_x'] = json_row['hed'][0]
            json_row['heading_y'] = json_row['hed'][1]
            #json_row['heading_z'] = json_row['hed'][2]
            del json_row['hed']

            # heading noise
            json_row['heading_x_noise'] = json_row['hed_noise'][0]
            json_row['heading_y_noise'] = json_row['hed_noise'][1]
            # json_row['heading_z'] = json_row['hed'][2]
            del json_row['hed_noise']

            # remove unnecessary columns
            del json_row['acl_noise']
            del json_row['sendTime']
            del json_row['type']
            # del json_row['senderPseudo']
            del json_row['messageID']
            #del json_row['hed_noise']

            # add new dict to list
            BSMList.append(json_row)

    # creat gps df
    GPS_df = pd.DataFrame(GPSList)
    if not GPS_df.empty:
        GPS_df = GPS_df.sort_values(["rcvTime"])

    # create bsm df
    BSM_df = pd.DataFrame(BSMList)
    if not BSM_df.empty:
        BSM_df = BSM_df.sort_values(["senderPseudo", "rcvTime"])

    # trust_dict= assignTrust(BSM_df=BSM_df,GPS_df=GPS_df)

    return GPS_df,BSM_df

def plausibilityParser(vehicle_ID,vehicle_plausibility_record):

    file_path =vehicle_plausibility_record
    f = open(file_path, )

    data = json.load(f)

    data_list = []
    for row in data:
        json_row = {}

        json_row['vehicle_ID'] = vehicle_ID  # set here, change later

        json_row['sender'] = row['BsmPrint']['BSMs'][0]['RealId']

        json_row['rcvTime'] = row['BsmPrint']['BSMs'][0]['ArrivalTime']

        json_row['proximityPlausibility'] = row['BsmPrint']['BsmCheck']['rpP']

        json_row['rangePlausibility'] = row['BsmPrint']['BsmCheck']['rP']

        json_row['PositionPlausibility'] = row['BsmPrint']['BsmCheck']['pP']

        json_row['SpeedPlausibility'] = row['BsmPrint']['BsmCheck']['sP']

        json_row['PositionConsistancy'] = row['BsmPrint']['BsmCheck']['pC']

        json_row['SpeedConsistancy'] = row['BsmPrint']['BsmCheck']['sC']

        json_row['PositionSpeedConsistancy'] = row['BsmPrint']['BsmCheck']['psC']

        json_row['PositionSpeedMaxConsistancy'] = row['BsmPrint']['BsmCheck']['psmC']

        json_row['PositionHeadingConsistancy'] = row['BsmPrint']['BsmCheck']['phC']

        json_row['SuddenAppearence'] = row['BsmPrint']['BsmCheck']['sA']

        json_row['BeaconFrequency'] = row['BsmPrint']['BsmCheck']['bF']

        data_list.append(json_row)

    # create bsm df
    BSM_df = pd.DataFrame(data_list)
    if not BSM_df.empty:
        BSM_df = BSM_df.sort_values(["sender", "rcvTime"])

    return BSM_df

def vehicleIDfromPlausibility(filename):

    name_components = re.split('_', os.path.splitext(filename)[0])

    vehicle_ID = name_components[2]

    # remove first "10" and last "1" to get the real ID of vehicles
    vehicle_ID =vehicle_ID[2:]
    vehicle_ID=vehicle_ID[:-1]

    return vehicle_ID

def parsePlausibilityRecords(plausibility_path):

    vehicle_records= os.listdir(plausibility_path)
    big_df = pd.DataFrame()

    for vehicle_plausibility_record in vehicle_records:

        vehicle_ID = vehicleIDfromPlausibility(vehicle_plausibility_record)

        cur_file = os.path.join(plausibility_path,vehicle_plausibility_record)
        parsed_df = plausibilityParser(vehicle_ID,cur_file)
        big_df = pd.concat([big_df, parsed_df], axis=0, ignore_index=True)

    return big_df

def mergePlausibility(plausibility_records,BSM_records):

    # from the beginning of time step
    plausibility_records['rcvTime'] = plausibility_records['rcvTime'].apply(np.floor)
    BSM_records['rcvTime'] = BSM_records['rcvTime'].apply(np.floor)

    trust_records = createTrustRecords(plausibility_records)

    all_data = pd.merge(BSM_records, trust_records, how='left', left_on=['vehicle_ID', 'senderPseudo', 'rcvTime'], right_on=['vehicle_ID', 'senderPseudo', 'rcvTime'])

    return all_data

    pass

def calculateTrust(sender,receiver,messages):

    considered_modules= ["rangePlausibility","positionConsistency","speedConsistency","positionSpeedConsistancy","positionHeadingConsistancy"]

    num_modules = len(considered_modules)
    module_weights = [1/num_modules]*num_modules # equal weights for all for now
    initial_trust= INITIAL_TRUST
    aging_factor = AGING_FACTOR

    # sort by message receiving time for time-based records
    trust_records = {}

    for index,row in messages.iterrows():
        cur_time = row['rcvTime']

        # find the prev trust
        if trust_records.keys() and (min(trust_records.keys())) < cur_time:

            # previous record exists, find the latest record
            max_time = max(trust_records.keys())
            prev_trust = trust_records[max_time]
        else:

            # no records exist
            prev_trust= initial_trust

        ## calculate the current step trust contribution
        current_step_score = 0
        for i,module in enumerate(considered_modules):
            current_step_score += module_weights[i] * row[module]

        # age the trust
        eventual_trust = aging_factor*prev_trust + (1-aging_factor) * current_step_score
        trust_records[cur_time] = eventual_trust

    # convert to data frame and merge
    trust_df = pd.DataFrame.from_dict(trust_records, orient='index',
                           columns=['Trust'])
    trust_df['rcvTime'] =trust_df.index
    trust_df.sort_values(['rcvTime'])

    new_messages = pd.merge(messages, trust_df, how='left', left_on=['rcvTime'], right_on=['rcvTime'])
    return new_messages

def trustEvolutionForReceiver(receiver_ID,records):

    sender_list = records['senderPseudo'].unique()
    g = records.groupby(["senderPseudo"])

    big_df = pd.DataFrame()

    for sender in sender_list:
        sender_records = g.get_group(sender)

        trust_records = calculateTrust(sender=sender,receiver=receiver_ID,messages=sender_records)
        pass

        big_df = pd.concat([big_df, trust_records], axis=0, ignore_index=True)

    return big_df

def createTrustRecords(plausibility_records):

    receiver_vehicles = plausibility_records['vehicle_ID'].unique()
    g = plausibility_records.groupby(['vehicle_ID'])

    big_df = pd.DataFrame()

    for receiver_vehicle in receiver_vehicles:
        current_records = g.get_group(receiver_vehicle)
        receiver_trust = trustEvolutionForReceiver(receiver_vehicle,current_records)

        big_df = pd.concat([big_df, receiver_trust], axis=0, ignore_index=True)

    return big_df

def parseSim(data_folder,output_path):
    print("Current data folder: "+data_folder)

    folder_list = os.listdir(data_folder)
    plausibility_path = None

    for folder in folder_list:
        if "MDBsmsList" in folder:
            plausibility_path = os.path.join(data_folder,folder)
        if "VeReMi" in folder:
            path_to_json = os.path.join(data_folder,folder)

    if plausibility_path:
        plausibility_records = parsePlausibilityRecords(plausibility_path=plausibility_path)

    else:
        # extract some plausibilties here
        pass

    """Find the json files"""

    json_files = [pos_json for pos_json in os.listdir(path_to_json) if
                  (pos_json.endswith('.json') and pos_json.startswith('traceJSON'))]

    ground_truth_file_path = [pos_json for pos_json in os.listdir(path_to_json) if
                              (pos_json.endswith('.json') and pos_json.startswith('traceGroundTruth'))]

    # json_files=json_files[:10] # remove later
    # start parsing

    big_df = pd.DataFrame()
    plausibility_records=pd.DataFrame()
    for json_file in json_files:
        GPS_df,BSM_df = vehicleParser(json_file, path_to_json,
                                  {})  # .sort_values(["sender","rcvTime"])

        if len(BSM_df)<1:
            continue # do not append anything from here

        plausibilities= createPlausibilities(GPS_df=GPS_df,BSM_df=BSM_df)

        big_df = pd.concat([big_df, BSM_df], axis=0, ignore_index=True)
        plausibility_records=pd.concat([plausibility_records,plausibilities], axis=0, ignore_index=True)

    # rename if wrong name
    if 'vehicle_ID_bsm' in plausibility_records.columns:
        plausibility_records.rename(columns={"vehicle_ID_bsm": "vehicle_ID"}, inplace=True)

    big_df = mergePlausibility(plausibility_records=plausibility_records,BSM_records=big_df)

    # drop noise columns before saving
    big_df.drop(list(big_df.filter(regex="noise")),axis=1,inplace=True)
    if 'sender_x' in big_df.columns:
        big_df.rename(columns={"sender_x": "sender"}, inplace=True)
        big_df.drop(['sender_y'],axis=1,inplace=True)

    # make the export file name
    dir_lst = data_folder.split(sep="/")
    exportfilename=os.path.join(output_path,dir_lst[1] + "_" + "parsed.csv")
    big_df.to_csv(index=False, path_or_buf=exportfilename)

    return big_df

def createPlausibilities(GPS_df,BSM_df):
    """
    Extract the palusibilities and append to BSM_df for additional features
    Args:
        GPS_df:
        BSM_df:

    Returns:

    """

    TOTAL_TIME=0
    NUM_MESSAGES=0

    MAX_PLAUSIBLE_RANGE = 420

    # get all unique senders in session
    all_senders = BSM_df['senderPseudo'].unique()

    BSM_df['rcvTime'] = BSM_df['rcvTime'].apply(np.floor)

    big_df_merged = pd.DataFrame()

    non_records = 0
    for sender in all_senders:

        sender_records = BSM_df[BSM_df['senderPseudo'] == sender]

        # align time vectors
        sliced_GPS_df= GPS_df[GPS_df.rcvTime.isin(sender_records.rcvTime)]
        merged= sender_records.merge(sliced_GPS_df,left_on='rcvTime',right_on='rcvTime',suffixes=('_bsm','_gps'))

        for index,row in merged.iterrows():

            try:
                # range plausibility
                merged.loc[index,'rangePlausibility']=rangePlausibility(sender_records.iloc[index],sliced_GPS_df.iloc[index])
            except:
                # range plausibility
                merged.loc[index, 'rangePlausibility'] = INITIAL_TRUST
                print("SYBIL,NON EXISTENT RECORD,initial trust value\n")
                non_records+=1

            if index==0:
                merged.loc[index, 'positionConsistency'] = INITIAL_TRUST #positionConsistencyCheck(row, oldRow)

                merged.loc[index, 'speedConsistency'] = INITIAL_TRUST # speedConsistencyCheck(row, oldRow)

                # position speed check
                merged.loc[index, 'positionSpeedConsistancy'] =INITIAL_TRUST # positionSpeedConsistancyCheck(row, oldRow)

                # position heading check
                merged.loc[index, 'positionHeadingConsistancy'] = INITIAL_TRUST # PositionHeadingConsistancyCheck(row, oldRow)

            else:

                oldRow =merged.loc[index-1]
                # speed and position consistency

                start = time.time()

                merged.loc[index, 'positionConsistency'] = positionConsistencyCheck(row,oldRow)

                merged.loc[index, 'speedConsistency'] = speedConsistencyCheck(row,oldRow)

                #position speed check
                merged.loc[index, 'positionSpeedConsistancy'] = positionSpeedConsistancyCheck(row, oldRow)

                # position heading check
                merged.loc[index, 'positionHeadingConsistancy'] = PositionHeadingConsistancyCheck(row, oldRow)

                end = time.time()
                current_elapsed_time = end - start
                TOTAL_TIME += current_elapsed_time
                NUM_MESSAGES+=1
                #print("Calculating trust: " + " took" + str(current_elapsed_time) + " seconds")

        #print("Average Time:" + str(TOTAL_TIME / NUM_MESSAGES))

        big_df_merged = pd.concat([big_df_merged,merged], axis=0, ignore_index=True)

    plausibilities= big_df_merged[["rcvTime","sender","senderPseudo","vehicle_ID_bsm","positionHeadingConsistancy","positionSpeedConsistancy","speedConsistency","positionConsistency","rangePlausibility"]]
    #BSM_df.reset_index(inplace=True,drop=True)
    #BSM_df=BSM_df.join(plausibilities)
    # print(non_records)
    return plausibilities #BSM_df

PI=math.pi

def PositionHeadingConsistancyCheck(row,oldRow):

    """
    (veins::Coord* curHeading,
    veins::Coord* curHeadingConfidence, veins::Coord* oldPosition,
    veins::Coord* oldPositionConfidence, veins::Coord* curPosition,
    veins::Coord* curPositionConfidence, double deltaTime, double curSpeed,
    double curSpeedConfidence):
    """

    POS_HEADING_TIME = 1.1
    MAX_HEADING_CHANGE = 90

    curHeading =calculateHeadingAngle(row)
    curHeadingConfidence=0
    oldPositionConfidence=calculatePositionConf(oldRow)
    curPositionConfidence=calculatePositionConf(row)
    deltaTime= np.abs(row.rcvTime - oldRow.rcvTime)
    curSpeed=calculateSpeedPtr(row)
    curSpeedConfidence=calculateSpeedConf(row)

    if (deltaTime < POS_HEADING_TIME):
        distance = calculateDistancePtr(row,oldRow);
        if (distance < 1):
            return 1;

        if (curSpeed - curSpeedConfidence < 1):
            return 1;

        curHeadingAngle = curHeading#calculateHeadingAnglePtr(curHeading);

        #relativePos = (curPosition->x - oldPosition->xcurPosition->y - oldPosition->y,curPosition->z - oldPosition->z);
        positionAngle = calculateHeadingAnglePtr(row.pos_x_bsm-oldRow.pos_x_bsm,row.pos_y_bsm-oldRow.pos_y_bsm);
        angleDelta = np.abs(curHeadingAngle - positionAngle);

        if (angleDelta > 180):
            angleDelta = 360 - angleDelta;

        angleLow = angleDelta - row.heading_x_noise_bsm;
        if (angleLow < 0):
            angleLow = 0;

        angleHigh = angleDelta + row.heading_x_noise_bsm;
        if (angleHigh > 180):
            angleHigh = 180;

        xLow = distance * np.cos(angleLow * PI / 180);

        curFactorLow = 1;
        if (row.heading_x_noise_bsm == 0):
            if (angleLow <= MAX_HEADING_CHANGE):
                curFactorLow = 1;

            else:
                curFactorLow = 0;

        else:
            curFactorLow =calculateCircleSegment(row.pos_x_noise_bsm,
                                                 row.pos_x_noise_bsm + xLow) /(PI * row.pos_x_noise_bsm * row.pos_x_noise_bsm);

        oldFactorLow = 1;
        if (oldRow.pos_x_noise_bsm == 0):
            if (angleLow <= MAX_HEADING_CHANGE):
                oldFactorLow = 1;

            else:
                oldFactorLow = 0;

        else:
            oldFactorLow = 1 - calculateCircleSegment(oldRow.pos_x_noise_bsm,oldRow.pos_x_noise_bsm - xLow) / (PI * oldRow.pos_x_noise_bsm * oldRow.pos_x_noise_bsm);

        xHigh = distance * np.cos(angleHigh * PI / 180);
        curFactorHigh = 1;
        if (row.pos_x_noise_bsm == 0):
            if (angleHigh <= MAX_HEADING_CHANGE):
                curFactorHigh = 1;

            else:
                curFactorHigh = 0;

        else:
            curFactorHigh =calculateCircleSegment(row.pos_x_noise_bsm,
                    row.pos_x_noise_bsm + xHigh) / (PI * row.pos_x_noise_bsm * row.pos_x_noise_bsm);

        oldFactorHigh = 1;
        if (oldRow.pos_x_noise_bsm == 0):
            if (angleHigh <= MAX_HEADING_CHANGE):
                oldFactorHigh = 1;

            else:
                oldFactorHigh = 0;

        else:
            oldFactorHigh = 1 - calculateCircleSegment(oldRow.pos_x_noise_bsm, oldRow.pos_x_noise_bsm - xHigh) / (PI * oldRow.pos_x_noise_bsm * oldRow.pos_x_noise_bsm);

        factor = (curFactorLow + oldFactorLow + curFactorHigh + oldFactorHigh) / 4;

        return factor;

    else:
        return 1; # 1

def calculateHeadingAnglePtr(heading_x_bsm,heading_y_bsm):
    x2 = 1;
    y2 = 0;

    dot = heading_x_bsm * x2 + heading_y_bsm * y2; # dot product between [x1, y1] and [x2, y2]
    det =heading_x_bsm  * y2 - heading_y_bsm * x2;      # determinant
    angle = np.arctan2(det, dot) * 180 / PI; # atan2(y, x) or atan2(sin, cos);

    if (heading_x_bsm  >= 0 and heading_y_bsm  > 0):
        angle = 360 + angle;
    elif (heading_x_bsm < 0 and heading_y_bsm  >= 0):
        angle = 360 + angle;

    return angle;

def calculateHeadingAngle(row):
    x2 = 1;
    y2 = 0;

    dot = row.heading_x_bsm * x2 + row.heading_y_bsm * y2; # dot product between [x1, y1] and [x2, y2]
    det =row.heading_x_bsm  * y2 - row.heading_y_bsm * x2;      # determinant
    angle = np.arctan2(det, dot) * 180 / PI; # atan2(y, x) or atan2(sin, cos);

    if (row.heading_x_bsm  >= 0 and row.heading_y_bsm  > 0):
        angle = 360 + angle;
    elif (row.heading_x_bsm < 0 and row.heading_y_bsm  >= 0):
        angle = 360 + angle;

    return angle;

def positionSpeedConsistancyCheck(row,oldRow):
    MAX_TIME_DELTA = 3.1

    MAX_MGT_RNG_DOWN = 6.2
    MAX_MGT_RNG_UP = 2.1

    MAX_PLAUSIBLE_ACCEL=2.6
    MAX_PLAUSIBLE_DECEL = 4.5

    MAX_PLAUSIBLE_SPEED=15
    MAX_PLAUSIBLE_RANGE=420

    speedDelta = calculateSpeedPtr(row) - calculateSpeedPtr(oldRow)

    curSpeed = calculateSpeedPtr(row)
    oldspeed = calculateSpeedPtr(oldRow)

    time= np.abs(row.rcvTime - oldRow.rcvTime)
    curSpeedConfidence=calculateSpeedConf(row)
    oldSpeedConfidence=calculateSpeedConf(oldRow)

    if (time < MAX_TIME_DELTA):
        distance = calculateDistancePtr(row, oldRow);

        curTest_1 = calculateSpeedPtr(row) + curSpeedConfidence;
        oldTest_1 = calculateSpeedPtr(oldRow) - oldSpeedConfidence;

        curTest_2 = calculateSpeedPtr(row) - curSpeedConfidence;
        oldTest_2 = calculateSpeedPtr(oldRow) + oldSpeedConfidence;

        if (curTest_2 < oldTest_2):
            curTest_2 = (curSpeed + oldspeed) / 2;
            oldTest_2 = (curSpeed + oldspeed) / 2;

        minSpeed = min(curSpeed, oldspeed);

        addon_mgt_range = MAX_MGT_RNG_DOWN + 0.3571 * minSpeed - 0.01694 * minSpeed * minSpeed;
        if (addon_mgt_range < 0):
            addon_mgt_range = 0;

        retDistance_1= [0]*2;
        calculateMaxMinDist(curTest_1, oldTest_1, time,MAX_PLAUSIBLE_ACCEL, MAX_PLAUSIBLE_DECEL, MAX_PLAUSIBLE_SPEED,
            retDistance_1);

        factorMin_1 = 1 - CircleCircleFactor(distance, row.pos_x_noise_bsm, oldRow.pos_x_noise_bsm, retDistance_1[0]);
        factorMax_1 = OneSidedCircleSegmentFactor(distance,row.pos_x_noise_bsm,oldRow.pos_x_noise_bsm,
            retDistance_1[1] + MAX_MGT_RNG_UP)

        retDistance_2 = [0] *2
        calculateMaxMinDist(curTest_2, oldTest_2, time,
            MAX_PLAUSIBLE_ACCEL, MAX_PLAUSIBLE_DECEL, MAX_PLAUSIBLE_SPEED,
            retDistance_2);
        factorMin_2 = 1 - OneSidedCircleSegmentFactor(distance, row.pos_x_noise_bsm,oldRow.pos_x_noise_bsm, retDistance_2[0] - addon_mgt_range);
        factorMax_2 = OneSidedCircleSegmentFactor(distance,
            row.pos_x_noise_bsm,oldRow.pos_x_noise_bsm,
            retDistance_2[1] + MAX_MGT_RNG_UP);

        retDistance_0= [0]*2
        calculateMaxMinDist(curSpeed, oldspeed, time,
            MAX_PLAUSIBLE_ACCEL, MAX_PLAUSIBLE_DECEL, MAX_PLAUSIBLE_SPEED,
            retDistance_0);
        factorMin_0 = 1 - OneSidedCircleSegmentFactor(distance, row.pos_x_noise_bsm, oldRow.pos_x_noise_bsm, retDistance_0[0] - addon_mgt_range);
        factorMax_0 = OneSidedCircleSegmentFactor(distance,
            row.pos_x_noise_bsm,oldRow.pos_x_noise_bsm,
            retDistance_0[1] + MAX_MGT_RNG_UP);

        factorMin = (factorMin_1 + factorMin_0 + factorMin_2) / 3.0;
        factorMax = (factorMax_1 + factorMax_0 + factorMax_2) / 3.0;

        return min(factorMin, factorMax);

    else:
        return INITIAL_TRUST # 1

PI =math.pi

def OneSidedCircleSegmentFactor(d, r1, r2,range):

    if (d < 0):
        return 1;

    if (range > d + r1 + r2):
        return 1;
    else:
        if (range < d - r1 - r2):
            return 0;
        else:
            d1 = 0;
            d2 = 0;
            if (d > 0):
                d1 = (r1 * r1 + d * d - r2 * r2) / (2 * d);
                d2 = (r2 * r2 + d * d - r1 * r1) / (2 * d);
                if ((d1 + r1) < range / 2 and (d2 + r2) > range / 2):
                    d2 = d2 - (range / 2 - (d1 + r1));
                    d1 = d1 + (range / 2 - (d1 + r1));

                if ((d2 + r2) < range / 2 and (d1 + r1) > range / 2):
                    d1 = d1 - (range / 2 - (d2 + r2));
                    d2 = d2 + (range / 2 - (d2 + r2));

            if (r1 <= 0 and r2 <= 0):
                if (range >= d):
                    return 1;
                else:
                    return 0;

            elif (r1 <= 0):
                if (range / 2 >= d1):
                    intD2 = (range / 2) - (d2 - r2);
                    area2 = calculateCircleSegment(r2, intD2);

                    factor = (area2) / (PI * r2 * r2);
                    return factor;
                else:
                    return 0;

            elif (r2 <= 0):
                if (range / 2 >= d2):
                    intD1 = (range / 2) - (d1 - r1);
                    area1 = calculateCircleSegment(r1, intD1);

                    factor = (area1) / (PI * r1 * r1);

                    return factor;
                else:
                    return 0;

            else:

                intD1 = (range / 2) - (d1 - r1);
                intD2 = (range / 2) - (d2 - r2);

                area1 = calculateCircleSegment(r1, intD1);
                area2 = calculateCircleSegment(r2, intD2);

                factor = (area1 + area2) / (PI * r1 * r1 + PI * r2 * r2);

                return factor;

def calculateCircleSegment(radius, intDistance):
    area = 0;

    if (radius <= 0):
        return 0;

    if (intDistance <= 0):
        return 0;

    if (intDistance > 2 * radius):
        return PI * radius * radius;

    if (radius > intDistance):
        area = radius * radius * SafeAcos((radius - intDistance) / radius)- (radius - intDistance)* math.sqrt(
                                2 * radius * intDistance
                                        - intDistance * intDistance);
    else:
        intDistanceTemp = 2 * radius - intDistance;
        area = radius * radius * SafeAcos((radius - intDistanceTemp) / radius)- (radius - intDistanceTemp)* math.sqrt(
                                2 * radius * intDistanceTemp
                                        - intDistanceTemp * intDistanceTemp);
        area = PI * radius * radius - area;

    return area;

def SafeAcos(x):
    if (x < -1.0):
        x = -1.0;
    elif (x > 1.0):
        x = 1.0;
    return np.arccos(x);

def CircleCircleFactor(d, r1, r2,range):

    d1 = 0;
    d2 = 0;
    if (d > 0):
        d1 = (r1 * r1 + d * d - r2 * r2) / (2 * d);
        d2 = (r2 * r2 + d * d - r1 * r1) / (2 * d);

        if ((d1 + r1) < range / 2 and (d2 + r2) > range / 2):
            d2 = d2 - (range / 2 - (d1 + r1));
            d1 = d1 + (range / 2 - (d1 + r1));

        if ((d2 + r2) < range / 2 and (d1 + r1) > range / 2):
            d1 = d1 - (range / 2 - (d2 + r2));
            d2 = d2 + (range / 2 - (d2 + r2));

    if (r1 <= 0 and r2 <= 0):
        if (range >= d):
            return 1;
        else:
            return 0;

    elif (r1 <= 0):
        if (range / 2 >= d1):
            area2 = calculateCircleCircleIntersection(r2, range / 2, d2);
            factor = (area2) / (math.pi * r2 * r2);
            return factor;
        else:
            return 0;

    elif (r2 <= 0) :
        if (range / 2 >= d2):
            area1 = calculateCircleCircleIntersection(r1, range / 2, d1);
            factor = (area1) / (math.pi * r1 * r1);
            return factor;
        else:
            return 0;

    else:
        area1 = calculateCircleCircleIntersection(r1, range / 2, d1);
        area2 = calculateCircleCircleIntersection(r2, range / 2, d2);
        factor = (area1 + area2) / (math.pi * r1 * r1 + math.pi * r2 * r2);
        return factor;

def calculateCircleCircleIntersection(r0,r1,d):

    if (r0 <= 0 or r1 <= 0):
        return 0;

    rr0 = r0 * r0;
    rr1 = r1 * r1;

    # Circles do not overlap
    if (d > r1 + r0):
        return 0;

    # Circle1 is completely inside circle0
    elif (d <= np.abs(r0 - r1) and r0 >= r1):
        # Return area of circle1
        return math.pi * rr1;

# Circle0 is completely inside circle1
    elif (d <= np.abs(r0 - r1) and r0 < r1):
        #Return area of circle0
        return math.pi * rr0;

    else:
        phi = (SafeAcos((rr0 + (d * d) - rr1) / (2 * r0 * d))) * 2;
        theta = (SafeAcos((rr1 + (d * d) - rr0) / (2 * r1 * d))) * 2;
        area1 = 0.5 * theta * rr1 - 0.5 * rr1 * np.sin(theta);
        area2 = 0.5 * phi * rr0 - 0.5 * rr0 * np.sin(phi);

# Return area of intersection
        return area1 + area2;

def calculateMaxMinDist(curSpeed, oldspeed, time,MAX_PLAUSIBLE_ACCEL,MAX_PLAUSIBLE_DECEL,MAX_PLAUSIBLE_SPEED,returnDistance):

    if(curSpeed<0):
        curSpeed = 0;

    if(oldspeed<0):
        oldspeed = 0;

    deltaV = curSpeed - oldspeed;

    T_1 = (deltaV + time * MAX_PLAUSIBLE_DECEL) / (MAX_PLAUSIBLE_ACCEL + MAX_PLAUSIBLE_DECEL);
    T_2 = time - T_1;

    maxSpeed = MAX_PLAUSIBLE_ACCEL * T_1 + oldspeed;
    maxDistance = 0;
    if (maxSpeed > MAX_PLAUSIBLE_SPEED):
        newT_1 = (MAX_PLAUSIBLE_SPEED - oldspeed) / MAX_PLAUSIBLE_ACCEL;
        newT_2 = (MAX_PLAUSIBLE_SPEED - curSpeed)/ MAX_PLAUSIBLE_DECEL;

        maxDistance = oldspeed * newT_1+ 0.5 * MAX_PLAUSIBLE_ACCEL * newT_1 * newT_1+ maxSpeed * newT_2- \
                      0.5 * MAX_PLAUSIBLE_DECEL * newT_2 * newT_2+ MAX_PLAUSIBLE_SPEED * (time - newT_1 - newT_2);
    else:
        maxDistance = oldspeed * T_1 + 0.5 * MAX_PLAUSIBLE_ACCEL * T_1 * T_1 + maxSpeed * T_2 - 0.5 * MAX_PLAUSIBLE_DECEL * T_2 * T_2;

    minSpeed = -MAX_PLAUSIBLE_DECEL * T_2 + oldspeed;
    minDistance = 0;
    if (minSpeed < 0):
        newT_1 = curSpeed / MAX_PLAUSIBLE_ACCEL;
        newT_2 = oldspeed / MAX_PLAUSIBLE_DECEL;

        minDistance = oldspeed * newT_2- 0.5 * MAX_PLAUSIBLE_DECEL * newT_2 * newT_2 + 0.5 * MAX_PLAUSIBLE_ACCEL * newT_1 * newT_1;
    else:
        minDistance = oldspeed * T_2 - 0.5 * MAX_PLAUSIBLE_DECEL * T_2 * T_2 + minSpeed * T_1 + 0.5 * MAX_PLAUSIBLE_ACCEL * T_1 * T_1;

    returnDistance[0] = minDistance;
    returnDistance[1] = maxDistance;

def rangePlausibility(row1,row2):

    MAX_PLAUSIBLE_RANGE=420

    def helper(row1, row2):
        x_2 = (row1.pos_x - row2.pos_x) ** 2
        y_2 = (row1.pos_y - row2.pos_y) ** 2
        return math.sqrt(x_2 + y_2)

    distance = helper(row1,row2)

    factor = CircleCircleFactor(distance,row1.pos_x_noise, row2.pos_x_noise,MAX_PLAUSIBLE_RANGE);

    return factor;

def positionConsistencyCheck(row,oldRow):

    MAX_PLAUSIBLE_SPEED=15

    distance = calculateDistancePtr(row,oldRow)
    timediff= np.abs(row.rcvTime - oldRow.rcvTime)

    if distance<MAX_PLAUSIBLE_SPEED*timediff:
        return 1
    else:
        return 0

def speedConsistencyCheck(row,oldRow):

    MAX_PLAUSIBLE_ACCEL=2.6
    MAX_PLAUSIBLE_DECEL = 4.5

    speedDelta = calculateSpeedPtr(row) - calculateSpeedPtr(oldRow)

    timediff= np.abs(row.rcvTime - oldRow.rcvTime)
    curSpeedConfidence=calculateSpeedConf(row)
    oldSpeedConfidence=calculateSpeedConf(oldRow)

    factor = 1;
    if (speedDelta > 0):
        factor = SegmentSegmentFactor(speedDelta, curSpeedConfidence,
            oldSpeedConfidence, MAX_PLAUSIBLE_ACCEL * timediff)

    else:
        factor = SegmentSegmentFactor(np.abs(speedDelta),
            curSpeedConfidence, oldSpeedConfidence,
            MAX_PLAUSIBLE_DECEL * timediff);

    return factor;

def SegmentSegmentFactor(d, r1,r2,range):

    d1 = 0
    d2 = 0
    if (d > 0) :
        d1 = (r1 * r1 + d * d - r2 * r2) / (2 * d)
        d2 = (r2 * r2 + d * d - r1 * r1) / (2 * d)

        if ((d1 + r1) < range / 2 and (d2 + r2) > range / 2) :
            d2 = d2 - (range / 2 - (d1 + r1))
            d1 = d1 + (range / 2 - (d1 + r1))

        if ((d2 + r2) < range / 2 and (d1 + r1) > range / 2):
            d1 = d1 - (range / 2 - (d2 + r2))
            d2 = d2 + (range / 2 - (d2 + r2))

    overlap1 = 0;
    overlap2 = 0;

    addon = 0;

    if ((d1 - range / 2) < r1):
        if ((d1 - range / 2) > -r1):
            addon = -(d1 - r1)
            overlap1 = range / 2 + addon

        else:
            overlap1 = 2 * r1

    if ((d2 - range / 2) < r2):
        if ((d2 - range / 2) > -r2):
            addon = -(d2 - r2)
            overlap2 = range / 2 + addon

        else:
            overlap2 = 2 * r2

    if (r1 == float(0) and r2 == float(0)):
        if (d > range):
            return 0
        else:
            return 1

    factor = (overlap1 + overlap2 ) / (2 * r1 + 2 * r2)

    return factor

def calculateSpeedPtr(row):

    return math.sqrt(row.spd_x_bsm**2 + row.spd_y_bsm**2)

def calculateSpeedConf(row):
    return math.sqrt(row.spd_x_noise_bsm**2 + row.spd_y_noise_bsm**2)

def calculatePositionConf(row):
    return math.sqrt(row.pos_x_noise_bsm**2 + row.pos_y_noise_bsm**2)

def calculateDistancePtr(row1,row2):
    x_2 = (row1.pos_x_bsm-row2.pos_x_bsm)**2
    y_2 =(row1.pos_y_bsm-row2.pos_y_bsm)**2
    return math.sqrt(x_2+y_2)

def findAndParseAllData(sim_path,num_cases,output_path):
    from functools import partial
    from multiprocessing import Pool

    test_folders = os.listdir(sim_path)

    for i,folder in enumerate(test_folders):
        test_folders[i]=os.path.join(sim_path,folder)

    # Create the pool and start parsing
    func = partial(parseSim, output_path=output_path)
    pool = Pool()
    pool.map(func, test_folders)
    pool.close()
    pool.join()

if __name__ == '__main__':

    sim_path = "raw_logs-veremiExtension/" # raw data path
    num_cases = 2
    output_path ="parsed_logs-veremiExtension/"
    findAndParseAllData(sim_path,num_cases,output_path)


import sys
import argparse
from pathlib import Path

parser = argparse.ArgumentParser()
parser.add_argument('path')

args = parser.parse_args()

target_dir = Path(args.path)

if not target_dir.exists():
	print('The target directory does not exist')
	raise SystemExit(1)

for entry in target_dir.iterdir():
	print(entry.name)

# if (args_count := len(sys.argv)) > 2:
# 	print(f'One argument expected, got {args_count - 1}')
# 	raise SystemExit(2)
# elif args_count < 2:
# 	print('You must specify the target directory')
# 	raise SystemExit(2)

# target_dir = Path(sys.argv[1])

# if not target_dir.is_dir():
# 	print('The target directory does not exist')
# 	raise SystemExit(1)

# for entry in target_dir.iterdir():
# 	print(entry.name)

# >>>python ls_argv.py sample/
# hello.txt
# lorem.md
# realpython.md

# >>>python ls_argv.py
# You must specify the target directory

# >>>python ls_argv.py sample/ other_dir/
# One argument expected, got 2

# >>>python ls_argv.py non_existing/
# The target directory doesn't exist

Build Command-Line Interfaces With Python's argparse/index.py


import flickrapi
from common.utility import Coordinate_handler, Date_handler, File_handler
import settings
from common.content import Post

class Base():
    def __init__(self, access_token):
        self.token = access_token
        self.api = flickrapi.FlickrAPI(self.token)
    def get_media_objects_in_bbox(self, bbox, min_timestamp):
        '''returns xmlnode with photo ids

        '''
        r = self.api.photos_search(min_taken_date=min_timestamp, accuracy='11', privacy_filter='1',
                                   bbox=bbox)
        return media
    def make_igram(self, media_obj):
        o = media_obj
        post_id = o.id
        if o.caption:
            text = o.caption.text
        else:
            text = 'Check out my instagram!'
        created = o.created_time
        content_img_url = o.link
        user_img_url = o.user.profile_picture
        user_id = o.user.id
        username = o.user.username
        coord = "{0},{1}".format(o.location.point.latitude, o.location.point.longitude)
        if o.location.name:
            place_name = o.location.name
        else:
            place_name = Coordinate_handler().reverse_lookup_locality(coord)
        up_votes = o.like_count
        i = Post(post_id, text, created, content_img_url, user_img_url, user_id, place_name, coord, username, 'instagram', up_votes)
        return i
    def get_staging_file(self):
        staging_file_path = settings.STAGING_RAW_FEED_FILE_PATH
        file_name_prefix = settings.insta_raw_feed_file_prefix
        self.staging_full_path = "{0}{1}{2}.txt".format(staging_file_path, file_name_prefix,
                                                        Date_handler().get_current_utc_date_string(settings.UTC_TIMESTAMP_FORMAT))
        f = File_handler(self.staging_full_path)
        return f
    def write_flickrs_to_file(self, bbox):
        min_timestamp = Date_handler().get_utc_x_minutes_ago(settings.flickr_fetch_window_in_minutes)
        media_set = self.get_closest_media_objects(lat, lon, min_timestamp)
        igrams = [(self.make_igram(media)) for media in media_set]
        f = self.get_staging_file()
        [f.append_to_file_as_json(i.get_as_dict()) for i in igrams]
        f.copy_file_to(self.staging_full_path, settings.RAW_FEED_FILE_PATH)


import pygame
import sys
from src.Rocket import Rocket

screen = pygame.display.set_mode((900, 600))
rockets = []
rocket_every_x_frames = 8
counter = 0

while True:
    counter += 1
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
    if counter % rocket_every_x_frames == 0:
        rockets.append(Rocket())

    # Render
    pygame.display.get_surface().fill((41, 50, 65))
    for r in rockets[:]:
        r.render()
        if r.dead:
            rockets.remove(r)
    pygame.time.Clock().tick(30)
    pygame.display.flip()


from tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping, ReduceLROnPlateau,TensorBoard
import tensorflow.keras.backend as K
import tensorflow as tf
from config import patience, epochs, num_train_samples, num_lfw_valid_samples, batch_size,alpha
from data_generator import DataGenSequence
from model import build_model

# 定义triplet loss损失函数
def triplet_loss(y_ture,y_pred):
    batch_num = len(y_pred)
    a_pred = y_pred[0:int(batch_num/3)]
    p_pred = y_pred[int(batch_num/3):int(2*batch_num/3)]
    n_pred = y_pred[int(2*batch_num/3):batch_num]
    positive_distance = K.square(tf.norm(a_pred - p_pred, axis=-1))
    negative_distance = K.square(tf.norm(a_pred - n_pred, axis=-1))
    loss = K.mean(K.maximum(0.0, positive_distance - negative_distance + alpha))
    return loss

if __name__ == '__main__':

    # 模型保存路径
    checkpoint_models_path = 'weights/'

    model_names = checkpoint_models_path + 'model.{epoch:02d}-{val_loss:.4f}.h5'
    # 定义模型保存信息
    model_checkpoint = ModelCheckpoint(model_names, monitor='val_loss', verbose=1, save_best_only=True)
    # 训练早停
    early_stop = EarlyStopping('val_loss', patience=patience)
    # 学习率衰减策略
    reduce_lr = ReduceLROnPlateau('val_loss', factor=0.5, patience=int(patience / 2), verbose=1)

    log = TensorBoard(log_dir='log')

    # 构建网络模型
    model = build_model()

    # 定义优化器和学习率
    # sgd = keras.optimizers.SGD(lr=1e-5, momentum=0.9, nesterov=True, decay=1e-6)
    adam = tf.keras.optimizers.Adam(lr=0.0001)
    # 编译，使用triplet loss损失函数
    model.compile(optimizer=adam, loss=triplet_loss)

    print(model.summary())

    # 声明回调函数
    callbacks = [model_checkpoint, early_stop, reduce_lr,log]

    # 开始训练
    model.fit_generator(DataGenSequence('face_dataset'),
            steps_per_epoch=num_train_samples // batch_size,
            validation_data=DataGenSequence('face_dataset'),
            validation_steps=num_lfw_valid_samples // batch_size,
            epochs=epochs,verbose=1,callbacks=callbacks,
            use_multiprocessing=True,workers=0)


'''
N*N 격자
블록정보 입력

블록
검정: -1 : 그룹에 포함될 수 없음, 중력 적용 안됨
무지개: 0 : 어디든 포함될 수 있음
일반블록: 1~M 값을 가지고 있음

인접 기준: 상하좌우

그룹 조건
1. 일반 블록이 하나 이상 포함되어야 함
2. 한 그룹에 일반 블록 색은 모두 같아야 한다
3. 검은블록은 포함될 수 없음
4. 무지개블록은 개수 상관 없이 포함될 수 있음
5. 한 그룹은 2개 이상 블록을 가져야함 (블록개수 < 2 면 brek)
6. 그룹의 기준 번호는 일반블록 중 행, 열이 가장 작은 블록.

[1] 그룹 탐색
1. 포함된 블록 개수가 가장 큰 그룹 탐색
2. 무지개 블록 개수가 가장 많은 그룹
3. 기준블록 좌표가 가장 작은 것
=> 그룹 정보: 전체 블록 개수, 무지개 블록 개수, 일반블록좌표(sort()로 기준 찾음)

[2] 해당 그룹 블록 삭제
점수는 (그룹 내 블록 개수)^2
=> 누적함

[3] 격자에 중력 작용
=> 이거 외워가자

[4] 격자 90도 반시계방향으로 회전
=> 구상해보자
==> 다른 방향으로 돌리기도 생각하기

[5] 다시 중력 작용

--> 획득한 점수 합 구하기
'''

from copy import deepcopy
from collections import deque

# 인접블록 상하좌우
d = [(-1,0),(1,0),(0,-1),(0,1)]

N, M = map(int, input().split())

block_map = []

for _ in range(N):
    block_map.append( list( map(int, input().split()) ) )

#print(N, M, '\n', block_map, '\n')

# 빈블록 표현
# block_map[r][c] = -2

# 그룹 정보: 전체 블록 개수, 무지개 블록 개수, 일반블록좌표(sort()로 기준 찾음)
group_blockpos = []

# group_count[0]: 전체개수
# group_count[1]: 무지개블록 개수
group_count = [0,0]

def search_group(block_map, visited, x, y):

    group_visited = deepcopy(visited)
    block_num = block_map[x][y]
    rainbow_block = 0
    block_pos = []
    point = [x,y]

    all_block = 0
    q = deque()
    q.append([x,y])
    visited[x][y] = 1
    group_visited[x][y] = 1
    #print('q: ', q)

    while q:
        pos = q.popleft()
        #visited[pos[0]][pos[1]] = 1
        #group_visited[pos[0]][pos[1]] = 1
        block_pos.append([pos[0], pos[1]])
        all_block += 1
        #print('q: {} x,y:{}'.format(q, pos))

        for di in d:
            nx = pos[0] + di[0]
            ny = pos[1] + di[1]
            #print('pos[0], di[0]: ',pos[0], di[0], '  nx? ',nx)
            #print('pos[1], di[1]: ',pos[1], di[1], '  ny? ',ny)
            if 0 <= nx < N and 0 <= ny < N:
                if group_visited[nx][ny] != 1:
                    if block_map[nx][ny] == block_num:
                        #print('1 in??')
                        #all_block += 1
                        #block_pos.append([nx,ny])
                        point = min(point, [nx,ny])
                        visited[nx][ny] = 1
                        group_visited[nx][ny] = 1
                        q.append([nx,ny])
                    elif block_map[nx][ny] == 0:
                        #print('2 in??')
                        #all_block += 1
                        rainbow_block += 1
                        #block_pos.append([nx,ny])
                        #visited[nx][ny] = 1
                        group_visited[nx][ny] = 1
                        q.append([nx,ny])
                    #else:
                        #print('3 in??')
                        #visited[nx][ny] = 1

    # [전체블록개수, 무지개블록개수, (기준좌표), blocks]
    result = [ 0 for _ in range(4)]
    result[0] = all_block
    result[1] = rainbow_block
    result[2] = point
    # 결과 확인용
    block_pos.sort()
    result[3] = block_pos

    #print('[search_group] result: ',result)

    return result

def big_group(block_map):

    # group_list[n] = [전체블록개수, 무지개블록개수, (기준좌표), blocks]
    group_list = []
    del_group = [ 0 for _ in range(2)]
    visited = [ [0]*N for _ in range(N) ]

    for r in range(N):
        for c in range(N):
            # 일반블록인 경우 탐색 시작
            if block_map[r][c] > 0:
                if visited[r][c] != 1:
                    #print('어디? ',r,c,' 값은? ', block_map[r][c])
                    group_info = search_group(block_map, visited, r, c)
                    #print('[big_group] group_info: ',group_info)
                    # 일반 블록 개수가 2개 이상인 경우
                    # 그룹에는 일반 블록이 적어도 하나 있어야 하며: 일반블록 1개 포함
                    # 룹에 속한 블록의 개수는 2보다 크거나 같아야 하며: 2개 이상
                    if group_info[0] >= 2 :
                        group_list.append(group_info)

    #print('[big_group] group_list: ',group_list)

    if group_list:

        # 아.. 지문을 잘못 이해했네....
        # 기준 블록을 정하는 기준
        # 기준 블록 중 행열이 큰게 고르는 기준
        #group_list.sort( key = lambda x : (-x[0], -x[1], x[2][0])   )
        group_list.sort(reverse=True)
        #print('[big_group] group_list sort: ',group_list)

        del_group[0] = group_list[0][0]
        del_group[1] = group_list[0][3]
        #print('del_group: ',del_group)
        return del_group
    else:
        return []

def gravity(block_map):

    # 배열 아래서부터 확인
    # N-2~0
    # 맨 아래칸은 탐색하지 않음
    for i in range(N-2, -1, -1):
        # 0~N-1
        for j in range(N):
            # 옮길 수 있는 블록 확인.
            # 옮길 수 없는 블록, 빈칸 쓰루
            if block_map[i][j] > -1:
                # r: 옮길 블록의 행번호
                r = i
                # 블록의 아래 좌표를 한 칸씩 탐색
                while True:
                    # 옮기려는 좌표가 배열 범위인지, 빈칸인지 확인
                    if 0 <= r+1 < N and block_map[r+1][j] == -2:
                        # 한 칸씩 이동
                        block_map[r][j], block_map[r+1][j] = block_map[r+1][j], block_map[r][j]
                        r += 1
                    # 배열 범위를 넘거나, 빈칸이 아니면(-1블록도 자연스럽게 쓰루) 옮기기 종료
                    else:
                        break

def rotate(block_map):

    new_block_map = []

    for z in zip(*block_map):
        new_block_map.append(list(z))

    new_block_map.reverse()

    return new_block_map

# 더 쉬운 방법 찾아서 이건 사용 안함
def rotate_1(block_map):

    new_block_map = [ [0]*N for _ in range(N) ]

    for r in range(N):
        for c in range(N):
            nr = (N-1)-c
            nc = r
            new_block_map[nr][nc] = block_map[r][c]

    return new_block_map

score = 0
while True:

    #print('1 block_map: ',block_map,'\n')

    del_group = big_group(block_map)

    #print('2 del_group: ',del_group, '\n')

    # 삭제할 수 있는 그룹이 없음
    if not del_group:
        break

    score += del_group[0]**2

    #print('3 score: ',score, '\n')

    # 블록 삭제
    for pos in del_group[1]:
            block_map[pos[0]][pos[1]] = -2

    #print('4 블록 삭제: ',block_map, '\n')

    # 블록 아래로 땡기기
    gravity(block_map)
    #print('5 중력1: ',block_map, '\n')

    # 반시계 90도 회전
    block_map = rotate(block_map)
    #print('6 회전: ',block_map, '\n')

    # 블록 아래로 땡기기
    gravity(block_map)
    #print('7 중력2: ',block_map, '\n')

print(score)

def gravity_1(block_map):

    print('[gravity] block_map: ',block_map)
    temp = []
    last_pos = -1
    for c in range(N):
        # 작은 행 -> 마지막 행 까지 탐색
        for r in range(N):
            print('[gravity] start r, c: ', r, c)

            if block_map[r][c] == -1:
                if temp:
                #지금까지 저장한 원소 좌표 옮기기
                    for r2 in range(r-1, -1, -1):
                        #print('# ',r2)
                        if not temp:
                            #print('!! ',temp)
                            break
                        else:
                            #print('!temp: ',temp)
                            #print('? ',temp.pop())
                            block_map[r2][c] = temp.pop()
                            #print('r2, c',r2, c, ' block_map',  block_map[r2][c])
                    print('[gravity] !!! block_map: {}   temp: {}'.format(block_map, temp))
                continue

            elif block_map[r][c] == -2:
                #가장 마지막에 비어있는 좌표 기억
                last_pos = r
            else:
                print('[gravity] block_map[r][c]: ', block_map[r][c])
                temp.append(block_map[r][c])
                block_map[r][c] = -2
                print('[gravity] append temp: ',temp)

            if r == N-1:
                # 저장된 블록이 있는 경우
                # 빈칸이 있는 경우
                if last_pos != -1 and temp:
                    print('[gravity] r == N-1:', r, '  temp: ', temp)
                    #지금까지 저장한 원소 좌표 옮기기
                    for r2 in range(last_pos, -1, -1):
                        if not temp:
                            break
                        else:
                            block_map[r2][c] = temp.pop()
                    print('[gravity] ??? block_map: {}   temp: {}'.format(block_map, temp))

def gravity_2(block_map):

    # 1. 빈공간 확인
    # 2. 옮길 블록 확인
    # 옮기는 시점: -1 블록을 만났을 때, 배열의 끝에 도달했을때
    # 블록이 빈공간 없이 꽉 차이있으면 옮길 필요 없음!

    empty_pos = []
    temp = []

    # 열 고정, 행 이동
    for r in range(N):
        for c in range(N):

            # 빈칸 좌표 다 저장
            if block_map[r][c] == -2:
                empty_pos.append(r)

            # 옮기는 시점
            elif block_map[r][c] == -1:
                # 탐색 시작 지점 ~ -1 블록까지 빈칸이 존재
                if temp:
                    # 맨 마지막에 저장된 원소부터 확인하기 위함
                    temp.reverse()
                    for t in temp:
                        # 빈 칸이 없으면 바로 out: 옮길게 없음
                        if not empty_pos:
                            break
                        # 맨 마지막 좌표 값
                        empty_pos.sort(reverse=True)
                        # 저장된 값도 있고, 빈칸도 있는 경우
                        if t[0] < empty_pos:
                            # 빈칸에 값 저장
                            block_map[empty_pos][c] = t[1]

                # 빈칸 없으면 temp 비우고 탐색 넘어감
                else:
                    temp = []
                    continue

            else:
                #저장
                temp.append([r, block_map[r][c]])

        if empty_pos != -1 and temp:
            return
            #옮기기


from unityagents import UnityEnvironment
import random
import torch
import numpy as np
from collections import deque
import os
import argparse
import matplotlib.pyplot as plt

from memory import ReplayBuffer, PrioritizedReplayBuffer
from model import QNetwork, DuelQNetwork
from agent import Agent

# Arguments Parsing Settings
parser = argparse.ArgumentParser(description="DQN Reinforcement Learning Agent")

parser.add_argument('--seed', help="Seed for random number generation", type=int, default=0)
parser.add_argument('--checkpoint', help="The model checkpoint file name", default="checkpoint.pth")
parser.add_argument('--reward_plot', help="The reward plot file name", default="reward_plot.png")

# training/testing flags
parser.add_argument('--train', help="train or test (flag)", action="store_true")
parser.add_argument('--algorithm', choices=["dqn", "ddqn"], help="The algorithm", default="ddqn")
parser.add_argument('--test_episodes', help="The number of episodes for testing", type=int, default=3)
parser.add_argument('--train_episodes', help="The number of episodes for training", type=int, default=500)
parser.add_argument('--batch_size', help="The mini batch size", type=int, default=64)
parser.add_argument('--eps_start', help="Epsilon start value for exploration/exploitation", type=float, default=1.0)
parser.add_argument('--eps_decay', help="Epsilon decay value for exploration/exploitation", type=float, default=0.995)
parser.add_argument('--eps_end', help="Epsilon minimum value for exploration/exploitation", type=float, default=0.01)
parser.add_argument('--gamma', help="The reward discount factor", type=float, default=0.99)
parser.add_argument('--tau', help="For soft update of target parameters", type=float, default=1e-3)
parser.add_argument('--lr', help="The learning rate ", type=float, default=0.00025)
parser.add_argument('--update_network_steps', help="How often to update the network", type=int, default=4)

# replay memory
parser.add_argument('--buffer_type', choices=["uniform", "prioritized"], help="The replay buffer type", default="uniform")
parser.add_argument('--buffer_size', help="The replay buffer size", type=int, default=int(1e5))
parser.add_argument('--update_buffer_steps', help="How often to update the buffer", type=int, default=15000)
parser.add_argument('--alpha', help="The priority exponent", type=float, default=0.7)
parser.add_argument('--beta', help="The importance sampling exponent", type=float, default=0.5)
parser.add_argument('--beta_inc', help="The importance sampling exponent increment", type=float, default=1.075)

# model
parser.add_argument('--network', choices=["linear", "linear_duel"], help="The neural network model", default="linear_duel")

def create_environment():
    env = UnityEnvironment("Banana_Windows_x86_64/Banana.app")

    # get the default brain
    brain_name = env.brain_names[0]
    brain = env.brains[brain_name]

    # reset the environment
    env_info = env.reset(train_mode=True)[brain_name]

    # number of agents in the environment
    print('Number of agents:', len(env_info.agents))

    # number of actions
    action_size = brain.vector_action_space_size
    print('Number of actions:', action_size)

    # examine the state space
    state = env_info.vector_observations[0]
    print('States look like:', state)
    state_size = len(state)
    print('States have length:', state_size)

    return env, brain, brain_name, action_size, state_size

def test(agent, env, brain, brain_name, n_episodes):
    if os.path.isfile(args.checkpoint):
        print("loading checkpoint for agent:", args.checkpoint)
        agent.qnetwork_local.load_state_dict(torch.load(args.checkpoint))

    # watch an untrained agent
    score = 0
    for i_episode in range(1, n_episodes+1):
        env_info = env.reset(train_mode=False)[brain_name]
        state = env_info.vector_observations[0]
        while True:
            action = agent.act(state)

            env_info = env.step(int(action))[brain_name]        # send the action to the environment
            state = env_info.vector_observations[0]   # get the next state
            reward = env_info.rewards[0]                   # get the reward
            done = env_info.local_done[0]                  # see if episode has finished

            score += reward
            if done:
                break

    print("score:", float(score)/float(n_episodes))

def train(agent, env, brain, brain_name, n_episodes, eps_start, eps_end, eps_decay):
    scores_window = deque(maxlen=100)  # last 100 scores
    eps = eps_start                    # initialize epsilon
    for i_episode in range(1, n_episodes+1):
        env_info = env.reset(train_mode=True)[brain_name]
        state = env_info.vector_observations[0]
        score = 0
        while True:
            # agent chooses an action
            action = agent.act(state, eps)

            # interact with the environment
            env_info = env.step(int(action))[brain_name]        # send the action to the environment
            next_state = env_info.vector_observations[0]   # get the next state
            reward = env_info.rewards[0]                   # get the reward
            done = env_info.local_done[0]                  # see if episode has finished

            # agent learns with the new experience
            agent.step(state, action, reward, next_state, done)

            state = next_state
            score += reward
            if done:
                break

        # verify if the goal has been achieved
        scores_window.append(score)       # save most recent score
        eps = max(eps_end, eps_decay*eps) # decrease epsilon
        print('\rEpisode {}\tAverage Score: {:.2f}'.format(i_episode, np.mean(scores_window)), end="")
        if i_episode % 100 == 0:
            print('\rEpisode {}\tAverage Score: {:.2f}'.format(i_episode, np.mean(scores_window)))
        if np.mean(scores_window)>=13.0:
            print('\nEnvironment solved in {:d} episodes!\tAverage Score: {:.2f}'.format(i_episode, np.mean(scores_window)))
            torch.save(agent.qnetwork_local.state_dict(), args.checkpoint)
            plot_rewards(scores_window)
            break

def plot_rewards(scores):
    fig = plt.figure()
    ax = fig.add_subplot(111)
    plt.plot(np.arange(len(scores)), scores)
    plt.ylabel('Score')
    plt.xlabel('Episode #')
    plt.savefig(args.reward_plot, transparent=True)
    plt.close()

if __name__ == '__main__':
    args = parser.parse_args()

    # environment
    env, brain, brain_name, action_size, state_size = create_environment()

    # replay memory
    if args.buffer_type == "prioritized":
        memory = PrioritizedReplayBuffer(action_size=action_size,
                                         state_size=state_size,
                                         buffer_size=args.buffer_size,
                                         batch_size=args.batch_size,
                                         update_buffer_steps=args.update_buffer_steps,
                                         seed=args.seed,
                                         alpha=args.alpha,
                                         beta=args.beta,
                                         beta_inc=args.beta_inc)
    else:
        memory = ReplayBuffer(action_size=action_size,
                                         state_size=state_size,
                                         buffer_size=args.buffer_size,
                                         batch_size=args.batch_size,
                                         seed=args.seed)

    # model
    network = DuelQNetwork if args.network == "linear_duel" else QNetwork

    # agent
    agent = Agent(algorithm=args.algorithm,
                  state_size=state_size,
                  action_size=action_size,
                  batch_size=args.batch_size,
                  seed=args.seed,
                  network=network,
                  memory=memory,
                  lr=args.lr,
                  gamma=args.gamma,
                  tau=args.tau,
                  update_network_steps=args.update_network_steps)

    if args.train:
        train(agent, env, brain, brain_name,
              n_episodes=args.train_episodes,
              eps_start=args.eps_start,
              eps_end=args.eps_end,
              eps_decay=args.eps_decay)
    else:
        test(agent, env, brain, brain_name, n_episodes=args.test_episodes)

    env.close()


'''Focal Related Utilities'''
import re
import warnings

from numba import prange
import numpy as np
import xarray as xr
from xarray import DataArray

from xrspatial.utils import ngjit
from xrspatial.convolution import convolve_2d

from typing import Optional

warnings.simplefilter('default')

# TODO: Make convolution more generic with numba first-class functions.

DEFAULT_UNIT = 'meter'
METER = 1
FOOT = 0.3048
KILOMETER = 1000
MILE = 1609.344
UNITS = {'meter': METER, 'meters': METER, 'm': METER,
         'feet': FOOT, 'foot': FOOT, 'ft': FOOT,
         'miles': MILE, 'mls': MILE, 'ml': MILE,
         'kilometer': KILOMETER, 'kilometers': KILOMETER, 'km': KILOMETER}

def _is_numeric(s):
    try:
        float(s)
        return True
    except ValueError:
        return False

def _to_meters(d, unit):
    return d * UNITS[unit]

def _get_distance(distance_str):
    # return distance in meters

    # spit string into numbers and text
    splits = [x for x in re.split(r'(-?\d*\.?\d+)', distance_str) if x != '']
    if len(splits) not in [1, 2]:
        raise ValueError("Invalid distance.")

    unit = DEFAULT_UNIT
    if len(splits) == 1:
        warnings.warn('Raster distance unit not provided. '
                      'Use meter as default.', Warning)
    elif len(splits) == 2:
        unit = splits[1]

    number = splits[0]
    if not _is_numeric(number):
        raise ValueError(
            "Invalid value.\n"
            "Distance should be a possitive numeric value.\n")

    distance = float(number)
    if distance <= 0:
        raise ValueError(
            "Invalid value.\n"
            "Distance should be a possitive.\n")

    unit = unit.lower()
    unit = unit.replace(' ', '')
    if unit not in UNITS:
        raise ValueError(
            "Invalid value.\n"
            "Distance unit should be one of the following: \n"
            "meter (meter, meters, m),\n"
            "kilometer (kilometer, kilometers, km),\n"
            "foot (foot, feet, ft),\n"
            "mile (mile, miles, ml, mls)")

    # convert distance to meters
    meters = _to_meters(distance, unit)
    return meters

def calc_cellsize(raster: xr.DataArray,
                  x: str = 'x',
                  y: str = 'y') -> tuple:
    """
    Calculates cell size of an array based on its attributes.
    Default = meters. If lat-lon units are converted to meters.

    Parameters:
    ----------
    raster: xarray.DataArray
        2D array of input values.
    x: str (Default = "x")
        Name of input x-axis.
    y: str (Default = "y")
        Name of input y-axis

    Returns:
    ----------
    cellsize_x: float
        Size of cells in x direction.
    cellsize_y: float
        Size of cells in y direction.

    Notes:
    ----------

    Examples:
    -----------
    Imports
    >>> import numpy as np
    >>> import xarray as xr
    >>> from xrspatial import focal

    Create Data Array
    >>> np.random.seed(0)
    >>> agg = xr.DataArray(np.random.rand(4,4),
                               dims = ["lat", "lon"])
    >>> height, width = nir_agg.shape
    >>> _lat = np.linspace(0, height - 1, height)
    >>> _lon = np.linspace(0, width - 1, width)
    >>> nir_agg["lat"] = _lat
    >>> nir_agg["lon"] = _lon

    Calculate Cell Size
    >>> focal.calc_cellsize(agg, 'lon', 'lat')
    (1, 1)
    """

    if 'unit' in raster.attrs:
        unit = raster.attrs['unit']
    else:
        unit = DEFAULT_UNIT
        warnings.warn('Raster distance unit not provided. '
                      'Use meter as default.', Warning)

    # TODO: check coordinate system
    #       if in lat-lon, need to convert to meter, lnglat_to_meters
    cellsize_x = raster.coords[x].data[1] - raster.coords[x].data[0]
    cellsize_y = raster.coords[y].data[1] - raster.coords[y].data[0]
    cellsize_x = _to_meters(cellsize_x, unit)
    cellsize_y = _to_meters(cellsize_y, unit)

    # When converting from lnglat_to_meters, could have negative cellsize in y
    return cellsize_x, np.abs(cellsize_y)

def _gen_ellipse_kernel(half_w, half_h):
    # x values of interest
    x = np.linspace(-half_w, half_w, 2 * half_w + 1)
    # y values of interest, as a "column" array
    y = np.linspace(-half_h, half_h, 2 * half_h + 1)[:, None]

    # True for points inside the ellipse
    # (x / a)^2 + (y / b)^2 <= 1, avoid division to avoid rounding issue
    ellipse = (x * half_h) ** 2 + (y * half_w) ** 2 <= (half_w * half_h) ** 2
    return ellipse.astype(float)

def _validate_kernel(kernel):
    """Validatetes that the kernel is a numpy array and has odd dimensions."""

    if not isinstance(kernel, np.ndarray):
        raise ValueError(
            "Received a custom kernel that is not a Numpy array.",
            """The kernel received was of type {} and needs to be of type `ndarray`
            """.format(type(kernel))
        )
    else:
        rows, cols = kernel.shape

    if (rows % 2 == 0 or cols % 2 == 0):
        raise ValueError(
            "Received custom kernel with improper dimensions.",
            """A custom kernel needs to have an odd shape, the
            supplied kernel has {} rows and {} columns.
            """.format(rows, cols)
        )

def circle_kernel(cellsize_x: int,
                  cellsize_y: int,
                  radius: int) -> np.array:
    """
    Generates a circular kernel of a given cellsize and radius.

    Parameters:
    ----------
    cellsize_x: int
        Cell size of output kernel in x direction.
    cellsize_y: int
        Cell size of output kernel in y direction.
    radius: int
        Radius of output kernel.

    Returns:
    ----------
    kernel: NumPy Array
        2D array where values of 1 indicate the kernel.

    Examples:
    ----------
        Imports
    >>> import numpy as np
    >>> import xarray as xr
    >>> from xrspatial import focal

        Create Kernels
    >>> focal.circle_kernel(1, 1, 3)
    array([[0., 0., 0., 1., 0., 0., 0.],
           [0., 1., 1., 1., 1., 1., 0.],
           [0., 1., 1., 1., 1., 1., 0.],
           [1., 1., 1., 1., 1., 1., 1.],
           [0., 1., 1., 1., 1., 1., 0.],
           [0., 1., 1., 1., 1., 1., 0.],
           [0., 0., 0., 1., 0., 0., 0.]])

    >>> focal.circle_kernel(1, 2, 3)
    array([[0., 0., 0., 1., 0., 0., 0.],
           [1., 1., 1., 1., 1., 1., 1.],
           [0., 0., 0., 1., 0., 0., 0.]])
    """

    # validate radius, convert radius to meters
    r = _get_distance(str(radius))

    kernel_half_w = int(r / cellsize_x)
    kernel_half_h = int(r / cellsize_y)

    kernel = _gen_ellipse_kernel(kernel_half_w, kernel_half_h)
    return kernel

def annulus_kernel(cellsize_x: int,
                   cellsize_y: int,
                   outer_radius: int,
                   inner_radius: int) -> np.array:
    """
    Generates a annulus (ring-shaped) kernel of a given cellsize and radius.

    Parameters:
    ----------
    cellsize_x: int
        Cell size of output kernel in x direction.
    cellsize_y: int
        Cell size of output kernel in y direction.
    outer_radius: int
        Outer ring radius of output kernel.
    inner_radius: int
        Inner circle radius of output kernel.

    Returns:
    ----------
    kernel: NumPy Array
        2D array of 0s and 1s where values of 1 indicate the kernel.

    Examples:
    ----------
    Imports
    >>> import numpy as np
    >>> import xarray as xr
    >>> from xrspatial import focal

    Create Kernels
    >>> focal.annulus_kernel(1, 1, 3, 1)
    array([[0., 0., 0., 1., 0., 0., 0.],
           [0., 1., 1., 1., 1., 1., 0.],
           [0., 1., 1., 0., 1., 1., 0.],
           [1., 1., 0., 0., 0., 1., 1.],
           [0., 1., 1., 0., 1., 1., 0.],
           [0., 1., 1., 1., 1., 1., 0.],
           [0., 0., 0., 1., 0., 0., 0.]])

    >>> focal.annulus_kernel(1, 2, 5, 2)
    array([[0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0.],
           [0., 1., 1., 1., 1., 0., 1., 1., 1., 1., 0.],
           [1., 1., 1., 0., 0., 0., 0., 0., 1., 1., 1.],
           [0., 1., 1., 1., 1., 0., 1., 1., 1., 1., 0.],
           [0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0.]])
    """

    # validate radii, convert to meters
    r2 = _get_distance(str(outer_radius))
    r1 = _get_distance(str(inner_radius))

    # Validate that outer radius is indeed outer radius
    if r2 > r1:
        r_outer = r2
        r_inner = r1
    else:
        r_outer = r1
        r_inner = r2

    if r_outer - r_inner < np.sqrt((cellsize_x / 2)**2 +
                                   (cellsize_y / 2)**2):
        warnings.warn('Annulus radii are closer than cellsize distance.',
                      Warning)

    # Get the two circular kernels for the annulus
    kernel_outer = circle_kernel(cellsize_x, cellsize_y, outer_radius)
    kernel_inner = circle_kernel(cellsize_x, cellsize_y, inner_radius)

    # Need to pad kernel_inner to get it the same shape and centered
    # in kernel_outer
    pad_vals = np.array(kernel_outer.shape) - np.array(kernel_inner.shape)
    pad_kernel = np.pad(kernel_inner,
                        # Pad ((before_rows, after_rows),
                        #      (before_cols, after_cols))
                        pad_width=((pad_vals[0] // 2, pad_vals[0] // 2),
                                   (pad_vals[1] // 2, pad_vals[1] // 2)),
                        mode='constant',
                        constant_values=0)
    # Get annulus by subtracting inner from outer
    kernel = kernel_outer - pad_kernel
    return kernel

def custom_kernel(kernel):
    """Validates a custom kernel. If the kernel is valid, returns itself."""
    _validate_kernel(kernel)
    return kernel

@ngjit
def _mean(data, excludes):
    out = np.zeros_like(data)
    rows, cols = data.shape
    for y in range(1, rows - 1):
        for x in range(1, cols - 1):

            exclude = False
            for ex in excludes:
                if data[y, x] == ex:
                    exclude = True
                    break

            if not exclude:
                a, b, c, d, e, f, g, h, i = [data[y - 1, x - 1],
                                             data[y, x - 1],
                                             data[y + 1, x - 1],
                                             data[y - 1, x], data[y, x],
                                             data[y + 1, x],
                                             data[y - 1, x + 1],
                                             data[y, x + 1],
                                             data[y + 1, x + 1]]
                out[y, x] = (a + b + c + d + e + f + g + h + i) / 9
            else:
                out[y, x] = data[y, x]
    return out

def mean(agg: xr.DataArray,
         passes: int = 1,
         excludes: list = [np.nan],
         name: Optional[str] = 'mean') -> xr.DataArray:
    """
    Returns Mean filtered array using a 3x3 window.

    Parameters:
    ----------
    agg : xarray.DataArray
        2D array of input values to be filtered.
    passes : int (default = 1)
        Number of times to run mean.
    name : str, optional (default = 'mean')
        output xr.DataArray.name property

    Returns:
    ----------
    data: xarray.DataArray
        2D array of filtered values.

    Examples:
    ----------
    Imports
    >>> import numpy as np
    >>> import xarray as xr
    >>> from xrspatial import focal

    Create Data Array
    >>> np.random.seed(0)
    >>> agg = xr.DataArray(np.random.rand(4,4), dims = ["lat", "lon"])
    >>> height, width = nir_agg.shape
    >>> _lat = np.linspace(0, height - 1, height)
    >>> _lon = np.linspace(0, width - 1, width)
    >>> nir_agg["lat"] = _lat
    >>> nir_agg["lon"] = _lon

    Calculate Mean
    >>> focal.mean(agg)
    array([[0.5488135 , 0.71518937, 0.60276338, 0.54488318],
           [0.4236548 , 0.64589411, 0.43758721, 0.891773  ],
           [0.96366276, 0.38344152, 0.79172504, 0.52889492],
           [0.56804456, 0.92559664, 0.07103606, 0.0871293 ]])
    """

    out = None
    for i in range(passes):
        if out is None:
            out = _mean(agg.data, tuple(excludes))
        else:
            out = _mean(out, tuple(excludes))

    return DataArray(out, name=name, dims=agg.dims,
                     coords=agg.coords, attrs=agg.attrs)

@ngjit
def calc_mean(array):
    return np.nanmean(array)

@ngjit
def calc_sum(array):
    return np.nansum(array)

@ngjit
def upper_bound_p_value(zscore):
    if abs(zscore) >= 2.33:
        return 0.0099
    if abs(zscore) >= 1.65:
        return 0.0495
    if abs(zscore) >= 1.29:
        return 0.0985
    return 1

@ngjit
def _hot_cold(zscore):
    if zscore > 0:
        return 1
    if zscore < 0:
        return -1
    return 0

@ngjit
def _confidence(zscore):
    p_value = upper_bound_p_value(zscore)
    if abs(zscore) > 2.58 and p_value < 0.01:
        return 99
    if abs(zscore) > 1.96 and p_value < 0.05:
        return 95
    if abs(zscore) > 1.65 and p_value < 0.1:
        return 90
    return 0

@ngjit
def _apply(data, kernel_array, func):
    out = np.zeros_like(data)
    rows, cols = data.shape
    krows, kcols = kernel_array.shape
    hrows, hcols = int(krows / 2), int(kcols / 2)
    kernel_values = np.zeros_like(kernel_array, dtype=data.dtype)

    for y in prange(rows):
        for x in prange(cols):
            # kernel values are all nans at the beginning of each step
            kernel_values.fill(np.nan)
            for ky in range(y - hrows, y + hrows + 1):
                for kx in range(x - hcols, x + hcols + 1):
                    if ky >= 0 and kx >= 0:
                        if ky >= 0 and ky < rows and kx >= 0 and kx < cols:
                            kyidx, kxidx = ky - (y - hrows), kx - (x - hcols)
                            if kernel_array[kyidx, kxidx] == 1:
                                kernel_values[kyidx, kxidx] = data[ky, kx]
            out[y, x] = func(kernel_values)
    return out

def apply(raster, kernel, x='x', y='y', func=calc_mean):
    """
    """

    # validate raster
    if not isinstance(raster, DataArray):
        raise TypeError("`raster` must be instance of DataArray")

    if raster.ndim != 2:
        raise ValueError("`raster` must be 2D")

    if not (issubclass(raster.values.dtype.type, np.integer) or
            issubclass(raster.values.dtype.type, np.floating)):
        raise ValueError(
            "`raster` must be an array of integers or float")

    raster_dims = raster.dims
    if raster_dims != (y, x):
        raise ValueError("raster.coords should be named as coordinates:"
                         "({0}, {1})".format(y, x))

    # Validate the kernel
    _validate_kernel(kernel)

    # apply kernel to raster values
    out = _apply(raster.values.astype(float), kernel, func)

    result = DataArray(out,
                       coords=raster.coords,
                       dims=raster.dims,
                       attrs=raster.attrs)

    return result

@ngjit
def _hotspots(z_array):
    out = np.zeros_like(z_array, dtype=np.int8)
    rows, cols = z_array.shape
    for y in prange(rows):
        for x in prange(cols):
            out[y, x] = _hot_cold(z_array[y, x]) * _confidence(z_array[y, x])
    return out

def hotspots(raster: xr.DataArray,
             kernel: xr.DataArray,
             x: Optional[str] = 'x',
             y: Optional[str] = 'y') -> xr.DataArray:
    """
    Identify statistically significant hot spots and cold spots in an input
    raster. To be a statistically significant hot spot, a feature will have a
    high value and be surrounded by other features with high values as well.
    Neighborhood of a feature defined by the input kernel, which currently
    support a shape of circle, annulus, or custom kernel.

    The result should be a raster with the following 7 values:
         90 for 90% confidence high value cluster
         95 for 95% confidence high value cluster
         99 for 99% confidence high value cluster
        -90 for 90% confidence low value cluster
        -95 for 95% confidence low value cluster
        -99 for 99% confidence low value cluster
         0 for no significance

    Parameters:
    ----------
    raster: xarray.DataArray
        2D Input raster image with shape = (height, width).
    kernel: Numpy Array
        2D array where values of 1 indicate the kernel.

    Returns:
    ----------
    xarray.DataArray
        2D array of hotspots with values indicating confidence level.

    Examples:
    ----------
        Imports
    >>> import numpy as np
    >>> import xarray as xr
    >>> from xrspatial import focal

    Create Data Array
    >>> agg = xr.DataArray(np.array([[0, 0, 0, 0, 0, 0, 0],
    >>>                              [0, 0, 0, 0, 0, 0, 0],
    >>>                              [0, 0, 10, 10, 10, 0, 0],
    >>>                              [0, 0, 10, 10, 10, 0, 0],
    >>>                              [0, 0, 10, 10, 10, 0, 0],
    >>>                              [0, 0, 0, 0, 0, 0, 0],
    >>>                              [0, 0, 0, 0, 0, 0, 0]]),
    >>>                              dims = ["lat", "lon"])
    >>> height, width = agg.shape
    >>> _lon = np.linspace(0, width - 1, width)
    >>> _lat = np.linspace(0, height - 1, height)
    >>> agg["lon"] = _lon
    >>> agg["lat"] = _lat

        Create Kernel
    >>> kernel = focal.circle_kernel(1, 1, 1)

        Create Hotspot Data Array
    >>> focal.hotspots(agg, kernel, x = 'lon', y = 'lat')
    <xarray.DataArray (lat: 7, lon: 7)>
    array([[ 0,  0,  0,  0,  0,  0,  0],
           [ 0,  0,  0,  0,  0,  0,  0],
           [ 0,  0,  0,  0,  0,  0,  0],
           [ 0,  0,  0, 95,  0,  0,  0],
           [ 0,  0,  0,  0,  0,  0,  0],
           [ 0,  0,  0,  0,  0,  0,  0],
           [ 0,  0,  0,  0,  0,  0,  0]], dtype=int8)
    Coordinates:
      * lon      (lon) float64 0.0 1.0 2.0 3.0 4.0 5.0 6.0
      * lat      (lat) float64 0.0 1.0 2.0 3.0 4.0 5.0 6.0
    """

    # validate raster
    if not isinstance(raster, DataArray):
        raise TypeError("`raster` must be instance of DataArray")

    if raster.ndim != 2:
        raise ValueError("`raster` must be 2D")

    if not (issubclass(raster.values.dtype.type, np.integer) or
            issubclass(raster.values.dtype.type, np.floating)):
        raise ValueError(
            "`raster` must be an array of integers or float")

    raster_dims = raster.dims
    if raster_dims != (y, x):
        raise ValueError("raster.coords should be named as coordinates:"
                         "({0}, {1})".format(y, x))

    # apply kernel to raster values
    mean_array = convolve_2d(raster.values, kernel / kernel.sum(), pad=True)

    # calculate z-scores
    global_mean = np.nanmean(raster.values)
    global_std = np.nanstd(raster.values)
    if global_std == 0:
        raise ZeroDivisionError("Standard deviation "
                                "of the input raster values is 0.")
    z_array = (mean_array - global_mean) / global_std

    out = _hotspots(z_array)

    result = DataArray(out,
                       coords=raster.coords,
                       dims=raster.dims,
                       attrs=raster.attrs)

    return result


from pathlib import Path

with open(Path(__file__).parent / "version.py") as _version_file:
    __version__ = _version_file.read().strip()

ROOT_DIR = Path(__file__).resolve().parent

# environment variable used to specify a testing environment
TEST_ENV_VAR="TEST"