import logging
import datetime
import string
import pprint


INT_TIMESTAMP = False


def jobs_diff(jobs_list):
    for job in jobs_list:
        job['id']


def camel_to_snake_case(text):
    result_text = ''
    if isinstance(text, str):
        for char in text:
            if char in string.ascii_uppercase:
                if not result_text.endswith('_'):
                    result_text += '_'
                char = char.lower()
            result_text += char
    return result_text.strip('_')


def hp_job_status_to_job_status(status):
    #return camel_to_snake_case(status)
    status = camel_to_snake_case(status)
    if status == "job_warming_up":
        status = "warming"
    elif status == "job_preparing_to_print":
        status = "preparing"
    elif status == "job_completed_succeeded":
        status = "completed"
    else:
        status = status.replace("job_", "")
    return status


def iso_time_to_unix_ts(iso_time_str):
    fmt_str = r"%Y-%m-%dT%H:%M:%SZ"
    #iso_time_str = iso_time_str.strip('Z')
    try:
        #datetime_obj = datetime.datetime.fromisoformat(iso_time_str)
        datetime_obj = datetime.datetime.strptime(iso_time_str, fmt_str)
    except (ValueError, TypeError):
        return 0.0
    else:
        ts = datetime_obj.timestamp()
        if INT_TIMESTAMP:
            ts = int(ts)
        return ts


def iso_duration_to_sec(iso_duration):
    def get_isosplit(dur_str, split_char):
        split_tuple = dur_str.split(split_char, maxsplit=1)
        if len(split_tuple) == 1:
            return 0, dur_str
        else:
            try:
                num = int(split_tuple[0])
            except (TypeError, ValueError):
                num = 0
            return num, split_tuple[1]
        
    iso_duration = iso_duration.strip('P')
    years, iso_duration = get_isosplit(iso_duration, 'Y')
    months, iso_duration = get_isosplit(iso_duration, 'M')
    days, iso_duration = get_isosplit(iso_duration, 'D')
    days += 30 * months + 365 * years
    iso_duration = iso_duration.strip('T')
    hours, iso_duration = get_isosplit(iso_duration, 'H')
    minutes, iso_duration = get_isosplit(iso_duration, 'M')
    seconds = get_isosplit(iso_duration, 'S')[0]
    try:
        duration = datetime.timedelta(days=days, hours=hours, minutes=minutes, seconds=seconds).total_seconds()
    except:
        return 0
    else:
        return duration


def unit_and_value_dict_to_str(unit_and_value_dict):
    value = float(unit_and_value_dict.get('Value', 0.0))
    units = unit_and_value_dict.get('Units', '')
    return {'value': value, 'units': units}


def get_material_from_dict(all_materials):
    materials = {}
    if not isinstance(all_materials, list):
        all_materials = [all_materials]
    for material_dict in all_materials:
        material_name = material_dict.get('Name', {}).get('#text', '')
        material_id = material_dict.get('ID', '')
        material_donor_id = material_dict.get('DonorID', '')
        materials[material_id] = {'name': material_name, 'donor_id': material_donor_id}
    return materials

def get_profile_from_dict(all_profiles):
    profiles = {}
    if not isinstance(all_profiles, list):
        all_profiles = [all_profiles]
    for profile_dict in all_profiles:
        profile_name = profile_dict.get('Name', {}).get('#text', '')
        profile_id = profile_dict.get('ID', '')
        profiles[profile_id] = {'name': profile_name}
    return profiles


def string_float_to_int(float_string):
    try:
        return int(float(float_string))
    except ValueError:
        return 0

def usage_data_to_dict(usage):
    data = {'agents': [], 'material': {}, 'material_disposition': {}}
    if isinstance(usage.get('Agent'), list):
        for agent in usage.get('Agent'):
            data.get('agents').append({
                'type': agent.get('InkAgentType', ''),
                'color': agent.get('Color', ''),
                'total': unit_and_value_dict_to_str(agent.get('Total', {}))
            })
    material = usage.get('Material', {})
    if material.get('ID') is not None:
        material_id = int(material.get('ID'))
        data.get('material')[material_id] = unit_and_value_dict_to_str(material.get('Total', {}))
    material_disposition = usage.get('MaterialDisposition', {})
    if material_disposition.get('ID') is not None:
        material_id = int(material_disposition.get('ID'))
        data.get('material_disposition')[material_id] = {
            'in_parts': unit_and_value_dict_to_str(material_disposition.get('InParts', {})),
            'recyclable': unit_and_value_dict_to_str(material_disposition.get('Recyclable', {}))
        }
    return data


#TODO Create a proper base class for all jobs dicts
class JobReport(dict):
    pass


class HPJobReport(JobReport):

    FIELDS_CONVERSION_MAP = {'Job_ID': ('id', None),
                              'Status': (None, hp_job_status_to_job_status),
                              'CreationDate': (None, iso_time_to_unix_ts),
                              'ScheduledDate': (None, iso_time_to_unix_ts),
                              'StartDate': (None, iso_time_to_unix_ts),
                              'PrintedDate': (None, iso_time_to_unix_ts),
                              #'CoolingStartDate': (None, iso_time_to_unix_ts),
                              #'CooledDate': (None, iso_time_to_unix_ts),
                              'CompletionDate': (None, iso_time_to_unix_ts),
                              'ElapsedTime': (None, iso_duration_to_sec),
                              #'PrintingSessionUUID': ('print_session_uuid', None),
                              'Name': (None, None),
                              #'SourceHost': (None, None),
                              #'UserID': ("user_id", None),
                              'Volume': (None, unit_and_value_dict_to_str),
                              #'SurfaceArea': (None, unit_and_value_dict_to_str),
                              'Density': (None, unit_and_value_dict_to_str),
                              'FusedMaterialDensity': (None, unit_and_value_dict_to_str),
                              'Material': (None, get_material_from_dict),
                              'PrintProfile': (None, get_profile_from_dict),
                              'CoolingProfile': (None, get_profile_from_dict),
                              'AnnealingProfile': (None, get_profile_from_dict),
                              'WarmingUpProfile': (None, get_profile_from_dict),
                              'UnpackingProfile': (None, get_profile_from_dict),
                              'Layers': (None, int),
                              'ZStart': (None, unit_and_value_dict_to_str),
                              'ZEnd': (None, unit_and_value_dict_to_str),
                              'ZPlatformOffset': (None, unit_and_value_dict_to_str),
                              'PrintingCurrentJobLayer': ("current_layer", int),
                              'PrintingProgress': ("progress", string_float_to_int),
                              'CoolingProgress': ("cooling_progress", string_float_to_int),
                              'EstimatedJobRemainingTime': ("remaining_time", iso_duration_to_sec),
                              'EstimatedPrintingRemainingTime': ("remaining_printing_time", iso_duration_to_sec),
                              'EstimatedCoolingRemainingTime': ("remaining_cooling_time", iso_duration_to_sec),
                              'Trolley_ID': ("trolley_id", str),
                              'TrolleyBed_ID': ("trolley_bed_id", int),
                              'Usage': ("usage", usage_data_to_dict)
                              }

    def __init__(self, job_data_dict=None):
        self.logger = logging.getLogger(__class__.__name__)
        if job_data_dict:
            self.convert(job_data_dict)

    def convert(self, input_job_dict):
        for field_name in self.FIELDS_CONVERSION_MAP:
            field = self.FIELDS_CONVERSION_MAP[field_name]
            name_func = None
            value_func = None
            if isinstance(field, tuple):
                if len(field) == 2: #TODO think about adding type for validation as third arg?
                    name_func, value_func = field
                if len(field) == 1:
                    name_func = field
            if not name_func:
                name_func = camel_to_snake_case 
            if callable(name_func):
                try:
                    name = name_func(field_name)
                except:
                    self.logger.warning('Invalid field name processing: %s' % field_name)
                if not name:
                    continue
            elif isinstance(name_func, str):
                name = name_func
            else:
                continue
            #print(("Original dict", input_job_dict))
            original_value = input_job_dict.get(field_name)
            if original_value == None:
                self.logger.debug('HP job lacks field %s' % field_name)
                continue
            if callable(value_func):
                try:
                    value = value_func(original_value)
                except:
                    self.logger.exception('Invalid data in field: %s. Value:%s' % (field_name, original_value))
                    #self.logger.warning('Invalid data in field: %s. Value:%s' % (field_name, original_value))
            elif isinstance(value_func, dict):
                pass
            elif isinstance(value_func, list):
                pass
            else:
                value = original_value
            self[name] = value
        return dict(self)

    def convert_from_hp_job(self, input_job_dict):
        self.logger.debug("Processing HP job dict: %s" % pprint.pformat(input_job_dict))
        self.convert(input_job_dict)
        return self
