"""Current shared MMML layout through the rooted UI handle table.

No selected marker, active detail panel, HUD cache, heap scan, input or client
call is needed. Every discovered service must agree. Open generic offsets are
read by exact current field names, never copied as the native zero offsets.
"""
from .map_resources import load_current_layout_rule


def read_current_layout(session, class_resolver):
    r=session.reader
    rule=load_current_layout_rule()
    if any(rule[k]!=session.plan[k] for k in ('metadata_sha256','game_assembly_sha256')):
        raise ValueError('map_layout_rule_version_mismatch')

    def pointer(at):
        session.watch(at,8)
        return r.u64(at)

    def named_fields(klass, entry):
        if r.class_name(klass)!=(entry['name'],entry['namespace']):
            raise ValueError('map_layout_named_class_changed')
        shape=r.layout(klass,entry['field_names'])
        return {f['name']:f for f in shape['fields']}

    singleton=session.schema('PresentationSingleton')
    klass=class_resolver(r,session.base,singleton)
    f=session.fields(klass,singleton)
    statics=pointer(klass+0xb8)
    instance=pointer(statics+f['instance']) if statics else 0
    if not instance or r.u64(instance)!=klass:
        raise ValueError('current_presentation_service_not_ready')
    service=pointer(instance+f['<UIInstanceService>k__BackingField'])
    f=session.object_fields(service,'UIInstanceService')
    table=pointer(service+f['handleTable'])
    f=session.object_fields(table,'UIInstanceHandleTable')
    dictionary=pointer(table+f['handleTable'])
    mmml_services=set()
    source_kinds=set()
    ready_instances=0
    for _,handle in session.dictionary_values(dictionary,limit=session.plan['limits']['instances']):
        if not handle or r.class_name(r.u64(handle))!=('UIInstanceHandle',''):
            continue
        fields=named_fields(r.u64(handle),rule['generic_instance_handle'])
        status=fields['<Status>k__BackingField'];target=fields['<Instance>k__BackingField']
        if status['offset']<16 or target['offset']<16 or status['type_code']!=17:
            raise ValueError('current_ui_handle_layout_changed')
        session.watch(handle+status['offset'],4)
        if r.i32(handle+status['offset'])!=2:continue
        ui=pointer(handle+target['offset'])
        if not ui:continue
        ready_instances+=1
        ui_class=pointer(ui)
        ui_name=r.class_name(ui_class)
        mmml_handle=0
        source_kind=None
        if ui_name==('MapLocationPanelUIInstance','MM.Client.Presentation.UI.Instances.StageScene'):
            fields=session.object_fields(ui,'MapLocationPanelUIInstance')
            controller=pointer(ui+fields['controller'])
            if not controller:continue
            session.object_fields(controller,'MapLocationUIController')
            parent=pointer(r.u64(controller)+0x58)
            base=session.fields(parent,session.schema('MMMLUIControllerBase'))
            mmml_handle=pointer(controller+base['handle'])
            source_kind='current MapLocationPanelUIInstance controller'
        else:
            # Shared layout is also available through an already created
            # MMML stackable instance. It need not be visible/selected.
            seen=set()
            for _ in range(8):
                if not ui_class or ui_class in seen:break
                seen.add(ui_class)
                if r.class_name(ui_class)==(rule['generic_stackable']['name'],rule['generic_stackable']['namespace']):
                    fields=named_fields(ui_class,rule['generic_stackable'])
                    field=fields[rule['generic_stackable']['handle_field']]
                    if field['type_code']!=18 or field['offset']<16:
                        raise ValueError('current_mmml_stackable_handle_layout_changed')
                    mmml_handle=pointer(ui+field['offset'])
                    source_kind='current rooted MMMLStackableUIInstanceBase handle'
                    break
                ui_class=pointer(ui_class+0x58)
        if not mmml_handle:continue
        fields=session.object_fields(mmml_handle,'MMMLUIHandle')
        mmml_service=pointer(mmml_handle+fields['service'])
        if not mmml_service:continue
        session.object_fields(mmml_service,'MMMLService')
        mmml_services.add(mmml_service)
        source_kinds.add(source_kind)
    if len(mmml_services)!=1:
        raise ValueError('current_shared_mmml_service_not_unique:'+str(len(mmml_services))+
                         ';ready_ui_instances='+str(ready_instances))
    service=next(iter(mmml_services))
    fields=session.object_fields(service,'MMMLService')
    for name,size in (('global_ScreenSize',8),('global_Orientation',4),('global_SafeArea',16),
                      ('lastReferenceResolutionScale',4),('<IsInitialized>k__BackingField',1)):
        session.watch(service+fields[name],size)
    if not session.bool(service+fields['<IsInitialized>k__BackingField']):
        raise ValueError('current_mmml_service_not_initialized')
    safe=service+fields['global_SafeArea']
    return dict(source='current rooted MMMLService globals',certainty='confirmed',
        canvas_scale=session.float(service+fields['lastReferenceResolutionScale']),
        screen_ui=session.pair(service+fields['global_ScreenSize']),
        orientation_raw=r.i32(service+fields['global_Orientation']),
        anchor_min=session.pair(safe+rule['safe_area']['anchor_min_offset']),
        anchor_max=session.pair(safe+rule['safe_area']['anchor_max_offset']),
        service_pointer=hex(service),source_kinds=sorted(source_kinds),
        field_guarded=True,selected_marker_required=False,
        source_refs=rule['source_refs'],safe_area_basis=rule['safe_area']['layout_basis'])
