"""Follow the live client's CURRENT singular entity, never arbitrary heap hits."""
import struct


def _class(reader, base, entry):
    for rva in entry['class_slot_rvas']:
        try:
            klass = reader.u64(base + rva)
            if reader.class_name(klass) == (entry['name'], entry['namespace']):
                return klass
        except (OSError, ValueError, UnicodeError):
            continue
    raise RuntimeError('현재 캐릭터 문맥의 클래스가 준비되지 않았습니다: ' + entry['name'])


def _fields(reader, klass, entry):
    # Large generated containers have >2500 fields. Inspect only these exact
    # file-derived indexes, checking count, name, declaring class and offset.
    if reader.u16(klass + 0x120) != entry['field_count']:
        raise ValueError('현재 캐릭터 문맥의 필드 구성이 변경되었습니다.')
    table = reader.u64(klass + 0x80)
    result = {}
    for field in entry['fields']:
        name, kind, owner, offset, _ = struct.unpack('<QQQiI', reader.read(table + 32 * field['index'], 32))
        if reader.ascii(name) != field['name'] or owner != klass or offset != field['offset']:
            raise ValueError('현재 캐릭터 문맥의 필드를 확인하지 못했습니다: ' + field['name'])
        if ((reader.u32(kind + 8) >> 16) & 255) not in (18, 21, 28):
            raise ValueError('현재 캐릭터 문맥의 참조 타입이 변경되었습니다.')
        result[field['name']] = offset
    return result


def active_context(reader, base, plan):
    guard=plan['context'].get('patch_guard')
    if guard:
        wrapper=reader.u64(base+guard['wrapper_slot_rva'])
        if reader.class_name(wrapper)!=('ILFixDynamicMethodWrapper','IFix'):
            raise ValueError('현재 캐릭터 getter의 패치 상태를 확인하지 못했습니다.')
        array=reader.u64(reader.u64(wrapper+0xb8))
        count=reader.u64(array+24)
        if count>100000 or any(count>slot and reader.u64(array+32+slot*8) for slot in guard['patch_slots']):
            raise ValueError('현재 캐릭터 getter가 변경되어 내부 읽기를 확정할 수 없습니다.')
    entries = {r['name']: r for r in plan['context']['classes']}
    classes = {name: _class(reader, base, row) for name, row in entries.items()}
    fields = {name: _fields(reader, classes[name], row) for name, row in entries.items()}
    statics = reader.u64(classes['IntegrationSingleton'] + 0xb8)
    singleton = reader.u64(statics + fields['IntegrationSingleton']['instance'])
    if reader.u64(singleton) != classes['IntegrationSingleton']:
        raise RuntimeError('현재 게임의 조작 문맥이 준비되지 않았습니다.')
    system = reader.u64(singleton + fields['IntegrationSingleton']['<PresentationContracts>k__BackingField'])
    if reader.u64(system) != classes['SystemContainers']:
        raise RuntimeError('현재 게임 시스템 문맥을 확인하지 못했습니다.')
    entity = reader.u64(system + fields['SystemContainers']['SingularEntity'])
    if reader.u64(entity) != classes['SingularEntity']:
        raise RuntimeError('현재 캐릭터로 접속한 뒤 확인해 주세요.')
    # GetI... loads the interface slot, not the adjacent concrete slot. The
    # referenced object's concrete class is checked by the payload reader.
    identity = reader.u64(entity + fields['SingularEntity']['IControlObjectIdentityComponent'])
    quest = reader.u64(entity + fields['SingularEntity']['IQuestComponent'])
    return dict(identity_pointer=identity, quest_pointer=quest, chain=(statics, singleton, system, entity, identity, quest))
