# -*- coding: utf-8 -*-
import json
from tabulate import tabulate
import pandas as pd
from . import constants
import itertools
import logging
import coloredlogs
import time
from multiprocessing import Process, Manager
import numpy
import requests
import ast
import os

coloredlogs.install(level=logging.INFO)


class RequestException(Exception):
    def __init__(self, value):
        self.value = value

    def __str__(self):
        return repr(self.value)


class JsonFormatException(Exception):
    def __init__(self, value):
        self.value = value

    def __str__(self):
        return repr(self.value)


class FatalException(Exception):
    def __init__(self, value):
        self.value = value

    def __str__(self):
        return repr(self.value)


def print_error_warning(error_json, params):
    print_warning("Facebook Error Code: " + str(error_json["error"]["code"]))
    print_warning("Facebook Error Message: " + str(error_json["error"]["message"]))
    if "error_user_title" in error_json["error"] and "error_user_msg" in error_json["error"]:
        print_warning("Facebook: " + str(error_json["error"]["error_user_title"]) + "\n" + str(
            error_json["error"]["error_user_msg"]))
    print_warning("Facebook Trace Id: " + str(error_json["error"]["fbtrace_id"]))
    print_warning("Request Params : " + str(params))


def get_dataframe_from_json_response_query_data(json_response):
    dataframe = pd.DataFrame()
    for entry in json_response["data"]:
        entry_details = {}
        for field in constants.DETAILS_FIELD_FROM_FACEBOOK_TARGETING_SEARCH:
            entry_details[field] = entry[field] if field in entry else None
        dataframe = dataframe.append(entry_details, ignore_index=True)
    return dataframe


def handle_send_request_error(response, url, params, tryNumber):
    try:
        error_json = json.loads(response.text)
        if error_json["error"]["code"] == constants.API_UNKOWN_ERROR_CODE_1 or error_json["error"][
            "code"] == constants.API_UNKOWN_ERROR_CODE_2:
            print_error_warning(error_json, params)
            time.sleep(constants.INITIAL_TRY_SLEEP_TIME * tryNumber)
            return send_request(url, params, tryNumber)
        elif error_json["error"]["code"] == constants.INVALID_PARAMETER_ERROR and "error_subcode" in error_json[
            "error"] and error_json["error"]["error_subcode"] == constants.FEW_USERS_IN_CUSTOM_LOCATIONS_SUBCODE_ERROR:
            return get_fake_response()
        elif "message" in error_json["error"] and "Invalid zip code" in error_json["error"][
            "message"] and constants.INGORE_INVALID_ZIP_CODES:
            print_warning("Invalid Zip Code:" + str(params[constants.TARGETING_SPEC_FIELD]))
            return get_fake_response()
        else:
            logging.error("Could not handle error.")
            logging.error("Error Code:" + str(error_json["error"]["code"]))
            if "message" in error_json["error"]:
                logging.error("Error Message:" + str(error_json["error"]["message"]))
            if "error_subcode" in error_json["error"]:
                logging.error("Error Subcode:" + str(error_json["error"]["error_subcode"]))
            raise FatalException(str(error_json["error"]))
    except Exception as e:
        logging.error(e)
        raise FatalException(str(response.text))


def send_request(url, params, tryNumber=0):
    tryNumber += 1
    if tryNumber >= constants.MAX_NUMBER_TRY:
        print_warning("Maximum Number of Tries reached. Failing.")
        raise FatalException("Maximum try reached.")
    try:
        response = requests.get(url, params=params, timeout=constants.REQUESTS_TIMEOUT)
    except Exception as error:
        raise RequestException(error.message)
    if response.status_code == 200:
        return response
    else:
        return handle_send_request_error(response, url, params, tryNumber)


def call_request_fb(row, token, account, url):
    target_request = row[constants.TARGETING_FIELD]
    payload = {
        'optimization_goal': "AD_RECALL_LIFT",
        'targeting_spec': json.dumps(target_request),
        'access_token': token,
    }
    payload_str = str(payload)
    print_warning("\tSending in request: %s" % (payload_str))
    url = url.format(account)
    response = send_request(url, payload)

    if "x-business-use-case-usage" in response.headers:
        usage_dict = ast.literal_eval(response.headers["x-business-use-case-usage"])[account][0]
        print_warning("\tUsage Stats: Call count (%d), cputime (%d), total_time (%d)." % (usage_dict["call_count"],
                                                                                          usage_dict["total_cputime"],
                                                                                          usage_dict["total_time"]))
    return response.content


def get_fake_response():
    response = requests.models.Response()
    response._content = constants.FAKE_DATA_RESPONSE_CONTENT
    response.status_code = 200
    logging.warn("Fake Response created: " + response.content)
    return response


def trigger_facebook_call(index, row, token, account, shared_queue, url):
    try:
        response = call_request_fb(row, token, account, url)
        shared_queue.put((index, response))
    except RequestException:
        print_warning("Warning Facebook Request Failed")
        print_warning("Row: " + str(row))
        print_warning("It will try again later")
        shared_queue.put((index, numpy.nan))


#    except Exception, e:
#        print_warning("request failed because %s"%(e))


def add_mocked_column(dataframe):
    dataframe["mock_response"] = dataframe["response"].apply(
        lambda response: True if (constants.MOCK_RESPONSE_FIELD in str(response)) else False)
    return dataframe


def add_timestamp(dataframe):
    dataframe["timestamp"] = constants.UNIQUE_TIME_ID
    return dataframe


def add_published_platforms(dataframe, input_json):
    platforms = constants.PUBLISHER_PLATFORM_DEFAULT
    if constants.API_PUBLISHER_PLATFORMS_FIELD in input_json:
        platforms = input_json[constants.API_PUBLISHER_PLATFORMS_FIELD]
    dataframe[constants.API_PUBLISHER_PLATFORMS_FIELD] = json.dumps(platforms)
    return dataframe


def trigger_request_process_and_return_response(rows_to_request, url):
    process_manager = Manager()
    shared_queue = process_manager.Queue()
    shared_queue_list = []

    # Trigger Process in rows
    for index, row in rows_to_request.iterrows():
        token, account = get_token_and_account_number_or_wait()
        p = Process(target=trigger_facebook_call, args=(index, row, token, account, shared_queue, url))
        p.start()
        p.join()

    # Put things from shared list to normal list
    while shared_queue.qsize() != 0:
        shared_queue_list.append(shared_queue.get())
    return shared_queue_list


def check_exception(p):
    if p.exitcode != 0:
        raise FatalException("FatalError: Check logging for clue. No way to proceed from here.")


def print_info(message):
    logging.info(message)


def unstrict_literal_eval(string):
    try:
        value = ast.literal_eval(string)
        return value
    except ValueError:
        return string
    except SyntaxError:
        return string


def load_dataframe_from_file(file_path):
    dataframe = pd.DataFrame.from_csv(file_path)
    return dataframe.applymap(unstrict_literal_eval)


def save_response_in_dataframe(shared_queue_list, df):
    for result_tuple in shared_queue_list:
        result_index = result_tuple[0]
        result_response = result_tuple[1]
        df.loc[result_index, "response"] = result_response


def save_skeleton_dataframe(dataframe, output_dir=""):
    print_info("Saving Skeleton file: " + constants.DATAFRAME_SKELETON_FILE_NAME)
    dataframe.to_csv(output_dir + constants.DATAFRAME_SKELETON_FILE_NAME)


def save_temporary_dataframe(dataframe, output_dir=""):
    print_info("Saving temporary file: " + constants.DATAFRAME_TEMPORARY_COLLECTION_FILE_NAME)
    dataframe.to_csv(output_dir + constants.DATAFRAME_TEMPORARY_COLLECTION_FILE_NAME)


def save_after_collecting_dataframe(dataframe, output_dir=""):
    print_info("Saving after collecting file: " + constants.DATAFRAME_AFTER_COLLECTION_FILE_NAME)
    dataframe.to_csv(output_dir + constants.DATAFRAME_AFTER_COLLECTION_FILE_NAME)


def save_after_collecting_dataframe_without_full_response(dataframe, output_dir=""):
    dataframe = dataframe.drop('response', 1)
    print_dataframe(dataframe)
    print_info("Saving after collecting file: " + constants.DATAFRAME_AFTER_COLLECTION_FILE_NAME_WITHOUT_FULL_RESPONSE)
    dataframe.to_csv(output_dir + constants.DATAFRAME_AFTER_COLLECTION_FILE_NAME_WITHOUT_FULL_RESPONSE)


def remove_temporary_dataframes():
    for file in [constants.DATAFRAME_SKELETON_FILE_NAME, constants.DATAFRAME_TEMPORARY_COLLECTION_FILE_NAME]:
        os.remove(file)


def print_warning(message):
    logging.warn(message)


def load_json_data_from_response(response):
    return json.loads(response.content)


def print_dataframe(df):
    print(tabulate(df, headers='keys', tablefmt='psql', floatfmt=".0f"))


def build_initial_collection_dataframe():
    return pd.DataFrame(columns=constants.DATAFRAME_COLUMNS)


def get_all_combinations_from_input(input_data_json):
    to_combine_fields = {}
    for field in constants.INPUT_FIELDS_TO_COMBINE:
        try:
            if isinstance(input_data_json[field], list):
                field_content = input_data_json[field]
                to_combine_fields[field] = field_content
            if isinstance(input_data_json[field], dict):
                for intra_field_key in list(input_data_json[field].keys()):
                    to_combine_fields[intra_field_key] = input_data_json[field][intra_field_key]

        except KeyError:
            print_warning("Field not expecified: " + field)

    for field in list(to_combine_fields.keys()):
        for index, value in enumerate(to_combine_fields[field]):
            to_combine_fields[field][index] = (field, value)
    all_combinations = list(itertools.product(*list(to_combine_fields.values())))
    return all_combinations


def add_list_of_ANDS_to_input(list_of_ANDS_between_groups, input_data_json):
    for interests_to_AND in list_of_ANDS_between_groups:
        names = []
        and_ors = []
        for interest_to_AND in interests_to_AND:
            names.append(interest_to_AND[constants.INPUT_NAME_FIELD])
            if "or" not in interest_to_AND:
                raise Exception("Only AND of ors are supported")
            and_ors.append(interest_to_AND["or"])
        new_and_interest = {
            constants.INPUT_NAME_FIELD: " AND ".join(names),
            "and_ors": and_ors,
            "isAND": True
        }
        input_data_json[constants.INPUT_INTEREST_FIELD].append(new_and_interest)


def generate_collection_request_from_combination(current_combination, input_data):
    targeting = build_targeting(current_combination, input_data)
    dataframe_row = {}
    for field in current_combination:
        field_name = field[0]
        value = field[1]
        dataframe_row[field_name] = value
    dataframe_row[constants.ALLFIELDS_FIELD] = current_combination
    dataframe_row[constants.TARGETING_FIELD] = targeting
    dataframe_row[constants.INPUT_NAME_FIELD] = input_data[constants.INPUT_NAME_FIELD]
    return dataframe_row


def select_common_fields_in_targeting(targeting, input_combination_dictionary):
    # Selecting Geolocation
    geo_location = input_combination_dictionary[constants.INPUT_GEOLOCATION_FIELD]
    if constants.INPUT_GEOLOCATION_LOCATION_TYPE_FIELD in geo_location:
        location_type = geo_location[constants.INPUT_GEOLOCATION_LOCATION_TYPE_FIELD]
    else:
        location_type = constants.DEFAULT_GEOLOCATION_LOCATION_TYPE_FIELD

    targeting[constants.API_GEOLOCATION_FIELD] = {
        geo_location["name"]: geo_location["values"],
        constants.INPUT_GEOLOCATION_LOCATION_TYPE_FIELD: location_type
    }
    # Selecting Age
    age_range = input_combination_dictionary[constants.INPUT_AGE_RANGE_FIELD]
    targeting[constants.API_MIN_AGE_FIELD] = age_range[constants.MIN_AGE] if constants.MIN_AGE in age_range else None
    targeting[constants.API_MAX_AGE_FIELD] = age_range[constants.MAX_AGE] if constants.MAX_AGE in age_range else None

    # Selecting genders
    gender = input_combination_dictionary[constants.INPUT_GENDER_FIELD]
    targeting[constants.API_GENDER_FIELD] = [gender]

    # Selecting Languages
    if constants.INPUT_LANGUAGE_FIELD in input_combination_dictionary:
        languages = input_combination_dictionary[constants.INPUT_LANGUAGE_FIELD]
        if languages:
            targeting[constants.API_LANGUAGES_FIELD] = languages["values"]
    else:
        print_warning("No field: " + constants.INPUT_LANGUAGE_FIELD)


def get_api_field_name(field_name):
    return constants.INPUT_TO_API_FIELD_NAME[field_name]


def process_dau_audience_from_response(literal_response):
    aud = json.loads(literal_response)["data"][0]
    audience = aud["estimate_dau"]
    return int(audience)


def process_mau_audience_from_response(literal_response):
    aud = json.loads(literal_response)["data"][0]
    if "estimate_mau" in aud:
        audience=int(aud["estimate_mau"])
    else:
        audience=None
    return audience

def process_mau_upper_audience_from_response(literal_response):
    aud = json.loads(literal_response)["data"][0]
    audience=aud["estimate_mau_upper_bound"]
    return int(audience)


def process_mau_lower_audience_from_response(literal_response):
    aud = json.loads(literal_response)["data"][0]
    audience=aud["estimate_mau_lower_bound"]
    return int(audience)


def post_process_collection(collection_dataframe):
    # For now just capture audience
    print_info("Computing Audience and DAU column")
    collection_dataframe["dau_audience"] = collection_dataframe["response"].apply(
        lambda x: process_dau_audience_from_response(x))

    collection_dataframe["mau_audience"] = collection_dataframe["response"].apply(
        lambda x: process_mau_audience_from_response(x))

    collection_dataframe["mau_audience_upper_bound"] = collection_dataframe["response"].apply(
        lambda x: process_mau_upper_audience_from_response(x))

    collection_dataframe["mau_audience_lower_bound"] = collection_dataframe["response"].apply(
        lambda x: process_mau_lower_audience_from_response(x))
    
    collection_dataframe = add_mocked_column(collection_dataframe)
    return collection_dataframe


def select_advance_targeting_type_array_ids(segment_type, input_value, targeting):
    api_field_name = get_api_field_name(segment_type)
    if input_value:
        if "or" in input_value:
            or_query = []
            for or_id in input_value["or"]:
                or_query.append({"id": or_id})
            targeting["flexible_spec"].append({api_field_name: or_query})

        if "and" in input_value:
            for id_and in input_value["and"]:
                ## TODO: make the behavior AND query request less hacky
                if(segment_type == constants.INPUT_BEHAVIOR_FIELD or segment_type == constants.INPUT_INCOME_FIELD):
                    if(len(targeting['flexible_spec']) == 1):
                        targeting['flexible_spec'].append({api_field_name : []})
                    targeting['flexible_spec'][1][api_field_name].append({"id" : id_and})
                else:
                    targeting["flexible_spec"].append({segment_type: {"id": id_and}})

        if "not" in input_value:
            if not "exclusions" in targeting:
                targeting["exclusions"] = {}
            if not api_field_name in list(targeting["exclusions"].keys()):
                targeting["exclusions"][api_field_name] = []
            for id_not in input_value["not"]:
                targeting["exclusions"][api_field_name].append({"id": id_not})

        if "and_ors" in input_value:
            for or_ids in input_value["and_ors"]:
                or_query = []
                for or_id in or_ids:
                    or_query.append({"id": or_id})
                targeting["flexible_spec"].append({segment_type: or_query})

        if "or" not in input_value and "and" not in input_value and "not" not in input_value and "and_ors" not in input_value:
            raise JsonFormatException("Something wrong with: " + str(input_value))


def get_interests_by_group_to_AND(input_data_json, groups_ids):
    interests_by_group_to_AND = {}
    for group_id in groups_ids:
        interests_by_group_to_AND[group_id] = []
    for interest_input in input_data_json[constants.INPUT_INTEREST_FIELD]:
        if interest_input:
            if constants.GROUP_ID_FIELD in interest_input:
                interest_group_id = interest_input[constants.GROUP_ID_FIELD]
                if interest_group_id in interests_by_group_to_AND:
                    interests_by_group_to_AND[interest_group_id].append(interest_input)
    return interests_by_group_to_AND


def select_advance_targeting_type_array_integer(segment_type, input_value, targeting):
    api_field_name = get_api_field_name(segment_type)
    if input_value:
        if "or" in input_value:
            targeting["flexible_spec"].append({api_field_name: input_value["or"]})
        elif "not" in input_value:
            if not "exclusions" in targeting:
                targeting["exclusions"] = {}
            if not api_field_name in list(targeting["exclusions"].keys()):
                targeting["exclusions"][api_field_name] = []
            for value in input_value["not"]:
                targeting["exclusions"][api_field_name].append(value)
        else:
            raise JsonFormatException("Something wrong with: " + str(input_value))


def select_advance_targeting_fields(targeting, input_combination_dictionary):
    # Selecting Advance Targeting
    targeting["flexible_spec"] = []

    for advance_field in constants.ADVANCE_TARGETING_FIELDS_TYPE_ARRAY_IDS:
        if advance_field in input_combination_dictionary:
            select_advance_targeting_type_array_ids(advance_field, input_combination_dictionary[advance_field],
                                                    targeting)
    for advance_field in constants.ADVANCE_TARGETING_FIELDS_TYPE_ARRAY_INTEGER:
        if advance_field in input_combination_dictionary:
            select_advance_targeting_type_array_integer(advance_field, input_combination_dictionary[advance_field],
                                                        targeting)
    return targeting


def select_publisher_platform(targeting, input_data):
    # Selecting Publisher Platform
    platform = constants.PUBLISHER_PLATFORM_DEFAULT
    if constants.API_PUBLISHER_PLATFORMS_FIELD in input_data:
        platform = input_data[constants.API_PUBLISHER_PLATFORMS_FIELD]
    targeting[constants.API_PUBLISHER_PLATFORMS_FIELD] = platform


def build_targeting(current_combination, input_data):
    targeting = {}
    input_combination_dictionary = dict(current_combination)
    select_common_fields_in_targeting(targeting, input_combination_dictionary)
    select_advance_targeting_fields(targeting, input_combination_dictionary)
    select_publisher_platform(targeting, input_data)
    return targeting


def get_token_and_account_number_or_wait():
    if not "used_tokens_time_map" in globals():
        global used_tokens_time_map
        used_tokens_time_map = {}
    while True:
        for token, account in constants.TOKENS:
            if token in used_tokens_time_map:
                last_used_time = used_tokens_time_map[token]
                time_since_used = time.time() - last_used_time
                if time_since_used > constants.SLEEP_TIME:
                    used_tokens_time_map[token] = time.time()
                    return token, account
            else:
                used_tokens_time_map[token] = time.time()
                return token, account
        time.sleep(1)


def print_collecting_progress(uncomplete_df, df):
    full_size = len(df)
    uncomplete_df_size = len(uncomplete_df)
    print_info(
        "Collecting... Completed: {:.2f}% , {:d}/{:d}".format((float(full_size - uncomplete_df_size) / full_size * 100),
                                                              full_size - uncomplete_df_size, full_size))


def send_dumb_query(token, account, url):
    try:
        row = pd.Series()
        row[constants.TARGETING_FIELD] = constants.DEFAULT_DUMB_TARGETING
        call_request_fb(row, token, account, url)
    except Exception as error:
        print_warning("Token or Account Number Error:")
        print_warning("Token:" + token)
        print_warning("Account:" + account)
        raise error


def from_FB_polygons_to_KML(poly):
    out = ""
    for p in poly:
        out += "<Polygon><outerBoundaryIs><LinearRing><coordinates>"
        for pair in p:
            out += " %s,%s" % (pair["lng"], pair["lat"])
        out += "</coordinates></LinearRing></outerBoundaryIs></Polygon>"
    return out


def double_country_conversion(input):
    mapping = {
        "AD": "Andorra",
        "AE": "United Arab Emirates",
        "AF": "Afghanistan",
        "AG": "Antigua and Barbuda",
        "AL": "Albania",
        "AM": "Armenia",
        "AO": "Angola",
        "AR": "Argentina",
        "AS": "American Samoa",
        "AT": "Austria",
        "AU": "Australia",
        "AW": "Aruba",
        "AZ": "Azerbaijan",
        "BA": "Bosnia and Herzegovina",
        "BB": "Barbados",
        "BD": "Bangladesh",
        "BE": "Belgium",
        "BF": "Burkina Faso",
        "BG": "Bulgaria",
        "BH": "Bahrain",
        "BI": "Burundi",
        "BJ": "Benin",
        "BM": "Bermuda",
        "BN": "Brunei",
        "BO": "Bolivia",
        "BR": "Brazil",
        "BS": "Bahamas",
        "BT": "Bhutan",
        "BW": "Botswana",
        "BY": "Belarus",
        "BZ": "Belize",
        "CA": "Canada",
        "CD": "Congo Dem. Rep.",
        "CF": "Central African Republic",
        "CG": "Congo Rep.",
        "CH": "Switzerland",
        "CI": "Cote d'Ivoire",
        "CK": "Cook Islands",
        "CL": "Chile",
        "CM": "Cameroon",
        "CN": "China",
        "CO": "Colombia",
        "CR": "Costa Rica",
        "CV": "Cape Verde",
        "CW": "Curacao",
        "CY": "Cyprus",
        "CZ": "Czech Republic",
        "DE": "Germany",
        "DJ": "Djibouti",
        "DK": "Denmark",
        "DM": "Dominica",
        "DO": "Dominican Republic",
        "DZ": "Algeria",
        "EC": "Ecuador",
        "EE": "Estonia",
        "EG": "Egypt",
        "EH": "Western Sahara",
        "ER": "Eritrea",
        "ES": "Spain",
        "ET": "Ethiopia",
        "FI": "Finland",
        "FJ": "Fiji",
        "FK": "Falkland Islands",
        "FM": "Micronesia",
        "FO": "Faroe Islands",
        "FR": "France",
        "GA": "Gabon",
        "GB": "United Kingdom",
        "GD": "Grenada",
        "GE": "Georgia",
        "GF": "French Guiana",
        "GG": "Guernsey",
        "GH": "Ghana",
        "GI": "Gibraltar",
        "GL": "Greenland",
        "GM": "Gambia",
        "GN": "Guinea-Bissau",
        "GP": "Guadeloupe",
        "GQ": "Equatorial Guinea",
        "GR": "Greece",
        "GT": "Guatemala",
        "GU": "Guam",
        "GW": "Guinea",
        "GY": "Guyana",
        "HK": "Hong Kong",
        "HN": "Honduras",
        "HR": "Croatia",
        "HT": "Haiti",
        "HU": "Hungary",
        "ID": "Indonesia",
        "IE": "Ireland",
        "IL": "Israel",
        "IM": "Isle of Man",
        "IN": "India",
        "IQ": "Iraq",
        "IR": "Iran",
        "IS": "Iceland",
        "IT": "Italy",
        "JE": "Jersey",
        "JM": "Jamaica",
        "JO": "Jordan",
        "JP": "Japan",
        "KE": "Kenya",
        "KG": "Kyrgyzstan",
        "KH": "Cambodia",
        "KI": "Kiribati",
        "KM": "Comoros",
        "KN": "Saint Kitts and Nevis",
        "KR": "South Korea",
        "KW": "Kuwait",
        "KY": "Cayman Islands",
        "KZ": "Kazakhstan",
        "LA": "Laos",
        "LB": "Lebanon",
        "LC": "Saint Lucia",
        "LI": "Liechtenstein",
        "LK": "Sri Lanka",
        "LR": "Liberia",
        "LS": "Lesotho",
        "LT": "Lithuania",
        "LU": "Luxembourg",
        "LV": "Latvia",
        "LY": "Libya",
        "MA": "Morocco",
        "MC": "Monaco",
        "MD": "Moldova",
        "ME": "Montenegro",
        "MF": "Saint Martin",
        "MG": "Madagascar",
        "MH": "Marshall Islands",
        "MK": "Macedonia",
        "ML": "Mali",
        "MM": "Myanmar",
        "MN": "Mongolia",
        "MO": "Macau",
        "MP": "Northern Mariana Islands",
        "MQ": "Martinique",
        "MR": "Mauritania",
        "MS": "Montserrat",
        "MT": "Malta",
        "MU": "Mauritius",
        "MV": "Maldives",
        "MW": "Malawi",
        "MX": "Mexico",
        "MY": "Malaysia",
        "MZ": "Mozambique",
        "NA": "Namibia",
        "NC": "New Caledonia",
        "NE": "Niger",
        "NF": "Norfolk Island",
        "NG": "Nigeria",
        "NI": "Nicaragua",
        "NL": "Netherlands",
        "NO": "Norway",
        "NP": "Nepal",
        "NR": "Nauru",
        "NU": "Niue",
        "NZ": "New Zealand",
        "OM": "Oman",
        "PA": "Panama",
        "PE": "Peru",
        "PF": "French Polynesia",
        "PG": "Papua New Guinea",
        "PH": "Philippines",
        "PK": "Pakistan",
        "PL": "Poland",
        "PM": "Saint Pierre and Miquelon",
        "PN": "Pitcairn",
        "PR": "Puerto Rico",
        "PS": "Palestine",
        "PT": "Portugal",
        "PW": "Palau",
        "PY": "Paraguay",
        "QA": "Qatar",
        "RE": "Reunion",
        "RO": "Romania",
        "RS": "Serbia",
        "RU": "Russia",
        "RW": "Rwanda",
        "SA": "Saudi Arabia",
        "SB": "Solomon Islands",
        "SC": "Seychelles",
        "SE": "Sweden",
        "SG": "Singapore",
        "SH": "Saint Helena",
        "SI": "Slovenia",
        "SJ": "Svalbard and Jan Mayen",
        "SK": "Slovakia",
        "SL": "Sierra Leone",
        "SM": "San Marino",
        "SN": "Senegal",
        "SO": "Somalia",
        "SR": "Suriname",
        "SS": "South Sudan",
        "ST": "Sao Tome and Principe",
        "SV": "El Salvador",
        "SY": "Syria",
        "SX": "Sint Maarten",
        "SZ": "Swaziland",
        "TC": "Turks and Caicos Islands",
        "TD": "Chad",
        "TG": "Togo",
        "TH": "Thailand",
        "TJ": "Tajikistan",
        "TK": "Tokelau",
        "TL": "Timor-Leste",
        "TM": "Turkmenistan",
        "TN": "Tunisia",
        "TO": "Tonga",
        "TR": "Turkey",
        "TT": "Trinidad and Tobago",
        "TV": "Tuvalu",
        "TW": "Taiwan",
        "TZ": "Tanzania",
        "UA": "Ukraine",
        "UG": "Uganda",
        "US": "United States",
        "UY": "Uruguay",
        "UZ": "Uzbekistan",
        "VC": "Saint Vincent and the Grenadines",
        "VE": "Venezuela",
        "VG": "British Virgin Islands",
        "VI": "US Virgin Islands",
        "VN": "Vietnam",
        "VU": "Vanuatu",
        "WF": "Wallis and Futuna",
        "WS": "Samoa",
        "XK": "Kosovo",
        "YE": "Yemen",
        "YT": "Mayotte",
        "ZA": "South Africa",
        "ZM": "Zambia",
        "ZW": "Zimbabwe",
    }

    if input in mapping:
        return mapping[input]

    inverted = dict(zip(mapping.values(), mapping.keys()))

    if input in inverted:
        return inverted[input]

    return None


def get_all_countries(two_letters_code=True):
    if two_letters_code:
        return ['AD', 'AE', 'AF', 'AG', 'AL', 'AM', 'AO', 'AR', 'AS', 'AT', 'AU', 'AW', 'AZ', 'BA', 'BB', 'BD', 'BE',
                'BF', 'BG', 'BH', 'BI', 'BJ', 'BM', 'BN', 'BO', 'BR', 'BS', 'BT', 'BW', 'BY', 'BZ', 'CA', 'CD', 'CF',
                'CG', 'CH', 'CI', 'CK', 'CL', 'CM', 'CN', 'CO', 'CR', 'CV', 'CW', 'CY', 'CZ', 'DE', 'DJ', 'DK', 'DM',
                'DO', 'DZ', 'EC', 'EE', 'EG', 'EH', 'ER', 'ES', 'ET', 'FI', 'FJ', 'FK', 'FM', 'FO', 'FR', 'GA', 'GB',
                'GD', 'GE', 'GF', 'GG', 'GH', 'GI', 'GL', 'GM', 'GN', 'GP', 'GQ', 'GR', 'GT', 'GU', 'GW', 'GY', 'HK',
                'HN', 'HR', 'HT', 'HU', 'ID', 'IE', 'IL', 'IM', 'IN', 'IQ', 'IS', 'IT', 'JE', 'JM', 'JO', 'JP',
                'KE', 'KG', 'KH', 'KI', 'KM', 'KN', 'KR', 'KW', 'KY', 'KZ', 'LA', 'LB', 'LC', 'LI', 'LK', 'LR', 'LS',
                'LT', 'LU', 'LV', 'LY', 'MA', 'MC', 'MD', 'ME', 'MF', 'MG', 'MH', 'MK', 'ML', 'MM', 'MN', 'MO', 'MP',
                'MQ', 'MR', 'MS', 'MT', 'MU', 'MV', 'MW', 'MX', 'MY', 'MZ', 'NA', 'NC', 'NE', 'NF', 'NG', 'NI', 'NL',
                'NO', 'NP', 'NR', 'NU', 'NZ', 'OM', 'PA', 'PE', 'PF', 'PG', 'PH', 'PK', 'PL', 'PM', 'PN', 'PR', 'PS',
                'PT', 'PW', 'PY', 'QA', 'RE', 'RO', 'RS', 'RU', 'RW', 'SA', 'SB', 'SC', 'SE', 'SG', 'SH', 'SI', 'SJ',
                'SK', 'SL', 'SM', 'SN', 'SO', 'SR', 'SS', 'ST', 'SV', 'SX', 'SZ', 'TC', 'TD', 'TG', 'TH', 'TJ',
                'TK', 'TL', 'TM', 'TN', 'TO', 'TR', 'TT', 'TV', 'TW', 'TZ', 'UA', 'UG', 'US', 'UY', 'UZ', 'VC', 'VE',
                'VG', 'VI', 'VN', 'VU', 'WF', 'WS', 'XK', 'YT', 'ZA', 'ZM', 'ZW']
    else:
        return ['Andorra', 'United Arab Emirates', 'Afghanistan', 'Antigua and Barbuda', 'Albania', 'Armenia', 'Angola',
                'Argentina', 'American Samoa', 'Austria', 'Australia', 'Aruba', 'Azerbaijan', 'Bosnia and Herzegovina',
                'Barbados', 'Bangladesh', 'Belgium', 'Burkina Faso', 'Bulgaria', 'Bahrain', 'Burundi', 'Benin',
                'Bermuda', 'Brunei', 'Bolivia', 'Brazil', 'Bahamas', 'Bhutan', 'Botswana', 'Belarus', 'Belize',
                'Canada', 'Congo Dem. Rep.', 'Central African Republic', 'Congo Rep.', 'Switzerland', "Cote d'Ivoire",
                'Cook Islands', 'Chile', 'Cameroon', 'China', 'Colombia', 'Costa Rica', 'Cape Verde', 'Curacao',
                'Cyprus', 'Czech Republic', 'Germany', 'Djibouti', 'Denmark', 'Dominica', 'Dominican Republic',
                'Algeria', 'Ecuador', 'Estonia', 'Egypt', 'Western Sahara', 'Eritrea', 'Spain', 'Ethiopia', 'Finland',
                'Fiji', 'Falkland Islands', 'Micronesia', 'Faroe Islands', 'France', 'Gabon', 'United Kingdom',
                'Grenada', 'Georgia', 'French Guiana', 'Guernsey', 'Ghana', 'Gibraltar', 'Greenland', 'Gambia',
                'Guinea-Bissau', 'Guadeloupe', 'Equatorial Guinea', 'Greece', 'Guatemala', 'Guam', 'Guinea', 'Guyana',
                'Hong Kong', 'Honduras', 'Croatia', 'Haiti', 'Hungary', 'Indonesia', 'Ireland', 'Israel', 'Isle of Man',
                'India', 'Iraq', 'Iceland', 'Italy', 'Jersey', 'Jamaica', 'Jordan', 'Japan', 'Kenya',
                'Kyrgyzstan', 'Cambodia', 'Kiribati', 'Comoros', 'Saint Kitts and Nevis', 'South Korea', 'Kuwait',
                'Cayman Islands', 'Kazakhstan', 'Laos', 'Lebanon', 'Saint Lucia', 'Liechtenstein', 'Sri Lanka',
                'Liberia', 'Lesotho', 'Lithuania', 'Luxembourg', 'Latvia', 'Libya', 'Morocco', 'Monaco', 'Moldova',
                'Montenegro', 'Saint Martin', 'Madagascar', 'Marshall Islands', 'Macedonia', 'Mali', 'Myanmar',
                'Mongolia', 'Macau', 'Northern Mariana Islands', 'Martinique', 'Mauritania', 'Montserrat', 'Malta',
                'Mauritius', 'Maldives', 'Malawi', 'Mexico', 'Malaysia', 'Mozambique', 'Namibia', 'New Caledonia',
                'Niger', 'Norfolk Island', 'Nigeria', 'Nicaragua', 'Netherlands', 'Norway', 'Nepal', 'Nauru', 'Niue',
                'New Zealand', 'Oman', 'Panama', 'Peru', 'French Polynesia', 'Papua New Guinea', 'Philippines',
                'Pakistan', 'Poland', 'Saint Pierre and Miquelon', 'Pitcairn', 'Puerto Rico', 'Palestine', 'Portugal',
                'Palau', 'Paraguay', 'Qatar', 'Reunion', 'Romania', 'Serbia', 'Russia', 'Rwanda', 'Saudi Arabia',
                'Solomon Islands', 'Seychelles', 'Sweden', 'Singapore', 'Saint Helena', 'Slovenia',
                'Svalbard and Jan Mayen', 'Slovakia', 'Sierra Leone', 'San Marino', 'Senegal', 'Somalia', 'Suriname',
                'South Sudan', 'Sao Tome and Principe', 'El Salvador', 'Sint Maarten', 'Swaziland',
                'Turks and Caicos Islands', 'Chad', 'Togo', 'Thailand', 'Tajikistan', 'Tokelau', 'Timor-Leste',
                'Turkmenistan', 'Tunisia', 'Tonga', 'Turkey', 'Trinidad and Tobago', 'Tuvalu', 'Taiwan', 'Tanzania',
                'Ukraine', 'Uganda', 'United States', 'Uruguay', 'Uzbekistan', 'Saint Vincent and the Grenadines',
                'Venezuela', 'British Virgin Islands', 'US Virgin Islands', 'Vietnam', 'Vanuatu', 'Wallis and Futuna',
                'Samoa', 'Kosovo', 'Mayotte', 'South Africa', 'Zambia', 'Zimbabwe']
