import logging

import simplejson
from pyhamtools.frequency import freq_to_band
from pyhamtools.locator import latlong_to_locator

from core.constants import UNKNOWN_BAND, BANDS, CW_MODES, PHONE_MODES, DATA_MODES, MODE_ALIASES, ALL_MODES
from core.data_store import DATA_STORE
from data.callsign import Callsign


def safe_json_dumps(obj):
    """Safe version of json.dumps that also converts objects to dicts so they can be output, and ignores NaN floats
    which are invalid in JSON."""

    return simplejson.dumps(obj, ensure_ascii=False, ignore_nan=True, default=lambda o: o.__dict__)


def infer_mode_from_comment(comment):
    """Infer a mode from the comment"""

    for mode in ALL_MODES:
        if mode in comment.upper():
            return mode
    for mode in MODE_ALIASES.keys():
        if mode in comment.upper():
            return MODE_ALIASES[mode]
    return None


def infer_mode_type_from_mode(mode):
    """Infer a "mode family" from a mode."""

    if mode.upper() in CW_MODES:
        return "CW"
    elif mode.upper() in PHONE_MODES:
        return "PHONE"
    elif mode.upper() in DATA_MODES:
        return "DATA"
    else:
        if mode.upper() != "OTHER":
            logging.warning("Found an unrecognised mode: " + mode + ". Developer should categorise this.")
        return None


def infer_band_from_freq(freq):
    """Infer a band from a frequency in Hz"""

    for b in BANDS:
        if b.start_freq <= freq <= b.end_freq:
            return b
    return UNKNOWN_BAND


def infer_mode_from_frequency(freq):
    """Infer a mode from the frequency (in Hz) according to the band plan. Just a guess really."""

    try:
        khz = freq / 1000.0
        mode = freq_to_band(khz)["mode"]
        # Some additional common digimode ranges in addition to what the 3rd-party freq_to_band function returns.
        # This is mostly here just because freq_to_band is very specific about things like FT8 frequencies, and e.g.
        # a spot at 7074.5 kHz will be indicated as LSB, even though it's clearly in the FT8 range. Future updates
        # might include other common digimode centres of activity here, but this achieves the main goal of keeping
        # large numbers of clearly-FT* spots off the list of people filtering out digimodes.
        if (7074 <= khz < 7077) or (10136 <= khz < 10139) or (14074 <= khz < 14077) or (18100 <= khz < 18103) or (
                21074 <= khz < 21077) or (24915 <= khz < 24918) or (28074 <= khz < 28077):
            mode = "FT8"
        if (7047.5 <= khz < 7050.5) or (10140 <= khz < 10143) or (14080 <= khz < 14083) or (
                18104 <= khz < 18107) or (21140 <= khz < 21143) or (24919 <= khz < 24922) or (28180 <= khz < 28183):
            mode = "FT4"
        return mode
    except KeyError:
        return None


def get_flag_for_dxcc(dxcc):
    """Get an emoji flag for a given DXCC entity ID"""

    dxcc_data = DATA_STORE.dxcc_data[dxcc] if dxcc in DATA_STORE.dxcc_data else None
    return dxcc_data["flag"] if dxcc_data else None


def get_callsign_object_from_pyhamtools_callinfo(callsign, callinfo):
    """Utility function to take the data provided by a PyHamTools CallInfo object and populate our own Callsign data
    object from it"""

    try:
        home_call = callinfo.get_homecall(callsign)
        data = callinfo.get_all(callsign)

        country = data["country"] if "country" in data else None
        dxcc_id = data["adif"] if "adif" in data else None
        continent = data["continent"] if "continent" in data else None
        cq_zone = data["cqz"] if "cqz" in data else None
        itu_zone = data["ituz"] if "ituz" in data else None
        lat = float(data["latitude"]) if "latitude" in data else None
        lon = float(data["longitude"]) if "longitude" in data else None
        grid = None
        if lat and lon:
            grid = latlong_to_locator(lat, lon)

        return Callsign(call=callsign,
                        home_call=home_call,
                        country=country,
                        dxcc_id=dxcc_id,
                        continent=continent,
                        cq_zone=cq_zone,
                        itu_zone=itu_zone,
                        latitude=lat,
                        longitude=lon,
                        grid=grid,
                        location_source="DXCC")

    except (KeyError, ValueError):
        # Unknown callsign, can't look anything up, return a Callsign object with basic data so that gets cached
        return Callsign(call=callsign)
