from enum import Enum
import numpy as np

class EncodingType(Enum):
    HAMMING = 1
    SIMPLE_PARITY_BIT = 2
    CONVOLUTIONAL = 3

class Generator:
    """
    Konstruktor klasy Generatora
    :param data_size: Określa wielkość generowanych danych, domyślnie 10 bitów
    :param data_type: Określa rodzaj kodowania informacji, domyślnie kodowanie Hamminga
    """
    def __init__(self, data_size=10, data_type=EncodingType.CONVOLUTIONAL):
        self.data_type = data_type
        self.__data_size = data_size  # dlugosc danych
        self.__data_buffer = []  # bufor, w ktorym znajduja sie wygenerowane dane i gotowe do dalszej pracy
        self.data_array = None  # dane, ktore zostaly wygenerowane (i zakodowane)
        self.__data_encoded = []  # zakodowane dane

    """
    Metoda ustawiająca długość tworzonych danych
    :param size: Określa nową długość dla data_array
    """
    def set_signal_length(self, size):
        self.__data_size = size

    """
    Metoda ustawiająca nowy typ kodowania
    :param data_type: Zmienna typu enum ustawiająca nowy typ kodowania
    """
    def set_encode(self, data_type: EncodingType):
        self.data_type = data_type

    """
    Metoda generująca dane do zakodowania. Dane mają rozkład normalny (gaussowski), z wartością średnią = 0.5
    odchyleniem standardowym = 0.01
    """
    def __generate_data(self, data_size):
        data = np.zeros(data_size)
        random_generator = np.random.RandomState()
        data = random_generator.normal(loc=0.5, scale=0.01, size=data_size)
        data = np.where(data >= 0.5, 1, 0)
        return data.tolist()

    """
    Prosta metoda sprawdzajaca warunek czy generowac dodatkowe dane.
    Stworzona tylko dla czytelności kodu
    :param data_length_to_generate: Zmienna okreslajaca liczbe bitow do pobrania -> ustalane w __get_data()
    :return: Wartość true lub false -> generowac dane, czy nie
    """
    def __should_generate(self, data_length_to_generate):
        if len(self.__data_buffer) < data_length_to_generate:
            return True
        return False

    """
    Metoda odpowiadająca za losowanie dodatkowych bitow do bufora
    :param data_length_to_generate: Zmienna okreslajaca liczbe bitow do pobrania -> ustalane w __get_data()
    """
    def __append_extra_data(self, data_length_to_generate):
        old_data = self.__data_buffer
        new_data_count = data_length_to_generate - len(self.__data_buffer)
        self.__data_buffer = old_data + self.__generate_data(new_data_count)
        self.__data_size = len(self.__data_buffer)

    """
    Metoda odpowiedzialna za aktualizowanie stanu bufora.
    Przesuniecie informacji w przypadku gdy data_length_to_generate jest mniejsza od stanu bufora
    Wyzerowanie stanu bufora w przeciwnym razie
    :param data_length_to_generate: Zmienna okreslajaca liczbe bitow do pobrania -> ustalane w __get_data()
    """
    def __update_data_buffer(self, data_length_to_generate):
        if data_length_to_generate < len(self.__data_buffer):
            self.data_array = self.__data_buffer[:data_length_to_generate]
            self.__data_buffer = self.__data_buffer[data_length_to_generate:]
            self.__data_size = len(self.data_array)
        else:
            self.data_array = self.__data_buffer
            self.__data_buffer = []

    """
    Metoda zbierająca w sobie 3 powyższe metody.
    Używana w każdej metodzie kodowania informacji
    Przygotowuje bufor i dane(ktore maja byc kodowane) do dzialania
    :param data_length_to_generate: Zmienna okreslajaca liczbe bitow do pobrania -> ustalane w __get_data()
    """
    def __prepare_buffer(self, data_length_to_generate):
        self.__data_encoded = []

        if data_length_to_generate > 0:
            if self.__should_generate(data_length_to_generate):
                self.__append_extra_data(data_length_to_generate)

            self.__update_data_buffer(data_length_to_generate)

    """
    Metoda, ktora wstawia liste podana w parametrze do bufora
    :param data_array: Zmienna, ktora powinna byc typu list. W niej przekazywane sa bity do bufora
    """
    def set_data(self, data_array: []):
        self.__data_buffer = data_array
        self.__data_size = len(self.__data_buffer)

    """
    Metoda, ktora koduje wygenerowane bity danych wg okreslonego kodowania
    :return bits: Lista zakodowanych bitow
    """
    def get_data(self, data_length_to_generate=10):
        bits = []

        if self.data_type == EncodingType.HAMMING:
            self.__create_hamming_code(data_length_to_generate)
            bits.extend(self.__data_encoded)
        elif self.data_type == EncodingType.CONVOLUTIONAL:
            self.__create_convolutional_code(data_length_to_generate)
            bits.extend(self.__data_encoded)
        else:
            self.__create_simple_parity_bit(data_length_to_generate)
            bits.extend(self.__data_encoded)
        return bits

    # region SIMPLE_PARITY_BIT
    """
    Metoda kodujaca dane z wykorzystaniem dodatkowego bitu parzystosci
    """
    def __create_simple_parity_bit(self, data_length_to_generate):

        self.__prepare_buffer(data_length_to_generate)

        self.__data_encoded += self.data_array
        parity_count = 0
        for i in self.data_array:
            if i == 1:
                parity_count += 1

        self.__data_encoded.insert(0, parity_count % 2)
    # endregion

    # region CONVOLUTIONAL

    # #######################
    # Kodowanie splotowe (n,k,m) = (2,1,3), rate_code = 0.5 -> na każdy bit wejścia przypadają dwa bity wyjścia
    # n - ilosc bitow wyjsciowych
    # k - ilosc bitow wejsciowych
    # m - ilosc komorek rejestru przesuwnego
    # wielomiany tworzące: g1 = (1,1,1), g2 = (1,1,0) -> zgodnie z wielomianami Busganga dla rate_code = 0.5
    # #######################

    """
    Metoda kodująca dane wykorzystujac kod splotowy
    """
    def __create_convolutional_code(self, data_length_to_generate):

        self.__prepare_buffer(data_length_to_generate)

        shift_register = [0] * 3  # rozmiar rejestru to 3, stan poczatkowy to (0,0,0)

        for i in range(self.__data_size - 1, -1, -1):
            self.__shift_right(self.data_array[i], shift_register)
            self.__calculate_modulos(shift_register)

    """
    Metoda wyliczajaca sumy modulo zgodnie z wielomianami tworzacymi.
    Tworzone sa tutaj zakodowane dane.
    :param shift_register: Referencja do software'owego rejestru przesuwnego
    """
    def __calculate_modulos(self, shift_register):
        modulo_1 = (shift_register[0] + shift_register[1] + shift_register[2]) % 2
        modulo_2 = (shift_register[0] + shift_register[1]) % 2
        self.__data_encoded.append(modulo_1)
        self.__data_encoded.append(modulo_2)

    """
    Metoda przesuwająca software'owy rejestr przesuwny w prawo i dodaje nowy bit do najmłodszej komorki rejestru
    :param next_input: Nowy bit do dodania do rejestru
    :param shift_register: Referencja do software'owego rejestru przesuwnego
    """
    def __shift_right(self, next_input, shift_register):
        shift_register[2] = shift_register[1]
        shift_register[1] = shift_register[0]
        shift_register[0] = next_input

    # endregion

    # region HAMMING
    """
    Metoda kodujaca dane zgodnie z kodowaniem Hamminga
    """
    def __create_hamming_code(self, data_length_to_generate):

        self.__prepare_buffer(data_length_to_generate)

        self.data_array = self.data_array[::-1]  # dla wygody obliczen
        positions_indexes = self.__generate_bin_position()
        parity_count, parity_bits_position = self.__parity_bits_positions()

        new_size = parity_count + self.__data_size

        self.__allocate_parity_bits(new_size, parity_count, parity_bits_position, positions_indexes)

        self.__count_parity_hamming(parity_count, new_size, positions_indexes, parity_bits_position)

    """
    Metoda zliczająca ilość bitów odpowiednio dla danego bitu parzystości.
    Parzysta ilość = 0, nieparzysta = 1
    :param parity_count: Ilość bitów parzystości dla danego zbioru danych
    :param encoding_size: Długość danych po zakodowaniu
    :param positions_indexes: Pomocnicza lista przetrzymująca binarne pozycje w liście -> nie trzeba się przejmować indeksowaniem listy od 0
    :param parity_indexes: Pomocnicza lista przetrzymująca binarne pozycje bitów parzystości
    """
    def __count_parity_hamming(self, parity_count, encoding_size, positions_indexes, parity_indexes):
        for i in range(parity_count):
            parity_counter = 0
            j = 0
            for j in range(i + j, encoding_size):
                single_sing = positions_indexes[j]  # tu znajduje sie jeszcze cala liczba, np. 101
                if len(single_sing) >= i + 1:
                    single_sing = single_sing[-(i + 1)]
                    if single_sing == '1':
                        if self.__data_encoded[j] == 1:
                            parity_counter += 1
            dec_index = int(parity_indexes[i], 2) - 1  # pozycja bitu parzystosci w zakodowanych danych
            self.__data_encoded[dec_index] = parity_counter % 2

        self.data_array = self.data_array[::-1]
        self.__data_encoded.reverse()  # dane byly obliczane w sposob, ze pozycja najbardziej na lewo byla najmlodsza (o indeksie 0), a powinna byc pozycja najstarsza

    """
    Metoda wyliczająca binarne pozycje bitów parzystości w zakodowanym zestawie danych.
    :returns r, parity_positions: Odpowiednio liczba bitów parzystości i lista pozycji tych bitów zapisana binarnie
    """
    def __parity_bits_positions(self):
        r = 0
        parity_positions = []
        while pow(2, r) < self.__data_size + 1 + r:  # obliczenie ilosci redundantnych bitow
            r += 1

        for i in range(0, r):  # zapisanie binarnie pozycji redundantnych bitow
            parity_positions.append(self.__bin_value(pow(2, i)))

        return r, parity_positions

    """
    Metoda generujaca liste kolejnych pozycji zapisanych binarnie
    :return positions_indexes: Lista wszystkich pozycji zapisanych binarnie
    """
    def __generate_bin_position(self):
        positions_indexes = []
        for i in range(self.__data_size):
            positions_indexes.append(self.__bin_value(i + 1))
        return positions_indexes

    """
    Metoda pomocnicza do generowania wartosci binarnej z pominięciem prefiksu '0b'
    :param dec_value: Wartość decymalna, która ma być zmieniona na binarny ekwiwalent
    :return: Wartość binarna zmiennej dec_value
    """
    def __bin_value(self, dec_value):
        return bin(dec_value)[2:]

    """
    Metoda wstawiająca w miejsce bitów parzystości wartość -1 i przepisująca wygenerowane dane
    :param new_size: Rozmiar danych po zakodowaniu
    :param parity_count: Ilość bitow parzystości
    :param parity_bits_positions: Pozycje bitów parzystości zapisane binarnie
    :param positions_indexes: Pozycje listy danych zapisanych binarnie
    """
    def __allocate_parity_bits(self, new_size, parity_count, parity_bits_position, positions_indexes):
        j = 0
        raw_data = 0
        # wstawienie bitow parzystosci na odpowiednie miejsca i nadanie im wartosci -1
        for i in range(new_size):
            if j < parity_count and self.__bin_value(i + 1) == parity_bits_position[j]:
                self.__data_encoded.append(-1)
                j += 1
            else:
                self.__data_encoded.append(self.data_array[raw_data])
                raw_data += 1
            if i > self.__data_size:
                positions_indexes.append(self.__bin_value(i))  # dodanie brakujacych indeksow pozycji
        positions_indexes.append(self.__bin_value(new_size))  # dodanie ostatniego brakujacego indeksu pozycji
    # endregion

if __name__ == '__main__':
    # przykładowa implementacja
    generator = Generator()
    generator.set_data([1,0,1,1,1])
    encoded_bits = generator.get_data(5)
    print('Surowe bity:')
    print(generator.data_array)
    print('Zakodowana informacja:')
    print(encoded_bits)


#   minnstateUtilites.py
#   4/30/2020
#   Brian R Steele
#
#   information about Minnstate campuses and schools to assist
#	with related classes.
#

# Dictionary with campus and school information
# Don't repeat yourself.
system_info = {
    'Alexandria Techncial and Community College' : ['ATCC', '0203', 'https://www.alextech.edu', '', '', '', ''],
    'Anoka Technical College' : ['ATC', '0202', 'https://www.anokatech.edu', '', '', '', ''],
    'Anoka-Ramsey Community College' : ['ARCC', '0152', 'http://www.anokaramsey.edu', '', '', '', ''],
    'Bemidji State University' : ['BSU', '0070', 'https://www.bemidjistate.edu', '', '', '', ''],
    'Central Lakes College' : ['CLC', '0301', 'http://www.clcmn.edu', '', '', '', ''],
    'Century College' : ['Century', '0304', 'https://www.century.edu', '', '', '', ''],
    'Dakota County Technical College' : ['DCTC', '0211', 'https://www.dctc.edu', '', '', '', ''],
    'Fond du Lac Tribal and Community College' : ['FDLTCC', '0163', 'https://fdltcc.edu', '', '', '', ''],
    'Hennepin Technical College' : ['HTC', '0204', 'https://www.hennepintech.edu', '', '', '', ''],
    'Hibbing Community College' : ['HCC', '0310', 'https://hibbing.edu', '', '', '', ''],
    'Inver Hills Community College' : ['IHCC', '0157', 'https://www.inverhills.edu', '', '', '', ''],
    'Itasca Community College' : ['ICC', '0144', 'https://www.itascacc.edu', '', '', '', ''],
    'Lake Superior College' : ['LSC', '0302', 'https://www.lsc.edu', '', '', '', ''],
    'Mesabi Range College' : ['MRC', '0411', 'https://www.mesabirange.edu', '', '', '', ''],
    'Metropolitan State University' : ['Metro State', '0076', 'https://www.metrostate.edu', '', '', '', ''],
    'Minneapolis Community and Technical College' : ['MCTC', '0305', 'https://www.minneapolis.edu', '', '', '', ''],
    'Minnesota State Community College - Southeast Technical' : ['MSCS', '0213', 'https://www.minneapolis.edu', '', '', '', ''],
    'Minnesota State Community and Technical College' : ['MSCTC', '0142', 'https://www.minnesota.edu', '', '', '', ''],
    'Minnesota State University, Moorhead' : ['MSUM', '0072', 'https://www.mnstate.edu', '', '', '', ''],
    'Minnesota State University, Mankato' : ['MNSU', '0071', 'https://mankato.mnsu.edu', '', '', '', ''],
    'Minnesota West Community and Technical College' : ['MWCTC', '0209', 'https://www.mnwest.edu', '', '', '', ''],
    'Normandale Community College' : ['NCC', '0156', 'http://www.normandale.edu', '', '', '', ''],
    'North Hennepin Community College' : ['NHCC', '0153', 'https://nhcc.edu', '', '', '', ''],
    'Northland Community and Technical College' : ['NCTC', '0303', 'http://www.northlandcollege.edu', '', '', '', ''],
    'Northwest Technical College' : ['NTC', '0263', 'https://www.ntcmn.edu', '', '', '', ''],
    'Pine Technical and Community College' : ['PTCC', '0205', 'http://www.pine.edu', '', '', '', ''],
    'Rainy River Community College' : ['RRCC', '0155', 'https://www.rainyriver.edu', '', '', '', ''],
    'Ridgewater College' : ['RC', '0308', 'https://www.ridgewater.edu', '', '', '', ''],
    'Riverland Community College' : ['RCC', '0307', 'https://www.riverland.edu', '', '', '', ''],
    'Rochester Community and Technical College' : ['RCTC', '0306', 'https://www.rctc.edu', '', '', '', ''],
    'Saint Paul College' : ['SPC', '0206', 'https://www.saintpaul.edu', '', '', '', ''],
    'South Central College' : ['SCC', '0309', 'http://southcentral.edu', '', '', '', ''],
    'Southwest Minnesota State University' : ['SMSU', '0075', 'https://www.smsu.edu', '', '', '', ''],
    'St. Cloud State University' : ['SCSU', '0073', 'https://www.stcloudstate.edu', '', '', '', ''],
    'St. Cloud Technical and Community College' : ['SCTCC', '0208', 'https://www.sctcc.edu', '', '', '', ''],
    'Vermillion Community College' : ['VCC', '0147', 'https://www.vcc.edu', '', '', '', ''],
    'Winona State University' : ['WSU', '0074', 'https://www.winona.edu', '', '', '', ''],
    }

def populateCampusInformation(campus_instance):
   """
       Populates a campus object with campus information

       :param campus_instance: a campus object
   """

   if campus_instance.name in system_info.keys():

       if (campus_instance.street_address == None):
           campus_instance.street_address = system_info[campus_instance.name][3]

       if (campus_instance.city == None):
           campus_instance.city = system_info[campus_instance.name][4]

       if (campus_instance.state == None):
           campus_instance.state = system_info[campus_instance.name][5]

       if (campus_instance.postal_code == None):
           campus_instance.postal_code = system_info[campus_instance.name][6]

def populateSchoolInformation(school_instance):
   """
       Populates a school object with school information

       :param school_instance: a school object
   """

   if school_instance.full_name in system_info.keys():

       if(school_instance.abbreviation == None):
           school_instance.abbreviation = system_info[school_instance.full_name][0]

       if(school_instance.id_number == None):
           school_instance.id_number = system_info[school_instance.full_name][1]

       if(school_instance.url == None):
           school_instance.url = system_info[school_instance.full_name][2]

       if (school_instance.street_address == None):
           school_instance.street_address = system_info[school_instance.full_name][3]

       if (school_instance.city == None):
           school_instance.city = system_info[school_instance.full_name][4]

       if (school_instance.state == None):
           school_instance.state = system_info[school_instance.full_name][5]

       if (school_instance.postal_code == None):
           school_instance.postal_code = system_info[school_instance.full_name][6]


from commonAuth import *
logger = getLogger("{}/splunk_scripted_authentication_okta.log".format(logPath), "okta")

if sys.version_info < (3,0):
    logger.error("Python 2 has been deprecated. Use Python 3 to execute this script instead.")

import requests
import json
from urllib.parse import quote
# This is for getting SAML user information, it is an alternative to using SAML attribute
# query requests (AQR) which Okta does not support.
#
# Provide Okta API key credentials and base url in the authentication.conf
# file or using the Splunk Web UI
# (Settings > Authentication Methods > SAML Configuration > Authentication Extensions)
# and use the Okta API to extract user information.
#
# In authentication.conf, configure the 'scriptSecureArguments' setting to
# "apiKey:<your Okta API key>" and "baseUrl:<your Okta url>. For example:
#
# scriptSecureArguments = apiKey:<your Okta API key string>,baseUrl:<your Okta url>
#
# After you restart the Splunk platform, the platform encrypts your Okta credentials.
# For more information about Splunk platform configuration files, search the
# Splunk documentation for "about configuration files".
#
# In Splunk Web UI under Authentication Extensions > Script Secure Arguments:
# key = apiKey, value = <your Okta API key string>
# key = baseUrl, value =<your Okta url>
request_timeout = 10
errMsg = ""
def getUserInfo(args):
    username = args['username']

    if not username:
        errMsg = "Username is empty. Not executing API call"
        logger.error(errMsg)
        return FAILED + " " + ERROR_MSG + errMsg
    logger.info("Running getUserInfo() for username={}".format(username))

    # Extracting base url and API key from authentication.conf under scriptSecureArguments
    BASE_URL = args['baseUrl']
    API_KEY = args['apiKey']
    API_KEY_HEADER = 'SSWS ' + API_KEY
    # create persistent connection
    session = requests.Session()
    session.headers = {'Accept': 'application/json', 'Content-Type': 'application/json', 'Authorization': API_KEY_HEADER}
    OKTA_USER_SEARCH_INPUT = "oktaUserSearch"
    encoded_username = quote(username)
    if OKTA_USER_SEARCH_INPUT not in args:
        # By default use the email as the attribute to query user information from Okta.
        # Typically Okta APIs can be quieried directly using the email attribute.
        # For example, for a customer Acme and username "acme@example.com" the Okta
        # URL will look something like
        # https://acme.okta.com/api/v1/users/<Base64UrlEncode(acme@example.com)>
        usernameUrl = BASE_URL + '/api/v1/users/' + encoded_username
        groupsUrl = usernameUrl + '/groups'

        logger.info("Okta username url is {}".format(usernameUrl))

        usernameResponse = session.get(usernameUrl, timeout=request_timeout)
        if usernameResponse.status_code != 200:
            errMsg = "Failed to get user info for username={} with status={} and response={}".format(username, usernameResponse.status_code, usernameResponse.text)
            logger.error(errMsg)
            if usernameResponse.status_code == 401:
                errMsg = "It appears your baseUrl and/or apiKey are incorrect. Check your Okta instance URL " \
                    "and search the Okta documentation to retrieve the apiKey: " \
                    "\"Create the token | Okta Developer\""
                logger.warning(errMsg)
            elif usernameResponse.status_code == 404:
                errMsg = "The user you are querying (username={}) does not exist".format(username)
                logger.error(errMsg)
            return FAILED + " " + ERROR_MSG + errMsg
        try:
            nameAttributes = json.loads(usernameResponse.text)
        except Exception as e:
            errMsg = "Failed to parse user info for username={} with error={}".format(username, str(e))
            logger.error(errMsg)
            return FAILED + " " + ERROR_MSG + errMsg
        if 'status' not in nameAttributes:
            errMsg = "Failed to parse user info for username={}, 'status' not present in response output: {}".format(username, usernameResponse.text)
            logger.error(errMsg)
            return FAILED + " " + ERROR_MSG + errMsg
        status = nameAttributes['status']
    else:
        # In rare cases (like when Okta has been paired with a customer's Active Directory) the email may *not*
        # used directly to lookup user information. In such cases an AD attribute e.g (samAccountName) is needed.
        # More info https://help.okta.com/en/prod/Content/Topics/Directory/Directory_AD_Field_Mappings.htm
        # In such cases we allow the customer to construct a search based on whatever attribute they have choosen.
        # Okta's user APIs are queried by construncting a search with the unique user identifier passed in as a
        # argument to the script. This can be done directly through the SAML configuration page or
        # through authentication.conf
        # if the user has passed in a custom search attribute, use that instead of the email.
        # API Ref: https://developer.okta.com/docs/reference/api/users/#list-users-with-search
        # Note that this search attribute is passed in as a key:value pair through the scripted inputs section.
        # E.g if  we want to search based on 'samAccountName' we will pass in the following input to the script
        #
        # search=profile.samAccountName eq <attr-to-be-queried>
        #
        # Currently, only one attribute is allowed as an input to search.
        # https://acme.okta.com/api/v1/users/?<Base64UrlEncode(search profile.samAccountName eq <username>)>
        logger.info('Using attribute={} to do a lookup for value={}'.format(args[OKTA_USER_SEARCH_INPUT], encoded_username))
        query = '{} eq \"{}\"'.format(args[OKTA_USER_SEARCH_INPUT], username)
        searchUrl = '/api/v1/users/?search=' + quote(query)
        usernameUrl = BASE_URL + searchUrl
        logger.info("Okta search url is {}".format(usernameUrl))
        usernameResponse = session.get(usernameUrl, timeout=request_timeout)
        if usernameResponse.status_code != 200:
            errMsg = "Failed to get user info for username={} with status={} and response={}".format(username, usernameResponse.status_code, usernameResponse.text)
            logger.error(errMsg)
            if usernameResponse.status_code == 400:
                errMsg = "It appears you are using a search parameter that does not exist. " \
                    "Search the Okta documentation for examples: " \
                    "\"Okta Users API - Okta Developer\" / \"List Users with Search\""
                logger.error(errMsg)
            elif usernameResponse.status_code == 401:
                errMsg = "It appears your baseUrl and/or apiKey are incorrect. Check your Okta instance URL " \
                    "and search the Okta documentation to retrieve the apiKey: " \
                    "\"Create the token | Okta Developer\""
                logger.warning(errMsg)
            elif usernameResponse.status_code == 404:
                errMsg = "The user you are querying ({}) does not exist".format(username)
                logger.error(errMsg)
            return FAILED + " " + ERROR_MSG + errMsg
        try:
            nameAttributes = json.loads(usernameResponse.text)
        except Exception as e:
            errMsg = "Failed to parse user info for username={} with error={}".format(username, str(e))
            logger.error(errMsg)
            return FAILED + " " + ERROR_MSG + errMsg
        if not len(nameAttributes):
            errMsg = "Search query returned an empty response using attribute={} to do a lookup for value={}".format(args[OKTA_USER_SEARCH_INPUT], encoded_username)
            logger.error(errMsg)
            return FAILED + " " + ERROR_MSG + errMsg
        if len(nameAttributes) > 1:
            logger.error("Returned more than one result while fetching get user info for username={} with user response status={} and user response={}. Check your search criteria.".format(username, usernameResponse.status_code, usernameResponse.text))
            errMsg = "Returned more than one result while fetching get user info for username={}. " \
                "Check your search criteria.".format(username)
            return FAILED + " " + ERROR_MSG + errMsg
        userId = nameAttributes[0]['id']
        groupsUrl = BASE_URL + '/api/v1/users/' + userId + '/groups'

        try:
            nameAttributes = json.loads(usernameResponse.text)[0]
        except Exception as e:
            errMsg = "Failed to parse user info for username={} with error={}".format(username, str(e))
            logger.error(errMsg)
            return FAILED + " " + ERROR_MSG + errMsg
        if 'status' not in nameAttributes:
            errMsg = "Failed to parse user info for username={}, status not present in response output".format(username)
            logger.error(errMsg)
            return FAILED + " " + ERROR_MSG + errMsg
        status = nameAttributes['status']

    roleString = ''
    realNameString = ''
    fullString = ''
    if usernameResponse.status_code == 429:
        logger.error("Rate limit reached for IdP, failed to get user info for username={} with user "
                        "response status={} and user response={}".format(username, usernameResponse.status_code, usernameResponse.text))
        errMsg = "Rate limit reached for IdP, failed to get user info for username={} " \
            "with user response status={}".format(username, usernameResponse.status_code)
        return FAILED + " " + ERROR_MSG + errMsg
    if usernameResponse.status_code != 200:
        logger.error("Failed to get user info for username={} with user response status={} and user "
                        "response={}".format(username, usernameResponse.status_code, usernameResponse.text))
        errMsg = "Failed to get user info for username={} " \
            "with user response status={}".format(username, usernameResponse.status_code)
        return FAILED + " " + ERROR_MSG + errMsg
    else:
        logger.info("Successfully obtained user info for username={} with user response status={} and user "
                    "response={}".format(username, usernameResponse.status_code, usernameResponse.text))

    # Available statuses : Staged, Pending User Action, Active, Password Reset, Locked Out, Suspended, Deactivated
    # https://help.okta.com/en/prod/Content/Topics/Directory/end-user-states.htm
    if status not in {"ACTIVE", "PASSWORD_EXPIRED", "RECOVERY", "LOCKED_OUT"}:
        errMsg = "User is not active in IdP for username={} with user status={}".format(username, status)
        logger.error(errMsg)
        return FAILED + " " + ERROR_MSG + errMsg
    realNameString += nameAttributes['profile']['firstName'] + ' ' + nameAttributes['profile']['lastName']

    encodeOutput = True # default to always encode unless specified in args
    if 'encodeOutput' in args and args['encodeOutput'].lower() == 'false':
        encodeOutput = False

    while groupsUrl:
        logger.info("Okta group url is {}".format(groupsUrl))
        groupsResponse = session.get(groupsUrl, timeout=request_timeout)
        if groupsResponse.status_code == 429:
            logger.error("Rate limit reached for IdP, failed to get group info for username={} with group "
                            "response status={} and group response={}".format(username, groupsResponse.status_code, groupsResponse.text))
            errMsg = "Rate limit reached for IdP, failed to get group info for username={} " \
                "with group response status={}".format(username, groupsResponse.status_code)
            return FAILED + " " + ERROR_MSG + errMsg
        if groupsResponse.status_code != 200:
            logger.error("Failed to get group info for username={} with group response status={} and group "
                            "response={}".format(username, groupsResponse.status_code, groupsResponse.text))
            errMsg = "Failed to get group info for username={} " \
                "with group response status={}".format(username, groupsResponse.status_code)
            return FAILED + " " + ERROR_MSG + errMsg
        try:
            groupAttributes = json.loads(groupsResponse.text)
        except Exception as e:
            errMsg = "Failed to parse group info for username={} with status={} and response={}".format(username, groupsResponse.status_code, groupsResponse.text)
            logger.error(errMsg)
            return FAILED + " " + ERROR_MSG + errMsg

        groupNames = ['{}'.format(urlsafe_b64encode_to_str(group['profile']['name'])) for group in groupAttributes] if encodeOutput else ['{}'.format(group['profile']['name']) for group in groupAttributes]
        roleString += ":".join(groupNames)

        if groupsResponse.links.get('next'):
            groupsUrl = groupsResponse.links['next']['url']
        else:
            groupsUrl = None
    logger.info("Successfully obtained group info for username={}".format(username))

    if encodeOutput:
        logger.info("base64 encoding script output")
        base64UrlEncodedUsername = urlsafe_b64encode_to_str(username)
        base64UrlEncodedRealName = urlsafe_b64encode_to_str(realNameString)

        fullString += '{} --userInfo={};{};{} --encodedOutput=true'.format(SUCCESS, base64UrlEncodedUsername, base64UrlEncodedRealName, roleString)
    else:
        logger.info("Not base64 encoding script output")
        fullString += '{} --userInfo={};{};{}'.format(SUCCESS, username, realNameString, roleString)

    logger.info("getUserInfo() successful for username={}".format(username))
    return fullString

if __name__ == "__main__":
    callName = sys.argv[1]
    dictIn = readInputs()

    if callName == "getUserInfo":
        response = getUserInfo(dictIn)
        print(response)


#!/venv/bin/python

from jinja2 import Environment, FileSystemLoader, select_autoescape
import json
from linebot import LineBotApi

from linebot.models import FlexSendMessage, TextSendMessage

from linebot.models import BubbleContainer
import datetime
import pytz
import gspread
from oauth2client.service_account import ServiceAccountCredentials
import config
from sales_report import SalesReport

line_bot_api = LineBotApi(config.COnfig.CHANNEL_ACCESS_TOKEN)
scope = ['https://www.googleapis.com/auth/spreadsheets',
            'https://www.googleapis.com/auth/drive']
creds = ServiceAccountCredentials.from_json_keyfile_name('dummy.json', scope)
client = gspread.authorize(creds)

# Template format definition
# Plus, the template was made on the LINE Flex Message Simulator
template_env = Environment(
    loader=FileSystemLoader('templates'),
    autoescape=select_autoescape(['html', 'xml', 'json'])
)

def push_report(content):
        #The coordinates for monthObjList will always be the same regardless of current date
        monthObjList = ws.acell('G36').value
        objList = ws.acell('G' + str(date.day + 4)).value
        pastList = ws.acell('H' + str(date.day + 4)).value
        # Here we remove the commas from the values for proper integer manipulation
        objList = objList.replace(',','')
        pastList = pastList.replace(',','')
        monthObjList = monthObjList.replace(',','')
        objList = int(objList)
        pastList = int(pastList)
        # MConverting all of the values into integers
        threepm = int(ws.acell('J' + str(date.day + 4)).value)
        sixpm = int(ws.acell('K' + str(date.day + 4)).value)
        ninepm = int(ws.acell('L' + str(date.day + 4)).value)
        totalSales = ws.acell('I' + str(date.day + 4)).value
        totalSales = totalSales.replace(',','')
        totalSales = int(totalSales)
        monthlyObj = int(monthObjList)
        lastYearSales = int(pastList)
        objective = int(objList)
		# Some math
        change = int(totalSales/lastYearSales*100-100)
        changetxt = ""

        # Increases or decreases in profit comparing to last year
        if change > 1.00:
            changetxt = "+"
        elif change < 1.00:
            changetxt = ""
        else:
            changetxt = " error"
        # Whether the objective was reached or not for that day sales
        objectivestatus = ""
        if totalSales >= objective:
            objectivestatus = "REACHED"
        elif totalSales < objective:
            objectivestatus = "NOT REACHED"
        else:
            objectivestatus = " error"
        # Here we append today's sales to the month dictionary to work with the variables below
        current = int(ws.acell('I36').value)
        left = int(monthlyObj) - int(current)
        percentage = int(current / monthlyObj * 100)
        # Class output variables
        objectivestatus_o = str(objectivestatus)
        date_o = date.strftime("%Y-%m-%d")
        lastYearSales_o = str("{:,}".format(lastYearSales)) + "円"
        objective_o = str("{:,}".format(objective)) + "円"
        totalSales_o = str("{:,}".format(totalSales)) + "円"
        threepm_o = str("{:,}".format(threepm)) + "円"
        sixpm_o = str("{:,}".format(sixpm)) + "円"
        ninepm_o = str("{:,}".format(ninepm)) + "円"
        changetxt_o = str(changetxt)
        change_o = str((changetxt) + (change) + "%")
        monthlyObj_o = str("{:,}".format(monthlyObj)) +  "円"
        current_o = str("{:,}".format(current)) + "円"
        left_o = str("{:,}".format(left)) + "円"
        percentage_o = str(percentage) + "%"
        report = SalesReport(objectivestatus_o, '#1DB446', date_o,
            lastYearSales_o, totalSales_o, objective_o, threepm_o,
            sixpm_o, ninepm_o, change_o, monthlyObj_o, current_o, left_o, percentage_o)
        template = template_env.get_template('sales-report.json')
        data = template.render(dict(data=report))
        # Send the structured message. Note, the template needs to be parsed to
        # an Flex MessageUI element, in this case a BubbleContainer
        msg = FlexSendMessage(alt_text = "Sales Report", contents=BubbleContainer.new_from_json_dict(json.loads(data)))
        line_bot_api.push_message(config.Config.GROUP_ID, msg)

if __name__ == "__main__":
    push_report()


#!/usr/bin/env python3
##############################################################################
# Description:
#
# Script for creating a generic spreadsheet of the Sun, Moon, and TrueNorthNode.
# The timestamp of each line entry is at the exact moment (within an error
# threshold of 1 minute) of each of the 22 moon synodic month phases.
#
# Note: Normally there are 30 moon phases, but this script is to get
#       the timestamps as if there are 22 moon synodic phases.  So the
#       lunation steps divided by 22.
#
# Data included is:
#
#        - Geocentric
#        - Heliocentric
#        - Declination
#        - Latitude
#
# Usage:
#
#   1) Ensure the global variables have been set appropriately:
#        - Start and end dates
#        - Location (coordinates)
#        - Time of day.
#        - Planets used in calculations
#        - Output CSV filename.
#
#    2) Run the script:
#
#        python3 createGenericEphemerisSpreadsheet.py
#
##############################################################################

# For obtaining current directory path information, and creating directories
import os
import sys
import errno

# For copy.deepcopy()
import copy

# For dates.
import datetime

# For logging.
import logging

# For math.floor()
import math

# Include some PriceChartingTool modules.
# This assumes that the relative directory from this script is: ../../../../src
thisScriptDir = os.path.dirname(os.path.abspath(__file__))
thisScriptDir = os.path.dirname(thisScriptDir)
thisScriptDir = os.path.dirname(thisScriptDir)
srcDir = os.path.dirname(os.path.dirname(thisScriptDir)) + os.sep + "src"
if srcDir not in sys.path:
    sys.path.insert(0, srcDir)
from astrologychart import AstrologyUtils
from ephemeris import Ephemeris
from data_objects import *

##############################################################################

##############################################################################
# Global variables

# Version string.
VERSION = "0.1"

# Location information to use with the Ephemeris.
locationName = "New York City"
locationLongitude = -74.0064
locationLatitude = 40.7142
locationElevation = 0

# Timezone information to use with the Ephemeris.
timezone = pytz.timezone("US/Eastern")

# Time of the day to use to whem getting ephemeris measurements.
hourOfDay = 12
minuteOfHour = 0

startDt = datetime.datetime(year=1906, month=1, day=1,
                            hour=hourOfDay, minute=minuteOfHour,
                            tzinfo=timezone)
#startDt = datetime.datetime(year=1984, month=1, day=1,
#                            hour=hourOfDay, minute=minuteOfHour,
#                            tzinfo=timezone)
#startDt = datetime.datetime(year=2013, month=1, day=1,
#                            hour=hourOfDay, minute=minuteOfHour,
#                            tzinfo=timezone)

#endDt   = datetime.datetime(year=1906, month=5, day=31,
#                            hour=hourOfDay, minute=minuteOfHour,
#                            tzinfo=timezone)
endDt   = datetime.datetime(year=1935, month=12, day=31,
                            hour=hourOfDay, minute=minuteOfHour,
                            tzinfo=timezone)
#endDt   = datetime.datetime(year=2015, month=12, day=31,
#                            hour=hourOfDay, minute=minuteOfHour,
#                            tzinfo=timezone)
#endDt   = datetime.datetime(year=2013, month=4, day=1,
#                            hour=hourOfDay, minute=minuteOfHour,
#                            tzinfo=timezone)

# Destination output CSV file.
outputFilename = "/home/rluu/programming/pricechartingtool/misc/EphemerisGeneration/moonPhases/moon_synodic_22_phases/sun_moon_node_ephemeris_nyc.csv"

# Planet names to do calculations for.
geocentricPlanetNames = [\
    "Sun",
    "Moon",
    #"Mercury",
    #"Venus",
    #"Earth",
    #"Mars",
    #"Jupiter",
    #"Saturn",
    #"Uranus",
    #"Neptune",
    #"Pluto",
    "TrueNorthNode",
    #"Chiron",
    #"Isis"
    ]

# Planet names to do calculations for.
heliocentricPlanetNames = [\
    #"Sun",
    #"Moon",
    #"Mercury",
    #"Venus",
    #"Earth",
    #"Mars",
    #"Jupiter",
    #"Saturn",
    #"Uranus",
    #"Neptune",
    #"Pluto",
    #"TrueNorthNode",
    #"Chiron",
    #"Isis"
    ]

# Planet names to do calculations for.
declinationPlanetNames = [\
    "Sun",
    "Moon",
    #"Mercury",
    #"Venus",
    #"Earth",
    #"Mars",
    #"Jupiter",
    #"Saturn",
    #"Uranus",
    #"Neptune",
    #"Pluto",
    #"TrueNorthNode",
    #"Chiron",
    #"Isis"
    ]

# Planet names to do calculations for.
geocentricLatitudePlanetNames = [\
    #"Sun",
    "Moon",
    #"Mercury",
    #"Venus",
    #"Earth",
    #"Mars",
    #"Jupiter",
    #"Saturn",
    #"Uranus",
    #"Neptune",
    #"Pluto",
    #"TrueNorthNode",
    #"Chiron",
    #"Isis"
    ]

# Planet names to do calculations for.
heliocentricLatitudePlanetNames = [\
    #"Sun",
    #"Moon",
    #"Mercury",
    #"Venus",
    #"Earth",
    #"Mars",
    #"Jupiter",
    #"Saturn",
    #"Uranus",
    #"Neptune",
    #"Pluto",
    #"TrueNorthNode",
    #"Chiron",
    #"Isis"
    ]

# For logging.
logging.basicConfig(format='%(levelname)s: %(message)s')
moduleName = globals()['__name__']
log = logging.getLogger(moduleName)
#log.setLevel(logging.DEBUG)
log.setLevel(logging.INFO)

##############################################################################

def shutdown(rc):
    """Exits the script, but first flushes all logging handles, etc."""

    # Close the Ephemeris so it can do necessary cleanups.
    Ephemeris.closeEphemeris()

    logging.shutdown()

    sys.exit(rc)

##############################################################################

def isNumber(numStr):
    """Returns True if the string is a number."""

    rv = True

    for letter in numStr:
        if not (letter.isdigit() or letter == "."):
            rv = False
            break

    return rv

def formatToDateStr(dt):
    """Returns a date string in the format: "YYYY-MM-DD".

    Arguments:
    dt - datetime.datetime object.

    Returns:
    str object holding the date in format "YYYY-MM-DD".
    """

    dateStr = "{:04}-{:02}-{:02}".\
              format(dt.year, dt.month, dt.day)

    return dateStr

def formatToDateAndTimeStr(dt):
    """Returns a timestamp string in the format: "YYYY-MM-DD HH:MM"

    Arguments:
    dt - datetime.datetime object.

    Returns:
    str object holding the date in format "YYYY-MM-DD HH:MM".
    """

    dateAndTimeStr = "{:04}-{:02}-{:02} {:02}:{:02}".\
              format(dt.year, dt.month, dt.day, dt.hour, dt.minute)

    return dateAndTimeStr

def formatToDateAndDetailedTimeStr(datetimeObj):
    """Returns a string representation of a datetime.datetime object.
    Normally we wouldn't need to do this, but the datetime.strftime()
    does not work on years less than 1900.

    Arguments:
    datetimeObj - datetime.datetime object with a tzinfo defined.

    Returns:
    String holding the info about the datetime.datetime object, in
    the datetime.strftime() format:  "%Y-%m-%d %H:%M:%S %Z%z"
    """

    # Timezone name string, extracted from datetime.tzname().
    # This accounts for the fact that datetime.tzname() can return None.
    tznameStr = datetimeObj.tzname()
    if tznameStr == None:
        tznameStr = ""

    # Return the formatted string.
    return "{:04}-{:02}-{:02} {:02}:{:02} {}{}".\
        format(datetimeObj.year,
               datetimeObj.month,
               datetimeObj.day,
               datetimeObj.hour,
               datetimeObj.minute,
               tznameStr,
               Ephemeris.getTimezoneOffsetFromDatetime(datetimeObj))

def getPlanetaryInfosForDatetime(dt):
    """Helper function for getting a list of PlanetaryInfo objects
    to display in the astrology chart.
    """

    # Set the location again (required).
    Ephemeris.setGeographicPosition(locationLongitude,
                                    locationLatitude,
                                    locationElevation)

    # Get planetary info for all the planets.
    planets = []

    # Astrological house system for getting the house cusps.
    houseSystem = Ephemeris.HouseSys['Porphyry']

    planets.append(Ephemeris.getH1PlanetaryInfo(dt, houseSystem))
    #planets.append(Ephemeris.getH2PlanetaryInfo(dt, houseSystem))
    #planets.append(Ephemeris.getH3PlanetaryInfo(dt, houseSystem))
    #planets.append(Ephemeris.getH4PlanetaryInfo(dt, houseSystem))
    #planets.append(Ephemeris.getH5PlanetaryInfo(dt, houseSystem))
    #planets.append(Ephemeris.getH6PlanetaryInfo(dt, houseSystem))
    #planets.append(Ephemeris.getH7PlanetaryInfo(dt, houseSystem))
    #planets.append(Ephemeris.getH8PlanetaryInfo(dt, houseSystem))
    #planets.append(Ephemeris.getH9PlanetaryInfo(dt, houseSystem))
    planets.append(Ephemeris.getH10PlanetaryInfo(dt, houseSystem))
    #planets.append(Ephemeris.getH11PlanetaryInfo(dt, houseSystem))
    #planets.append(Ephemeris.getH12PlanetaryInfo(dt, houseSystem))
    #planets.append(Ephemeris.getARMCPlanetaryInfo(dt, houseSystem))
    #planets.append(Ephemeris.getVertexPlanetaryInfo(dt, houseSystem))
    #planets.append(Ephemeris.getEquatorialAscendantPlanetaryInfo(dt, houseSystem))
    #planets.append(Ephemeris.getCoAscendant1PlanetaryInfo(dt, houseSystem))
    #planets.append(Ephemeris.getCoAscendant2PlanetaryInfo(dt, houseSystem))
    #planets.append(Ephemeris.getPolarAscendantPlanetaryInfo(dt, houseSystem))
    #planets.append(Ephemeris.getHoraLagnaPlanetaryInfo(dt))
    #planets.append(Ephemeris.getGhatiLagnaPlanetaryInfo(dt))
    #planets.append(Ephemeris.getMeanLunarApogeePlanetaryInfo(dt))
    #planets.append(Ephemeris.getOsculatingLunarApogeePlanetaryInfo(dt))
    #planets.append(Ephemeris.getInterpolatedLunarApogeePlanetaryInfo(dt))
    #planets.append(Ephemeris.getInterpolatedLunarPerigeePlanetaryInfo(dt))
    planets.append(Ephemeris.getSunPlanetaryInfo(dt))
    planets.append(Ephemeris.getMoonPlanetaryInfo(dt))
    planets.append(Ephemeris.getMercuryPlanetaryInfo(dt))
    planets.append(Ephemeris.getVenusPlanetaryInfo(dt))
    planets.append(Ephemeris.getEarthPlanetaryInfo(dt))
    planets.append(Ephemeris.getMarsPlanetaryInfo(dt))
    planets.append(Ephemeris.getJupiterPlanetaryInfo(dt))
    planets.append(Ephemeris.getSaturnPlanetaryInfo(dt))
    planets.append(Ephemeris.getUranusPlanetaryInfo(dt))
    planets.append(Ephemeris.getNeptunePlanetaryInfo(dt))
    planets.append(Ephemeris.getPlutoPlanetaryInfo(dt))
    #planets.append(Ephemeris.getMeanNorthNodePlanetaryInfo(dt))
    #planets.append(Ephemeris.getTrueSouthNodePlanetaryInfo(dt))
    planets.append(Ephemeris.getTrueNorthNodePlanetaryInfo(dt))
    #planets.append(Ephemeris.getTrueSouthNodePlanetaryInfo(dt))
    #planets.append(Ephemeris.getCeresPlanetaryInfo(dt))
    #planets.append(Ephemeris.getPallasPlanetaryInfo(dt))
    #planets.append(Ephemeris.getJunoPlanetaryInfo(dt))
    #planets.append(Ephemeris.getVestaPlanetaryInfo(dt))
    planets.append(Ephemeris.getIsisPlanetaryInfo(dt))
    #planets.append(Ephemeris.getNibiruPlanetaryInfo(dt))
    planets.append(Ephemeris.getChironPlanetaryInfo(dt))
    #planets.append(Ephemeris.getGulikaPlanetaryInfo(dt))
    #planets.append(Ephemeris.getMandiPlanetaryInfo(dt))
    #planets.append(Ephemeris.getMeanOfFivePlanetaryInfo(dt))
    #planets.append(Ephemeris.getCycleOfEightPlanetaryInfo(dt))
    #planets.append(Ephemeris.getAvgMaJuSaUrNePlPlanetaryInfo(dt))
    #planets.append(Ephemeris.getAvgJuSaUrNePlanetaryInfo(dt))
    #planets.append(Ephemeris.getAvgJuSaPlanetaryInfo(dt))

    return planets

def getEphemerisDataLineForDatetime(dt):
    """Obtains the line of CSV text of planetary position data.

    Arguments:
    dt - datetime.datetime object with the timestamp seeked.

    Returns:

    str in CSV format. Since there are a lot of fields, please See the
    section of code where we write the header info str for the format.
    """

    # Return value.
    rv = ""

    planetaryInfos = getPlanetaryInfosForDatetime(dt)

    log.debug("Just obtained planetaryInfos for timestamp: {}".\
              format(Ephemeris.datetimeToStr(dt)))

    # Planet geocentric longitude 15-degree axis points.
    for planetName in geocentricPlanetNames:
        for pi in planetaryInfos:
            if pi.name == planetName:
                lon = pi.geocentric['tropical']['longitude']
                rv += "{:.3f},".format(lon % 15.0)

    # Planet geocentric longitude.
    for planetName in geocentricPlanetNames:
        for pi in planetaryInfos:
            if pi.name == planetName:
                lon = pi.geocentric['tropical']['longitude']
                rv += "{:.3f},".format(lon)

    # Planet geocentric longitude in zodiac str format.
    for planetName in geocentricPlanetNames:
        for pi in planetaryInfos:
            if pi.name == planetName:
                lon = pi.geocentric['tropical']['longitude']
                valueStr = \
                         AstrologyUtils.\
                         convertLongitudeToStrWithRasiAbbrev(lon)
                rv += valueStr + ","

    # Planet heliocentric longitude 15-degree axis points.
    for planetName in heliocentricPlanetNames:
        for pi in planetaryInfos:
            if pi.name == planetName:
                lon = pi.heliocentric['tropical']['longitude']
                rv += "{:.3f},".format(lon % 15.0)

    # Planet heliocentric longitude.
    for planetName in heliocentricPlanetNames:
        for pi in planetaryInfos:
            if pi.name == planetName:
                lon = pi.heliocentric['tropical']['longitude']
                rv += "{:.3f},".format(lon)

    # Planet heliocentric longitude in zodiac str format.
    for planetName in heliocentricPlanetNames:
        for pi in planetaryInfos:
            if pi.name == planetName:
                lon = pi.heliocentric['tropical']['longitude']
                valueStr = \
                         AstrologyUtils.\
                         convertLongitudeToStrWithRasiAbbrev(lon)
                rv += valueStr + ","

    # Planet declination.
    for planetName in declinationPlanetNames:
        for pi in planetaryInfos:
            if pi.name == planetName:
                declination = pi.geocentric['tropical']['declination']
                rv += "{:.3f},".format(declination)

    # Planet geocentric latitude.
    for planetName in geocentricLatitudePlanetNames:
        for pi in planetaryInfos:
            if pi.name == planetName:
                latitude = pi.geocentric['tropical']['latitude']
                rv += "{:.3f},".format(latitude)

    # Planet heliocentric latitude.
    for planetName in heliocentricLatitudePlanetNames:
        for pi in planetaryInfos:
            if pi.name == planetName:
                latitude = pi.heliocentric['tropical']['latitude']
                rv += "{:.3f},".format(latitude)

    # Remove trailing comma.
    rv = rv[:-1]

    return rv

##############################################################################

def getLongitudeAspectTimestamps(\
    startDt, endDt,
    planet1ParamsList,
    planet2ParamsList,
    degreeDifference,
    uniDirectionalAspectsFlag=True,
    maxErrorTd=datetime.timedelta(hours=1)):
    """Obtains a list of datetime.datetime objects that contain
    the moments when the aspect specified is active.

    Warning on usage:
    When planet-longitude-averaging is utilized for the longitude
    of planet1 or planet2, the aspects returned by this function
    cannot be fully relied upon.

    This short-coming happens under these circumstances because it
    is possible that the longitude can abruptly 'jump' or hop a
    large distance when measurements are taken between timestamp
    steps.

    For example, this 'jumping' effect can occur if two planets A
    and B, are both around 355 degrees, and planet A crosses the 0
    degree mark.  Now the average goes from around 355 degrees
    (355 + 355 = 710 / 2 = 355), to about 180 degrees (355 + 0 =
    355 / 2 = about 180).

    While corrections for this can be made for the case of having
    only 2 planets involved, if more planets are involved then the
    adjustment required quickly becomes non-trivial.

    Arguments:
    startDt   - datetime.datetime object for the starting timestamp
                to do the calculations for artifacts.
    endDt     - datetime.datetime object for the ending timestamp
                to do the calculations for artifacts.
    highPrice - float value for the high price to end the vertical line.
    lowPrice  - float value for the low price to end the vertical line.

    planet1ParamsList - List of tuples that will be used as parameters
                  for planet1.  Each tuple contained in this list
                  represents parameters for each planet that will
                  get averaged to create what is known as planet1.

                  The contents of the tuple are:
                  (planetName, centricityType, longitudeType)

                  Where:
                  planetName - str holding the name of the second
                               planet to do the calculations for.
                  centricityType - str value holding either "geocentric",
                                   "topocentric", or "heliocentric".
                  longitudeType - str value holding either
                                  "tropical" or "sidereal".

                  Example: So if someone wanted planet1 to be the
                  average location of of geocentric sidereal
                  Saturn and geocentric sidereal Uranus, the
                  'planet1ParamsList' parameter would be:

                  [("Saturn", "geocentric", "sidereal"),
                   ("Uranus", "geocentric", "sidereal")]

                  If the typical use-case is desired for the
                  longitude of just a single planet, pass a list
                  with only 1 tuple.  As an example, for Mercury
                  it would be:

                  [("Mercury", "heliocentric", "tropical")]

    planet2ParamsList - List of tuples that will be used as parameters
                  for planet2.  For additional details about the
                  format of this parameter field, please see the
                  description for parameter 'planet1ParamsList'

    degreeDifference - float value for the number of degrees of
                       separation for this aspect.

    uniDirectionalAspectsFlag - bool value for whether or not
                 uni-directional aspects are enabled or not.  By
                 default, aspects are bi-directional, so Saturn
                 square-aspect Jupiter would be the same as
                 Jupiter square-aspect Saturn.  If this flag is
                 set to True, then those two combinations would be
                 considered unique.  In the case where the flag is
                 set to True, for the aspect to be active,
                 planet2 would need to be 'degreeDifference'
                 degrees in front of planet1.

    maxErrorTd - datetime.timedelta object holding the maximum
                 time difference between the exact planetary
                 combination timestamp, and the one calculated.
                 This would define the accuracy of the
                 calculations.

    Returns:

    List of datetime.datetime objects.  Each timestamp in the list
    is the moment where the aspect is active and satisfies the
    given parameters.  In the event of an error, the reference
    None is returned.

    """

    log.debug("Entered " + inspect.stack()[0][3] + "()")

    # List of timestamps of the aspects found.
    aspectTimestamps = []

    # Make sure the inputs are valid.
    if endDt < startDt:
        log.error("Invalid input: 'endDt' must be after 'startDt'")
        return None

    # Check to make sure planet lists were given.
    if len(planet1ParamsList) == 0:
        log.error("planet1ParamsList must contain at least 1 tuple.")
        return None
    if len(planet2ParamsList) == 0:
        log.error("planet2ParamsList must contain at least 1 tuple.")
        return None

    log.debug("planet1ParamsList passed in is: {}".\
              format(planet1ParamsList))
    log.debug("planet2ParamsList passed in is: {}".\
              format(planet2ParamsList))

    # Check for valid inputs in each of the planet parameter lists.
    for planetTuple in planet1ParamsList + planet2ParamsList:
        if len(planetTuple) != 3:
            log.error("Input error: " + \
                      "Not enough values given in planet tuple.")
            return None

        planetName = planetTuple[0]
        centricityType = planetTuple[1]
        longitudeType = planetTuple[2]

        loweredCentricityType = centricityType.lower()
        if loweredCentricityType != "geocentric" and \
            loweredCentricityType != "topocentric" and \
            loweredCentricityType != "heliocentric":

            log.error("Invalid input: Centricity type is invalid.  " + \
                  "Value given was: {}".format(centricityType))
            return None

        # Check inputs for longitude type.
        loweredLongitudeType = longitudeType.lower()
        if loweredLongitudeType != "tropical" and \
            loweredLongitudeType != "sidereal":

            log.error("Invalid input: Longitude type is invalid.  " + \
                  "Value given was: {}".format(longitudeType))
            return None

    # Field name we are getting.
    fieldName = "longitude"

    # Initialize the Ephemeris with the birth location.
    log.debug("Setting ephemeris location ...")

    Ephemeris.setGeographicPosition(locationLongitude,
                                    locationLatitude,
                                    locationElevation)

    # Set the step size.
    stepSizeTd = datetime.timedelta(days=1)
    for planetTuple in planet1ParamsList + planet2ParamsList:
        planetName = planetTuple[0]

        if Ephemeris.isHouseCuspPlanetName(planetName) or \
           Ephemeris.isAscmcPlanetName(planetName):

            # House cusps and ascmc planets need a smaller step size.
            stepSizeTd = datetime.timedelta(hours=1)
        elif planetName == "Moon":
            # Use a smaller step size for the moon so we can catch
            # smaller aspect sizes.
            stepSizeTd = datetime.timedelta(hours=3)

    log.debug("Step size is: {}".format(stepSizeTd))

    # Desired angles.  We need to check for planets at these angles.
    desiredAngleDegList = []

    desiredAngleDeg1 = Util.toNormalizedAngle(degreeDifference)
    desiredAngleDegList.append(desiredAngleDeg1)
    if Util.fuzzyIsEqual(desiredAngleDeg1, 0):
        desiredAngleDegList.append(360)

    if uniDirectionalAspectsFlag == False:
        desiredAngleDeg2 = \
            360 - Util.toNormalizedAngle(degreeDifference)
        if desiredAngleDeg2 not in desiredAngleDegList:
            desiredAngleDegList.append(desiredAngleDeg2)

    # Debug output.
    anglesStr = ""
    for angle in desiredAngleDegList:
        anglesStr += "{} ".format(angle)
    log.debug("Angles in desiredAngleDegList: " + anglesStr)

    # Iterate through, appending to aspectTimestamps list as we go.
    steps = []
    steps.append(copy.deepcopy(startDt))
    steps.append(copy.deepcopy(startDt))

    longitudesP1 = []
    longitudesP1.append(None)
    longitudesP1.append(None)

    longitudesP2 = []
    longitudesP2.append(None)
    longitudesP2.append(None)

    def getFieldValue(dt, planetParamsList, fieldName):
        """Creates the PlanetaryInfo object for the given
        planetParamsList and returns the value of the field
        desired.
        """

        log.debug("planetParamsList passed in is: {}".\
                  format(planetParamsList))

        unAveragedFieldValues = []

        for t in planetParamsList:
            planetName = t[0]
            centricityType = t[1]
            longitudeType = t[2]

            pi = Ephemeris.getPlanetaryInfo(planetName, dt)

            log.debug("Planet {} has geo sid longitude: {}".\
                      format(planetName,
                             pi.geocentric["sidereal"]["longitude"]))

            fieldValue = None

            if centricityType.lower() == "geocentric":
                fieldValue = pi.geocentric[longitudeType][fieldName]
            elif centricityType.lower() == "topocentric":
                fieldValue = pi.topocentric[longitudeType][fieldName]
            elif centricityType.lower() == "heliocentric":
                fieldValue = pi.heliocentric[longitudeType][fieldName]
            else:
                log.error("Unknown centricity type: {}".\
                          format(centricityType))
                fieldValue = None

            unAveragedFieldValues.append(fieldValue)

        log.debug("unAveragedFieldValues is: {}".\
                  format(unAveragedFieldValues))

        # Average the field values.
        total = 0.0
        for v in unAveragedFieldValues:
            total += v
        averagedFieldValue = total / len(unAveragedFieldValues)

        log.debug("averagedFieldValue is: {}".\
                  format(averagedFieldValue))

        return averagedFieldValue

    log.debug("Stepping through timestamps from {} to {} ...".\
              format(Ephemeris.datetimeToStr(startDt),
                     Ephemeris.datetimeToStr(endDt)))

    currDiff = None
    prevDiff = None

    while steps[-1] < endDt:
        currDt = steps[-1]
        prevDt = steps[-2]

        log.debug("Looking at currDt == {} ...".\
                  format(Ephemeris.datetimeToStr(currDt)))

        longitudesP1[-1] = \
            Util.toNormalizedAngle(\
            getFieldValue(currDt, planet1ParamsList, fieldName))
        longitudesP2[-1] = \
            Util.toNormalizedAngle(\
            getFieldValue(currDt, planet2ParamsList, fieldName))

        log.debug("{} {} is: {}".\
                  format(planet1ParamsList, fieldName,
                         longitudesP1[-1]))
        log.debug("{} {} is: {}".\
                  format(planet2ParamsList, fieldName,
                         longitudesP2[-1]))

        currDiff = Util.toNormalizedAngle(\
            longitudesP1[-1] - longitudesP2[-1])

        log.debug("prevDiff == {}".format(prevDiff))
        log.debug("currDiff == {}".format(currDiff))

        if prevDiff != None and \
               longitudesP1[-2] != None and \
               longitudesP2[-2] != None:

            if abs(prevDiff - currDiff) > 180:
                # Probably crossed over 0.  Adjust the prevDiff so
                # that the rest of the algorithm can continue to
                # work.
                if prevDiff > currDiff:
                    prevDiff -= 360
                else:
                    prevDiff += 360

                log.debug("After adjustment: prevDiff == {}".\
                          format(prevDiff))
                log.debug("After adjustment: currDiff == {}".\
                          format(currDiff))

            for desiredAngleDeg in desiredAngleDegList:
                log.debug("Looking at desiredAngleDeg: {}".\
                          format(desiredAngleDeg))

                desiredDegree = desiredAngleDeg

                if prevDiff < desiredDegree and currDiff >= desiredDegree:
                    log.debug("Crossed over {} from below to above!".\
                              format(desiredDegree))

                    # This is the upper-bound of the error timedelta.
                    t1 = prevDt
                    t2 = currDt
                    currErrorTd = t2 - t1

                    # Refine the timestamp until it is less than
                    # the threshold.
                    while currErrorTd > maxErrorTd:
                        log.debug("Refining between {} and {}".\
                                  format(Ephemeris.datetimeToStr(t1),
                                         Ephemeris.datetimeToStr(t2)))

                        # Check the timestamp between.
                        timeWindowTd = t2 - t1
                        halfTimeWindowTd = \
                            datetime.\
                            timedelta(days=(timeWindowTd.days / 2.0),
                                seconds=(timeWindowTd.seconds / 2.0),
                                microseconds=\
                                      (timeWindowTd.microseconds / 2.0))
                        testDt = t1 + halfTimeWindowTd

                        testValueP1 = \
                            Util.toNormalizedAngle(getFieldValue(\
                            testDt, planet1ParamsList, fieldName))
                        testValueP2 = \
                            Util.toNormalizedAngle(getFieldValue(\
                            testDt, planet2ParamsList, fieldName))

                        log.debug("testValueP1 == {}".format(testValueP1))
                        log.debug("testValueP2 == {}".format(testValueP2))

                        if longitudesP1[-2] > 240 and testValueP1 < 120:
                            # Planet 1 hopped over 0 degrees.
                            testValueP1 += 360
                        elif longitudesP1[-2] < 120 and testValueP1 > 240:
                            # Planet 1 hopped over 0 degrees.
                            testValueP1 -= 360

                        if longitudesP2[-2] > 240 and testValueP2 < 120:
                            # Planet 2 hopped over 0 degrees.
                            testValueP2 += 360
                        elif longitudesP2[-2] < 120 and testValueP2 > 240:
                            # Planet 2 hopped over 0 degrees.
                            testValueP2 -= 360

                        testDiff = Util.toNormalizedAngle(\
                            testValueP1 - testValueP2)

                        # Handle special cases of degrees 0 and 360.
                        # Here we adjust testDiff so that it is in the
                        # expected ranges.
                        if Util.fuzzyIsEqual(desiredDegree, 0):
                            if testDiff > 240:
                                testDiff -= 360
                        elif Util.fuzzyIsEqual(desiredDegree, 360):
                            if testDiff < 120:
                                testDiff += 360

                        log.debug("testDiff == {}".format(testDiff))

                        if testDiff < desiredDegree:
                            t1 = testDt
                        else:
                            t2 = testDt

                            # Update the curr values.
                            currDt = t2
                            currDiff = testDiff

                            longitudesP1[-1] = testValueP1
                            longitudesP2[-1] = testValueP2

                        currErrorTd = t2 - t1

                    # Update our lists.
                    steps[-1] = currDt

                    # Store the aspect timestamp.
                    aspectTimestamps.append(currDt)

                elif prevDiff > desiredDegree and currDiff <= desiredDegree:
                    log.debug("Crossed over {} from above to below!".\
                              format(desiredDegree))

                    # This is the upper-bound of the error timedelta.
                    t1 = prevDt
                    t2 = currDt
                    currErrorTd = t2 - t1

                    # Refine the timestamp until it is less than
                    # the threshold.
                    while currErrorTd > maxErrorTd:
                        log.debug("Refining between {} and {}".\
                                  format(Ephemeris.datetimeToStr(t1),
                                         Ephemeris.datetimeToStr(t2)))

                        # Check the timestamp between.
                        timeWindowTd = t2 - t1
                        halfTimeWindowTd = \
                            datetime.\
                            timedelta(days=(timeWindowTd.days / 2.0),
                                seconds=(timeWindowTd.seconds / 2.0),
                                microseconds=\
                                      (timeWindowTd.microseconds / 2.0))
                        testDt = t1 + halfTimeWindowTd

                        testValueP1 = \
                            Util.toNormalizedAngle(getFieldValue(\
                            testDt, planet1ParamsList, fieldName))
                        testValueP2 = \
                            Util.toNormalizedAngle(getFieldValue(\
                            testDt, planet2ParamsList, fieldName))

                        log.debug("testValueP1 == {}".format(testValueP1))
                        log.debug("testValueP2 == {}".format(testValueP2))

                        if longitudesP1[-2] > 240 and testValueP1 < 120:
                            # Planet 1 hopped over 0 degrees.
                            testValueP1 += 360
                        elif longitudesP1[-2] < 120 and testValueP1 > 240:
                            # Planet 1 hopped over 0 degrees.
                            testValueP1 -= 360

                        if longitudesP2[-2] > 240 and testValueP2 < 120:
                            # Planet 2 hopped over 0 degrees.
                            testValueP2 += 360
                        elif longitudesP2[-2] < 120 and testValueP2 > 240:
                            # Planet 2 hopped over 0 degrees.
                            testValueP2 -= 360

                        testDiff = Util.toNormalizedAngle(\
                            testValueP1 - testValueP2)

                        # Handle special cases of degrees 0 and 360.
                        # Here we adjust testDiff so that it is in the
                        # expected ranges.
                        if Util.fuzzyIsEqual(desiredDegree, 0):
                            if testDiff > 240:
                                testDiff -= 360
                        elif Util.fuzzyIsEqual(desiredDegree, 360):
                            if testDiff < 120:
                                testDiff += 360

                        log.debug("testDiff == {}".format(testDiff))

                        if testDiff > desiredDegree:
                            t1 = testDt
                        else:
                            t2 = testDt

                            # Update the curr values.
                            currDt = t2
                            currDiff = testDiff

                            longitudesP1[-1] = testValueP1
                            longitudesP2[-1] = testValueP2

                        currErrorTd = t2 - t1

                    # Update our lists.
                    steps[-1] = currDt

                    # Store the aspect timestamp.
                    aspectTimestamps.append(currDt)

        # Prepare for the next iteration.
        log.debug("steps[-1] is: {}".\
                  format(Ephemeris.datetimeToStr(steps[-1])))
        log.debug("stepSizeTd is: {}".format(stepSizeTd))

        steps.append(copy.deepcopy(steps[-1]) + stepSizeTd)
        del steps[0]
        longitudesP1.append(None)
        del longitudesP1[0]
        longitudesP2.append(None)
        del longitudesP2[0]

        # Update prevDiff as the currDiff.
        prevDiff = Util.toNormalizedAngle(currDiff)

    log.debug("Number of timestamps obtained: {}".\
              format(len(aspectTimestamps)))

    log.debug("Exiting " + inspect.stack()[0][3] + "()")
    return aspectTimestamps

##############################################################################

#if __name__ == "__main__":

# Initialize Ephemeris (required).
Ephemeris.initialize()

# Set the Location (required).
Ephemeris.setGeographicPosition(locationLongitude,
                                locationLatitude,
                                locationElevation)

# Log the parameters that are being used.
log.info("Location used is: {}  (lat={}, lon={})".\
         format(locationName, locationLatitude, locationLongitude))
log.info("Timezone used is: {}".format(timezone.zone))
log.info("Start timestamp:  {}".format(Ephemeris.datetimeToStr(startDt)))
log.info("End   timestamp:  {}".format(Ephemeris.datetimeToStr(endDt)))

# Compile the header line text.
headerLine = ""
headerLine += "Date" + ","
headerLine += "Day of week" + ","
headerLine += "Day count" + ","
headerLine += "Week count" + ","
headerLine += "Month count" + ","

# Planet geocentric longitude mod 15.
for planetName in geocentricPlanetNames:
    headerLine += "G." + planetName + "%15" + ","

# Planet geocentric longitude.
for planetName in geocentricPlanetNames:
    headerLine += "G." + planetName + ","

# Planet geocentric longitude in zodiac str format.
for planetName in geocentricPlanetNames:
    headerLine += "G." + planetName + ","

# Planet heliocentric longitude mod 15.
for planetName in heliocentricPlanetNames:
    headerLine += "H." + planetName + "%15" + ","

# Planet heliocentric longitude.
for planetName in heliocentricPlanetNames:
    headerLine += "H." + planetName + ","

# Planet heliocentric longitude in zodiac str form.
for planetName in heliocentricPlanetNames:
    headerLine += "H." + planetName + ","

# Planet declination.
for planetName in declinationPlanetNames:
    headerLine += "D." + planetName + ","

# Planet geocentric latitude.
for planetName in geocentricLatitudePlanetNames:
    headerLine += "G.L." + planetName + ","

# Planet heliocentric latitude.
for planetName in heliocentricLatitudePlanetNames:
    headerLine += "H.L." + planetName + ","

# Remove the trailing comma.
headerLine = headerLine[:-1]

# Initialize the currDt to the start date.  Manually set the hour and
# minute so we get the ephemeris at noon localized time.
currDt = copy.deepcopy(startDt)
currDt = currDt.replace(hour=hourOfDay, minute=minuteOfHour)

stepSizeTd = datetime.timedelta(days=1)

# Text in the output file.
outputLines = []
outputLines.append(headerLine)

prevDate = None
dayCount = 0
weekCount = 0
monthCount = 0

log.info("Doing ephemeris calculations ...")

# Moon synodic month normally has 30 phases, BUT, here we are doing
# calculations as if it has 22 phases.
degreesInCircle = 360.0
numMoonPhases = 22
increment = degreesInCircle / numMoonPhases

# Planet parameters.
planet1ParamsList = [("Moon", "geocentric", "tropical")]
planet2ParamsList = [("Sun",  "geocentric", "tropical")]

timestamps = []

aspectDegrees = []

for i in range(0, numMoonPhases):
    aspectDegrees.append(i * (degreesInCircle / numMoonPhases))

for degreeDifference in aspectDegrees:
    timestamps.extend(\
        getLongitudeAspectTimestamps(\
            startDt, endDt,
            planet1ParamsList,
            planet2ParamsList,
            degreeDifference,
            uniDirectionalAspectsFlag=True,
            maxErrorTd=datetime.timedelta(minutes=1)))

# Sort the timestamps.
timestamps.sort()

# Log debug information.
for i in range(len(timestamps)):
    timestamp = timestamps[i]
    log.debug("timestamps[{}] == {}".\
              format(i, Ephemeris.datetimeToStr(timestamp)))

    currDt = timestamp

    line = ""

    # Get date and time str.
    #line += formatToDateAndTimeStr(currDt) + ","
    line += formatToDateAndDetailedTimeStr(currDt) + ","

    #line += Ephemeris.datetimeToStr(currDt) + ","

    # Get day of the week str, as 3-letter str.
    line += currDt.date().ctime()[0:3] + ","

    # Get the day count, week count, and month count.
    if prevDate == None:
        # This is first iteration in this while loop.  All counts
        # should be zero.

        # Day count.
        line += "{}".format(dayCount) + ","

        # Week count.
        line += "{}".format(weekCount) + ","

        # Month count.
        line += "{}".format(monthCount) + ","

    else:
        # There is a previous date stored, so check to see if we are
        # on a new day now.
        if prevDate != currDt.date():
            # Date changed, increment the day count.
            dayCount += 1

            # Day count.
            line += "{}".format(dayCount) + ","

            # See if the week changed.  Weeks start with Sunday.
            if currDt.date().isoweekday() == 7:
                # It is a Sunday.  A new week has arrived, so increment.
                weekCount += 1

            # Week count.
            line += "{}".format(weekCount) + ","

            # See if the month changed.  Months start on the 1st of the month.
            if prevDate.month != currDt.date().month:
                # A new month has arrived, so increment.
                monthCount += 1

            # Month count.
            line += "{}".format(monthCount) + ","

        else:
            # Date did not change, so print out the same counts as the
            # last loop iteration.

            # Day count.
            line += "{}".format(dayCount) + ","

            # Week count.
            line += "{}".format(weekCount) + ","

            # Month count.
            line += "{}".format(monthCount) + ","

    line += getEphemerisDataLineForDatetime(currDt) + ","

    # Remove the last trailing comma.
    line = line[:-1]

    # Append to the output lines.
    outputLines.append(line)

    # Save the date for the next iteration, so we can maintain our
    # time-keeping for day, week, and month counts.
    prevDate = currDt.date()

# Write outputLines to output file.
try:
    with open(outputFilename, "w", encoding="utf-8") as f:
        log.info("Writing to output file '{}' ...".format(outputFilename))

        endl = "\n"

        for line in outputLines:
            f.write(line + endl)

except IOError as e:
    errStr = "I/O Error while trying to write file '" + \
             outputFilename + "':" + os.linesep + str(e)
    log.error(errStr)
    shutdown(1)

log.info("Done.")
shutdown(0)

##############################################################################


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

import rospy
import smach
from std_msgs.msg import Empty, Int16
from std_srvs.srv import Empty as Emptysrv, EmptyRequest
from butia_behavior.states.service_caller import ServiceCallerState
from butia_speech.srv import SynthesizeSpeech, SynthesizeSpeechRequest
from butia_behavior.machines import getWaitDoorMachine, getGoToFixedMachine
from butia_behavior.machines import getSaySomethingMachine, getFollowMeMachine
from butia_behavior.states import WaitTopicState, PublisherState, ServiceCallerState, WaitTimeState, GoToCancelState

if __name__ == '__main__':
  rospy.init_node('navigate_follow')
  sm = smach.StateMachine(outcomes = ['succeeded', 'aborted', 'preempted', 'arrived'])
  with sm:
    smach.StateMachine.add("STANDART_FACE_INIT", PublisherState("/updateEmotion", Int16, data=Int16(0)),
    transitions = {
        "succeeded": "SAY_START",
        "aborted": "SAY_START",
        "preempted": "preempted"
    })
    smach.StateMachine.add('SAY_START', getSaySomethingMachine(data = "I am ready to enter. Please, open the door for me.", lang = "en", use_topic = False),
    transitions = {
        'succeeded': 'WAIT_DOOR',
        'aborted': 'SAY_START',
        'preempted': 'preempted'
    })
    smach.StateMachine.add('WAIT_DOOR', getWaitDoorMachine(),
    transitions = {
      'succeeded': 'GOTO_WAYPOINT1',
      'aborted': 'WAIT_DOOR',
      'preempted': 'preempted'
    })
    smach.StateMachine.add('GOTO_WAYPOINT1', getGoToFixedMachine('waypoint_1'),
    transitions = {
      'succeeded': 'GOTO_WAYPOINT2',
      'aborted': 'GOTO_WAYPOINT1',
      'preempted': 'preempted',
      'arrived': 'GOTO_WAYPOINT2'
    })
    smach.StateMachine.add('GOTO_WAYPOINT2', getGoToFixedMachine('waypoint_2'),
    transitions = {
      'succeeded': 'HAPPY_FACE_WAYPOINT2',
      'aborted': 'GOTO_WAYPOINT2',
      'preempted': 'preempted',
      'arrived': 'HAPPY_FACE_WAYPOINT2'
    })
    smach.StateMachine.add('HAPPY_FACE_WAYPOINT2', PublisherState("/updateEmotion", Int16, data=Int16(2)),
    transitions = {
        "succeeded": "SAY_TO_COME",
        "aborted": "SAY_TO_COME",
        "preempted": "preempted"
    })
    smach.StateMachine.add('SAY_TO_COME', getSaySomethingMachine(data = "Please, come to me and stand one and a half meters in front of me , so I can get to know you, and say Follow Me when you are ready", lang = "en", use_topic = False),
    transitions = {
      'succeeded': 'WAIT_HELLO',
      'aborted': 'SAY_TO_COME',
      'preempted': 'preempted'
    })
    smach.StateMachine.add('WAIT_HELLO', WaitTopicState('/butia_speech/bhd/detected', Empty, timeout = 120),
    transitions = {
      'succeeded': 'STANDART_FACE_READY',
      'aborted': 'WAIT_HELLO',
      'preempted': 'preempted'
    })
    smach.StateMachine.add("STANDART_FACE_READY", PublisherState("/updateEmotion", Int16, data=Int16(0)),
    transitions = {
        "succeeded": "SAY_READY",
        "aborted": "SAY_READY",
        "preempted": "preempted"
    })
    smach.StateMachine.add('SAY_READY', getSaySomethingMachine(data = "I am going to recognize you, I have a important instrution for you, say Hello DoRIS to make me stop when you arrived in the next waypoint", lang = "en", use_topic = False), #When you arrived say hello DoRIS for me
    transitions = {
      'succeeded': 'FOLLOW_AND_HELLO',
      'aborted': 'SAY_READY',
      'preempted': 'preempted'
    })
    sm_follow = smach.Concurrence(outcomes = ['succeeded', 'aborted', 'preempted'],
    default_outcome = "aborted",
    outcome_map = {"succeeded":{
      "FOLLOW_ME":"succeeded",
      "FOLLOW_ME":"preempted"
    }},
    child_termination_cb = lambda map: map['WAIT_STOP_FOLLOW'] == 'succeeded'
    )
    with sm_follow:
      smach.Concurrence.add('FOLLOW_ME', getFollowMeMachine())
      smach.Concurrence.add('WAIT_STOP_FOLLOW', WaitTopicState('/butia_speech/bhd/detected', Empty))

    smach.StateMachine.add('FOLLOW_AND_HELLO', sm_follow,
    transitions = {
      'succeeded': 'LOST_GOTO_CANCEL',
      'aborted': 'FOLLOW_AND_HELLO',
      'preempted': 'preempted'
    })
    smach.StateMachine.add('LOST_GOTO_CANCEL', GoToCancelState(),
    transitions = {
      'succeeded': 'STOP_TRACKING',
      'aborted': 'LOST_GOTO_CANCEL',
      'preempted': 'preempted'
    })
    smach.StateMachine.add('STOP_TRACKING', ServiceCallerState('/butia_vision/pt/stop', Emptysrv, data = EmptyRequest()),
    transitions = {
      'succeeded': 'SAY_STOPPED_FOLLOW',
      'aborted': 'STOP_TRACKING',
      'preempted': 'preempted'
    })
    smach.StateMachine.add('SAY_STOPPED_FOLLOW', getSaySomethingMachine(data = "Ok, bye, bye, I am going to the exit...",  lang = "en", use_topic = False),
    transitions = {
      'succeeded':'GOTO_WAYPOINT_2_BACK',
      'aborted':'SAY_STOPPED_FOLLOW',
      'preempted':'preempted'
    })
    smach.StateMachine.add('GOTO_WAYPOINT_2_BACK', getGoToFixedMachine('waypoint_2'),
    transitions = {
      'succeeded': 'GOTO_EXIT',
      'aborted': 'GOTO_WAYPOINT_2_BACK',
      'preempted': 'preempted',
      'arrived': 'GOTO_EXIT'
    })
    smach.StateMachine.add('GOTO_EXIT', getGoToFixedMachine('exit'),
    transitions = {
      'succeeded': 'succeeded',
      'aborted': 'aborted',
      'preempted': 'preempted',
      'arrived': 'arrived'
    })
  outcome = sm.execute()


# Python
import json

# Django Rest Framework
from rest_framework import status

# Models
from movies.models import Movies

# setup test
from movies.test.test_setup import TestSetUp

class MoviesTestCase(TestSetUp):

    def test_create_movie(self):

        movie = {
            'name': 'titanic',
            'gender': 'AC',
            'author': 'leonardo',
            'production': 'netflix',
            'duration': '01:30:00',
            'date_launch': '1997-10-19'
        }

        response = self.client.post(
            '/movies/',
            movie,
            format='json'
        )

        self.assertEqual(response.status_code, status.HTTP_201_CREATED)

    def test_update_partial_movie(self):

        movie_update = {
            'name': 'titanic 2.0',
            'author': 'victor'
        }

        response = self.client.patch(
            f'/movies/{self.movie.pk}/',
            movie_update,
            format='json'
        )

        self.assertEqual(response.status_code, status.HTTP_200_OK)

    def test_update_movie(self):

        movie_update = {
            'name': 'titanic 2.0',
            'gender': 'SE',
            'author': 'victor',
            'production': 'youtube',
            'duration': '02:30:00',
            'date_launch': '1998-10-19',
            'user': f'{self.user.pk}'
        }

        response = self.client.put(
            f'/movies/{self.movie.pk}/',
            movie_update,
            format='json'
        )

        self.assertEqual(response.status_code, status.HTTP_200_OK)

    def test_delete_movie(self):

        response = self.client.delete(
            f'/movies/{self.movie.pk}/',
            format='json'
        )

        self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)

        movie_exists = Movies.objects.filter(pk=self.movie.pk)
        self.assertFalse(movie_exists)

    def test_get_movie(self):

        response = self.client.get('/movies/')

        result = json.loads(response.content)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(result['count'], 2)


import pandoc
import os

pandoc.core.PANDOC_PATH = '/usr/local/bin/pandoc'

def convert_md_to_rst():
    doc = pandoc.Document()
    doc.markdown = open('README.md').read()
    filtered = str(doc.rst)
    f = open('README', 'w+')
    f.write(filtered)
    f.close()

def main():
    convert_md_to_rst()

if __name__ == '__main__':
    main()


from django.shortcuts import render
from django.views.generic import ListView, DetailView, CreateView, TemplateView, UpdateView, DeleteView
from .models import Empleado
from django.urls import reverse_lazy

#forms
from .forms import EmpleadoForm

class InicioView(TemplateView):
    template_name = 'inicio.html'

class ListAllEmpleados(ListView):
    template_name = "persona/lista-empleados.html"
    paginate_by = 4
    ordering = 'first_name'
    context_object_name = 'lista_empleados'

    def get_queryset(self):
        palabra_clave = self.request.GET.get("kword", '')
        lista = Empleado.objects.filter(
            full_name__icontains=palabra_clave
        )

        return lista

class ListaEmpleadosAdmin(ListView):
    template_name = "persona/lista_empleados.html"
    paginate_by = 10
    ordering = 'first_name'
    model = Empleado
    context_object_name = 'empleados'

#Lista de empleados por área con parametro URL

class ListByAreaEmpleado(ListView):
    template_name = "persona/lista-by-area.html"
    context_object_name = 'empleados'
    def get_queryset(self):
        area = self.kwargs['shorname']
        lista = Empleado.objects.filter(
            departamento__shor_name=area
        )
        return lista

class ListEmpleadosByKeword(ListView):
    # Lista empleados por palabra clave

    template_name = 'persona/lista-by-keyword.html'
    context_object_name = 'empleados'

    def get_queryset(self):
        print('Holaaaaaaaaaaaaaaaaaa')
        palabra_clave = self.request.GET.get("kword",'')

        lista = Empleado.objects.filter(
            first_name=palabra_clave
        )
        print('lista resultado', lista)
        return lista

class ListaHabilidadesEmpleado(ListView):
    template_name = 'persona/habilidades.html'
    context_object_name = 'habilidades'

    def get_queryset(self):
        #obtener un unico registro de la base de datos get

        empleado = Empleado.objects.get(id=self.kwargs['id'])
        return empleado.habilidades.all()

class EmpleadoDetailView(DetailView):
    model = Empleado
    template_name = 'persona/detail_empleado.html'

    #Enviar un parametro diferente como empleado del mes
    def get_context_data(self, **kwargs):
        context = super(EmpleadoDetailView, self).get_context_data(**kwargs)

        context['titulo'] = 'Empleado del mes'
        return context

class SuccessView(TemplateView):
    template_name = 'persona/success.html'

class EmpleadoCreateView(CreateView):

    template_name = 'persona/add.html'
    model = Empleado
    form_class = EmpleadoForm

    # fields = ('__all__') Todos los datos
    success_url = reverse_lazy('persona_app:empleados_admin')

    def form_valid(self, form):

        empleado = form.save(commit=False)  # para no hacer 2ble guardado instancia
        empleado.fullname = empleado.first_name + '' + empleado.last_name
        empleado.save()

        return super(EmpleadoCreateView,self).form_valid(form)

class EmpleadoUpdateView(UpdateView):
    template_name = "persona/update.html"
    model = Empleado

    fields = [
        'first_name',
        'last_name',
        'job',
        'departamento',
        'habilidades',
        'hoja_vida',

    ]
    success_url = reverse_lazy('persona_app:empleados_admin')

    #Utilizar post cuando queramos guardar datos antes de ser validados por form_valid

    def post(self, request, *args, **kwargs):
        self.object = self.get_object()

        print('-----------METODO POST-------------')
        print(request.POST)
        print(request.POST['last_name'])
        return super().post(request, *args, **kwargs)

    def form_valid(self, form):
        print('-----------METODO FORM VALID-------------')
        return super(EmpleadoUpdateView, self).form_valid(form)

class EmpleadoDeleteView(DeleteView):
    model = Empleado
    template_name = "persona/delete.html"
    success_url = reverse_lazy('persona_app:empleados_admin')


''' IBEHS 1P10 Mini Milestone 12B Individual File
Name: Owen Johnstone

Date: March 3, 2020 '''
from Team76_GearTrain import *

##Student ID: 400268572
def create_mechanism_B(input_speed, num_gear_trains):
    total_gear_ratio = 1
    for i in range(0,num_gear_trains):
        num_gears = int(input("Please enter the number of gears: "))
        module = int(input("Please enter the module: "))
        gear_train = Gear_Train(num_gears, module)
        for i in range(0, num_gears):
            num_teeth = int(input("Please enter the number of teeth: "))
            gear_train.set_gear_item(i, num_teeth)
        total_gear_ratio = total_gear_ratio * gear_train.calc_GR()
        input_speed = gear_train.calc_wo(input_speed)
    return total_gear_ratio, input_speed

def main():
    inp_speed = int(input("Please enter the input speed: "))
    num_gear_trains = int(input("Please enter the number of gear trains: "))
    gear_ratio, output_speed = create_mechanism_B(inp_speed, num_gear_trains)
    print("Gear ratio: " + str(round(gear_ratio, 2)) + "\tOutput speed: " + str(round(output_speed, 2)))

main()


# v.1
# Задача на программирование: двоичный поиск
#
# В первой строке даны целое число 1≤n≤105 и массив A[1…n] из n различных натуральных чисел, не превышающих 109,
# в порядке возрастания, во второй — целое число 1≤k≤105 и k натуральных чисел b1,…,bk, не превышающих 109.
# Для каждого i от 1 до k необходимо вывести индекс 1≤j≤n, для которого A[j]=bi, или −1, если такого j нет.
#
# Sample Input:
# 5 1 5 8 12 13
# 5 8 1 23 1 11
# Sample Output:
# 3 1 -1 1 -1
#

def main():
    # Get input data
    a = [int(i) for i in input().split()]
    n = a[0]
    # print(type(n), n, type(a), a)

    b = [int(i) for i in input().split()]
    k = b[0]
    # print(type(k), k, type(b), b)

    result_list = []
    for i in b[1:]:
        l = 1
        r = n
        while l <= r:
            j = (l+r)//2
            # print('i=', i, 'j=', j, a[j])
            if a[j] == i:
                result_list.append(j)
                break
            elif a[j] > i:
                r = j-1
            else:
                l = j+1
        else:
            result_list.append(-1)

    # Print results
    for m in result_list:
        print(m, end=' ')

if __name__ == "__main__":
    main()


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

import logging
from typing import Dict, Any
from urllib.parse import quote

import apache_beam as beam
import requests
import json

from uploaders import utils
from models.execution import DestinationType, Batch

class GoogleAnalytics4MeasurementProtocolUploaderDoFn(beam.DoFn):
  def __init__(self):
    super().__init__()
    self.API_URL = 'https://www.google-analytics.com/mp/collect'

  def start_bundle(self):
    pass

  @staticmethod
  def _str2bool(s: str) -> bool:
    return s.lower() == 'true'

  @staticmethod
  def _exactly_one_of(a: Any, b: Any) -> bool:
    return (a and not b) or (not a and b)

  @utils.safe_process(logger=logging.getLogger('megalista.GoogleAnalytics4MeasurementProtocolUploader'))
  def process(self, batch: Batch, **kwargs):
    execution = batch.execution

    api_secret = execution.destination.destination_metadata[0]
    is_event = self._str2bool(execution.destination.destination_metadata[1])
    is_user_property = self._str2bool(execution.destination.destination_metadata[2])
    non_personalized_ads = self._str2bool(execution.destination.destination_metadata[3])

    firebase_app_id = None
    if len(execution.destination.destination_metadata) >= 5:
      firebase_app_id = execution.destination.destination_metadata[4]

    measurement_id = None
    if len(execution.destination.destination_metadata) >= 6:
      measurement_id = execution.destination.destination_metadata[5]

    if not self._exactly_one_of(firebase_app_id, measurement_id):
          raise ValueError(
            'GA4 MP should be called either with a firebase_app_id (for apps) or a measurement_id (for web)')

    if not self._exactly_one_of(is_event, is_user_property):
          raise ValueError(
            'GA4 MP should be called either for sending events or a user properties')

    payload: Dict[str, Any] = {
      'nonPersonalizedAds': non_personalized_ads
    }

    accepted_elements = []

    for row in batch.elements:
      app_instance_id = row.get('app_instance_id')
      client_id = row.get('client_id')
      user_id = row.get('user_id')

      if not self._exactly_one_of(app_instance_id, client_id):
        raise ValueError(
          'GA4 MP should be called either with an app_instance_id (for apps) or a client_id (for web)')

      if is_event:
        params = {k: v for k, v in row.items() if k not in ('name', 'app_instance_id', 'client_id', 'uuid', 'user_id')}
        payload['events'] = [{'name': row['name'], 'params': params}]

      if is_user_property:
        payload['userProperties'] = {k: {'value': v} for k, v in row.items() if k not in ('app_instance_id', 'client_id', 'uuid', 'user_id')}
        payload['events'] = {'name': 'user_property_addition_event', 'params': {}}

      url_container = [f'{self.API_URL}?api_secret={api_secret}']

      if firebase_app_id:
        url_container.append(f'&firebase_app_id={firebase_app_id}')
        if not app_instance_id:
          raise ValueError(
            'GA4 MP needs an app_instance_id parameter when used for an App Stream.')
        payload['app_instance_id'] = app_instance_id

      if measurement_id:
        url_container.append(f'&measurement_id={measurement_id}')
        if not client_id:
          raise ValueError(
            'GA4 MP needs a client_id parameter when used for a Web Stream.')
        payload['client_id'] = client_id

      if user_id:
        payload['user_id'] = user_id

      url = ''.join(url_container)
      response = requests.post(url,data=json.dumps(payload))
      if response.status_code != 204:
        logging.getLogger('megalista.GoogleAnalytics4MeasurementProtocolUploader').error(
          f'Error calling GA4 MP {response.status_code}: {response.raw}')
      else:
        accepted_elements.append(row)

    logging.getLogger('megalista.GoogleAnalytics4MeasurementProtocolUploader').info(
      f'Successfully uploaded {len(accepted_elements)}/{len(batch.elements)} events.')
    yield Batch(execution, accepted_elements)