import cocoa.objc.platform.*;
import cocoa.objc.runtime.*;
import identifiers.*;
public import cocoa.objc.runtime.(
    Id as ObjectHandle,
    Class as ClassHandle,
    nil as nilHandle,
    SEL as SelectorHandle,
    BOOL, YES, NO, IMP
);



//
// class, selector, and ivar data
//

private define objcClassMake;
private define objcInitIvar;
private define objcInitIvarOffset;
private var objcClass[Class] = objcClassMake(Class);

private var objcSelector[name] = sel_registerName(cstring(StaticName(name)));

private var objcIvar[Class, ivarName]
    = objcInitIvar(Class, ivarName);

private var objcIvarOffset[Class, ivarName]
    = objcInitIvarOffset(Class, ivarName);

private var objcConstantString[value]
    = CFConstantString(
        Class(&__CFConstantStringClassReference),
        CFConstantStringID,
        cstring(StaticName(value)),
        IdentifierSize(value)
    );



//
// constant NSStrings
//

[value | Identifier?(value)]
NSSTR(static value) = ObjectHandle(&objcConstantString[value]);



//
// encode
//

define Void; // stand-in symbol for "no return value"

define encodeIdentifier;

overload encodeIdentifier() = #"";

[X] overload encodeIdentifier(static X) = #"?";
[T] overload encodeIdentifier(static Pointer[T])
    = IdentifierConcat(#"^", encodeIdentifier(T));

// XXX distinguish raw pointer from Pointer[UInt8]
overload encodeIdentifier(static RawPointer) = #"^v";

[T | Record?(T)]
overload encodeIdentifier(static Pointer[Pointer[T]])
    = IdentifierConcat(#"^^{", IdentifierStaticName(T), #"}");
[T | Tuple?(T)]
overload encodeIdentifier(static Pointer[Pointer[T]]) = #"^^{Tuple}";
[T | Union?(T)]
overload encodeIdentifier(static Pointer[Pointer[T]]) = #"^^(Union)";

[T | RegularRecord?(T)] overload encodeIdentifier(static T)
    = IdentifierConcat(
        #"{", IdentifierStaticName(T), #"=",
        encodeIdentifier(..RecordFieldTypes(T)),
        #"}"
    );
[..Members] overload encodeIdentifier(static Tuple[..Members])
    = IdentifierConcat(#"{Tuple=", encodeIdentifier(..RecordFieldTypes(T)), #"}");
[..Members] overload encodeIdentifier(static Union[..Members])
    = IdentifierConcat(#"(Union=", encodeIdentifier(..Members), #")");

overload encodeIdentifier(static Pointer[CChar]) = #"*";
overload encodeIdentifier(static ObjectHandle) = #"@";
overload encodeIdentifier(static ClassHandle) = #"#";
overload encodeIdentifier(static SEL) = #":";

overload encodeIdentifier(static Int8) = #"c";
overload encodeIdentifier(static Int16) = #"s";
overload encodeIdentifier(static Int32) = #"i";
overload encodeIdentifier(static Int64) = #"q";
overload encodeIdentifier(static UInt8) = #"C";
overload encodeIdentifier(static UInt16) = #"S";
overload encodeIdentifier(static UInt32) = #"I";
overload encodeIdentifier(static UInt64) = #"Q";
overload encodeIdentifier(static Float32) = #"f";
overload encodeIdentifier(static Float64) = #"d";
overload encodeIdentifier(static Bool) = #"c";
overload encodeIdentifier(static Void) = #"v";
overload encodeIdentifier(static []) = #"v";

[..T | countValues(..T) > 1]
overload encodeIdentifier(..types: T)
    = IdentifierConcat(..mapValues(encodeIdentifier, ..types));

encode(..types) = StaticName(encodeIdentifier(..types));


//
// object parameters
//

define objectHandle;
define ObjectParameterType;

ObjectHandleType(T) = Type(objectHandle(typeToRValue(T)));

ObjectParameterType?(T)
    = CallDefined?(objectHandle, T)
      and CallDefined?(ObjectParameterType, Static[T])
      and ObjectHandleType(T) == ObjectHandle;

[T | ObjectParameterType?(T)]
objectParameter(forward x: T) = forward ObjectParameterType(T)(objectHandle(x));



//
// reference counting methods
//

private release(x) {
    if (not null?(x)) {
        objc_msgSend(objectHandle(x), objcSelector[#release]);
    }
}
private retain(x) {
    return objc_msgSend(objectHandle(x), objcSelector[#retain]);
}



//
// object references
//

ObjectType?(T) = false;

[O | ObjectType?(O)] overload objectHandle(x: O) : ObjectHandle = x.__handle__;
[O | ObjectType?(O)] overload ObjectParameterType(static O) = O;

[O | ObjectRef?(O)] nil(static O) = O();
[O | ObjectRef?(O)] nil?(obj: O) = null?(obj.__handle__);

[O | ObjectType?(O)] overload O() = O(nilHandle);

[O1, O2 | ObjectType?(O1) and ObjectType?(O2)]
overload O1(obj: O2) = O1(obj.__handle__);

private AssignableObjectTypes?(O1, O2)
    = ObjectType?(O1) and ObjectType?(O2);

[O1, O2 | AssignableObjectTypes?(O1, O2)]
overload assign(ref left: O1, right: O2) {
    left.__handle__ = right.__handle__;
}



//
// generic object reference
//

record Id (__handle__: ObjectHandle);

overload ObjectType?(static Id) = true;

overload nil() = nil(Id);

[T | ObjectParameterType?(T) and T != Id and T != ObjectHandle]
overload Id(object: T) = Id(objectHandle(object));



//
// cast between raw pointer and object types
//

[O | ObjectType?(O)]
overload RawPointer(o: O) = RawPointer(o.__handle__);
[O | ObjectType?(O)]
overload O(o: RawPointer) = O(ObjectHandle(o));



//
// managed object references
//

record Retained[O] (__handle__: ObjectHandle);

[O | ObjectType?(O)] overload RegularRecord?(static Retained[O]) = false;

[O | ObjectType?(O)] overload objectHandle(obj: Retained[O]) : ObjectHandle = obj.__handle__;
[O | ObjectType?(O)] overload ObjectParameterType(static Retained[O]) = O;

[O | ObjectType?(O)] overload Retained[O]() = Retained[O](nilHandle);

// copying managed <-> unmanaged
[O1, O2 | ObjectType?(O1) and ObjectType?(O2)]
overload Retained[O1](obj: O2) = Retained[O1](obj.__handle__);
[O1, O2 | ObjectType?(O1) and ObjectType?(O2)]
overload O1(obj: Retained[O2]) = O1(obj.__handle__);

// copying managed to managed
[O1, O2 | ObjectType?(O1) and ObjectType?(O2)]
overload Retained[O1](ref obj: Retained[O2]) = Retained[O1](retain(obj.__handle__));
[O1, O2 | ObjectType?(O1) and ObjectType?(O2)]
overload Retained[O1](rvalue obj: Retained[O2]) --> r: Retained[O1] {
    r.__handle__ <-- obj.__handle__;
    resetUnsafe(obj);
}

// shorthand copying
[O | ObjectType?(O)]
overload Retained(obj: O) = Retained[O](obj);
[O | ObjectType?(O)]
overload Retained(forward obj: Retained[O]) = Retained[O](obj);

[O | ObjectType?(O)]
weak(obj: Retained[O]) = O(obj);

// assigning managed <-> unmanaged
[O1, O2 | AssignableObjectTypes?(O1, O2)]
overload assign(ref left: Retained[O1], right: O2) {
    var oldHandle = left.__handle__;
    left.__handle__ = retain(right.__handle__);
    release(oldHandle);
}
[O1, O2 | AssignableObjectTypes?(O1, O2)]
overload assign(ref left: O1, right: Retained[O2]) {
    left.__handle__ = right.__handle__;
}

// assigning managed to managed
[O1, O2 | AssignableObjectTypes?(O1, O2)]
overload assign(ref left: Retained[O1], ref right: Retained[O2]) {
    var oldHandle = left.__handle__;
    left.__handle__ = retain(right.__handle__);
    release(oldHandle);
}
// assigning managed to managed
[O1, O2 | AssignableObjectTypes?(O1, O2)]
overload assign(ref left: Retained[O1], rvalue right: Retained[O2]) {
    var oldHandle = left.__handle__;
    left.__handle__ = right.__handle__;
    resetUnsafe(right);
    release(oldHandle);
}

[O | ObjectType?(O)] 
overload destroy(obj: Retained[O]) { release(obj.__handle__); }

[O | ObjectType?(O)] 
overload BitwiseMovedType?(static Retained[O]) = true;

[O | ObjectType?(O)] 
overload resetUnsafe(obj: Retained[O]) { obj.__handle__ = nilHandle; }



//
// raw object handles as parameters
//

overload objectHandle(x: ObjectHandle) : ObjectHandle = x;
overload ObjectParameterType(static ObjectHandle) = Id;



//
// class hierarchy
//

define SuperclassType;
define Subclass?;

[Super, Sub]
overload Subclass?(static Super, static Sub)
    = Subclass?(Super, SuperclassType(Sub));

[Super] overload Subclass?(static Super, static Super) = true;
[Super] overload Subclass?(static Super, static Void) = false;



//
// literal types for passing class attributes to class type definers
//

record InstanceVars[T] (value: T);
record ClassMethods[T] (value: T);
record InstanceMethods[T] (value: T);

overload InstanceVars(..values) = InstanceVars[Type(Tuple(..values))](Tuple(..values));
overload ClassMethods(..values) = ClassMethods[Type(Tuple(..values))](Tuple(..values));
overload InstanceMethods(..values) = InstanceMethods[Type(Tuple(..values))](Tuple(..values));



//
// class properties
//

[T | Class?(T)]
ClassInstanceVars(static T) = [];
define ClassClassMethods;
define ClassInstanceMethods;

MangledModuleChar(c) = c;
overload MangledModuleChar(static #".") = #__;

MangledModuleName(T) = IdentifierConcat(..mapValues(
    MangledModuleChar,
    ..IdentifierChars(IdentifierModuleName(T))
));

[T | ClayClass?(T)]
overload ClassName(static T) {
    return StaticName(IdentifierConcat(
        #"clay__", MangledModuleName(T), #"__", IdentifierStaticName(T)
    ));
}

[T | ExternalClass?(T) or ExternalClayClass?(T)]
ClassName(static T) = StaticName(T);

define ClassStaticName;

[T | CallDefined?(ClassStaticName, Static[T])]
overload ClassName(static T) = StaticName(ClassStaticName(T));

[T, name | Class?(T) and SuperclassType(T) != Void and Identifier?(name)]
ClassIvarType(static T, static name) = ClassIvarType(SuperclassType(T), name);
[T, name | Class?(T) and Identifier?(name) and assocValue?(name, ..ClassInstanceVars(T))]
overload ClassIvarType(static T, static name) = firstValue(..assocValue(name, ..ClassInstanceVars(T)));

ClassIvarNames(T) = ..mapValues(ivar => ivar.0, ..ClassInstanceVars(T));
ClassIvarTypes(T) = ..mapValues(ivar => ivar.1, ..ClassInstanceVars(T));

ClassInstanceVar?(T, ivar) = false;
[T, ivar | Class?(T) and Identifier?(ivar)]
overload ClassInstanceVar?(static T, static ivar) = ClassInstanceVar?(SuperclassType(T), ivar);
[T, ivar | Class?(T) and Identifier?(ivar) and assocValue?(ivar, ..ClassInstanceVars(T))]
overload ClassInstanceVar?(static T, static ivar) = true;



//
// bindings to external classes
//

Class?(T) = Type?(T) and CallDefined?(SuperclassType, Static[T]);
[T | Class?(T)] overload ObjectType?(static T) = true;
ExternalClass?(T) = false;

define classObjcName;

ValidSuperclass?(T) = Class?(T) or T == Void;

[Super | ValidSuperclass?(Super)]
externalClass(static Super) = recordWithProperties(
    [
        [SuperclassType, Super],
        [ExternalClass?, static true],
    ], 
    [#__handle__, ObjectHandle]
);

[name, Super | Identifier?(name) and ValidSuperclass?(Super)]
overload externalClass(static name, static Super) = recordWithProperties(
    [
        [SuperclassType, Super],
        [ExternalClass?, static true],
        [ClassStaticName, name],
    ], 
    [#__handle__, ObjectHandle]
);

record ExternalClassNotFound (name: StringConstant);
instance Exception (ExternalClassNotFound);

_objcExternalClassMake(name: StringConstant) {
    var handle = objc_getClass(cstring(name));
    if (null?(handle))
        throw ExternalClassNotFound(name);
    return handle;
}

[T | ExternalClass?(T)]
overload objcClassMake(static T) {
    return _objcExternalClassMake(ClassName(T));
}

[T | ExternalClass?(T)]
overload objcInitIvar(static T, ivarName)
    = class_getInstanceVariable(objcClass[Class], cstring(StaticName(ivarName)));

[T | ExternalClass?(T)]
overload objcInitIvarOffset(static T, ivarName)
    = ivar_getOffset(objcIvar[Class, ivarName]);



//
// defining new classes in Clay
//

ClayClass?(T) = false;
ExternalClayClass?(T) = false;

[Super, IV, CM, IM] // XXX | ValidSuperclass?(Super)]
newClass(static Super,
    instancevars: InstanceVars[IV],
    classmethods: ClassMethods[CM],
    instancemethods: InstanceMethods[IM]
) = recordWithProperties(
    [
        [SuperclassType, Super],
        [ClayClass?, static true],
        Tuple(ClassInstanceVars, ..unpack(instancevars.value)),
        Tuple(ClassClassMethods, ..unpack(classmethods.value)),
        Tuple(ClassInstanceMethods, ..unpack(instancemethods.value)),
    ], 
    [#__handle__, ObjectHandle]
);

[name, Super, IV, CM, IM] // XXX ValidSuperclass?(Super)]
overload newClass(static name, static Super,
    instancevars: InstanceVars[IV],
    classmethods: ClassMethods[CM],
    instancemethods: InstanceMethods[IM]
) = recordWithProperties(
    [
        [SuperclassType, Super],
        [ClayClass?, static true],
        [ClassStaticName, name],
        Tuple(ClassInstanceVars, ..unpack(instancevars.value)),
        Tuple(ClassClassMethods, ..unpack(classmethods.value)),
        Tuple(ClassInstanceMethods, ..unpack(instancemethods.value)),
    ], 
    [#__handle__, ObjectHandle]
);

[Super, IV, CM, IM] // XXX | ValidSuperclass?(Super)]
newExternalClass(static Super,
    instancevars: InstanceVars[IV],
    classmethods: ClassMethods[CM],
    instancemethods: InstanceMethods[IM]
) = recordWithProperties(
    [
        [SuperclassType, Super],
        [ClayClass?, static true],
        [ExternalClayClass?, static true],
        Tuple(ClassInstanceVars, ..unpack(instancevars.value)),
        Tuple(ClassClassMethods, ..unpack(classmethods.value)),
        Tuple(ClassInstanceMethods, ..unpack(instancemethods.value)),
    ], 
    [#__handle__, ObjectHandle]
);

record NewClassCouldNotBeCreated (module: StringConstant, name: StringConstant);
instance Exception (NewClassCouldNotBeCreated);

record NewClassCouldNotAddIvar (module: StringConstant, name: StringConstant, ivar: StringConstant);
instance Exception (NewClassCouldNotAddIvar);

record NewClassCouldNotAddMethod (module: StringConstant, name: StringConstant, selector: CStringRef);
instance Exception (NewClassCouldNotAddMethod);

private raiseNSException(reason: ObjectHandle, format: ObjectHandle, ..args) {
    objc_msgSend(
        ObjectHandle(objc_getClass(cstring("NSException"))),
        objcSelector[#"raise:format:"],
        reason, format, ..mapValues(varArgUnbox1, ..args) 
    );
}

[T, sel]
private classMethodImp(static T, static sel, fn) 
    = makeCCodePointer(
        (self, _cmd, ..args) => {
            try {
                return forward ..mapValues(
                    r => msgSendUnbox1(SelectorReturnType(Static[T], sel), r),
                    ..fn(objectParameter(T), ..msgSendBox(Static[T], sel, ..args))
                );
            } catch (ex) {
                raiseNSException(NSSTR(#"Clay Exception"), NSSTR(#"%s"), cstring(str(ex)));
                // not actually reached
                throw ex;
            }
        },
        ObjectHandle, SelectorHandle, ..SelectorArgumentParamTypes(Static[T], sel)
    );

[T, sel]
private instanceMethodImp(static T, static sel, fn)
    = makeCCodePointer(
        (self, _cmd, ..args) => {
            try {
                return forward ..mapValues(
                    r => msgSendUnbox1(SelectorReturnType(T, sel), r),
                    ..fn(T(self), ..msgSendBox(T, sel, ..args))
                );
            } catch (ex) {
                raiseNSException(NSSTR(#"Clay Exception"), NSSTR(#"%s"), cstring(str(ex)));
                // not actually reached
                throw ex;
            }
        },
        ObjectHandle, SelectorHandle, ..SelectorArgumentParamTypes(T, sel)
    );

private record ObjcNewClassInstanceVar(
    name: StringConstant,
    size: SizeT,
    alignment: UInt8,
    typeEncoding: StringConstant,
);

private objcNewClassInstanceVar(ivarPair) {
    var ivarName = ivarPair.0;
    var ivarType = ivarPair.1;

    return ObjcNewClassInstanceVar(
        StaticName(ivarName),
        TypeSize(ivarType),
        UInt8(log2(TypeAlignment(ivarType))),
        encode(ivarType)
    );
}

private var objcNewClassInstanceVarsTable[T]
    = Array[ObjcNewClassInstanceVar, countValues(..ClassInstanceVars(T))](
        ..mapValues(objcNewClassInstanceVar, ..ClassInstanceVars(T))
    );

private record ObjcNewClassMethod (
    selector: SelectorHandle,
    imp: RawPointer,
    typeEncoding: StringConstant,
);

private objcNewClassClassMethod(T, classMethod) {
    var selector = classMethod.0;
    var imp = classMethod.1;
    return ObjcNewClassMethod(
        objcSelector[selector],
        RawPointer(classMethodImp(T, selector, imp)),
        SelectorTypeEncoding(Static[T], selector)
    );
}

private var objcNewClassClassMethodsTable[T]
    = Array[ObjcNewClassMethod, countValues(..ClassClassMethods(T))](
        ..mapValues(m => objcNewClassClassMethod(T, m), ..ClassClassMethods(T))
    );

private objcNewClassInstanceMethod(T, instanceMethod) {
    var selector = instanceMethod.0;
    var imp = instanceMethod.1;
    return ObjcNewClassMethod(
        objcSelector[selector],
        RawPointer(instanceMethodImp(T, selector, imp)),
        SelectorTypeEncoding(T, selector)
    );
}

private var objcNewClassInstanceMethodsTable[T]
    = Array[ObjcNewClassMethod, countValues(..ClassInstanceMethods(T))](
        ..mapValues(m => objcNewClassInstanceMethod(T, m), ..ClassInstanceMethods(T))
    );

private _objcNewClassAddMethods(
    moduleName: StringConstant,
    symbolName: StringConstant,
    handle: ClassHandle,
    methodsBegin: Pointer[ObjcNewClassMethod],
    methodsEnd: Pointer[ObjcNewClassMethod]
) {
    for (method in coordinateRange(methodsBegin, methodsEnd))
        if (class_addMethod(
            handle,
            method.selector,
            method.imp,
            cstring(method.typeEncoding)
        ) == NO)
            throw NewClassCouldNotAddMethod(moduleName, symbolName, CStringRef(Pointer[CChar](method.selector)));
}

private _objcNewClassMake(
    super: ClassHandle,
    className: StringConstant,
    symbolName: StringConstant,
    moduleName: StringConstant,
    ivarsBegin: Pointer[ObjcNewClassInstanceVar],
    ivarsEnd: Pointer[ObjcNewClassInstanceVar],
    classMethodsBegin: Pointer[ObjcNewClassMethod],
    classMethodsEnd: Pointer[ObjcNewClassMethod],
    instanceMethodsBegin: Pointer[ObjcNewClassMethod],
    instanceMethodsEnd: Pointer[ObjcNewClassMethod]
) : ClassHandle {
    var handle = objc_allocateClassPair(
        super,
        cstring(className),
        SizeT(0)
    );
    if (null?(handle))
        throw NewClassCouldNotBeCreated(moduleName, symbolName);

    var metahandle = Class(object_getClass(ObjectHandle(handle)));

    for (ivarEntry in coordinateRange(ivarsBegin, ivarsEnd))
        if (class_addIvar(
            handle,
            cstring(ivarEntry.name),
            ivarEntry.size,
            ivarEntry.alignment,
            cstring(ivarEntry.typeEncoding),
        ) == NO)
            throw NewClassCouldNotAddIvar(moduleName, symbolName, ivarEntry.name);

    _objcNewClassAddMethods(moduleName, symbolName, metahandle,
        classMethodsBegin, classMethodsEnd
    );
    _objcNewClassAddMethods(moduleName, symbolName, handle,
        instanceMethodsBegin, instanceMethodsEnd
    );

    objc_registerClassPair(handle);
    return handle;
}

[T | ClayClass?(T)]
inline overload objcClassMake(static T) : ClassHandle {
    var handle = _objcNewClassMake(
        objcClass[SuperclassType(T)],
        ClassName(T),
        StaticName(T),
        ModuleName(T),
        begin(objcNewClassInstanceVarsTable[T]),
        end  (objcNewClassInstanceVarsTable[T]),
        begin(objcNewClassClassMethodsTable[T]),
        end  (objcNewClassClassMethodsTable[T]),
        begin(objcNewClassInstanceMethodsTable[T]),
        end  (objcNewClassInstanceMethodsTable[T]),
    );

    ..for (ivarPair in ClassInstanceVars(T)) {
        var ivar = ivarPair.0;
        objcIvar[T, ivar] = class_getInstanceVariable(handle, cstring(StaticName(ivar)));
        objcIvarOffset[T, ivar] = ivar_getOffset(objcIvar[T, ivar]);
    }

    return handle;
}

[T | ClayClass?(T)]
overload objcInitIvar(static T, ivarName) = Ivar();

[T | ClayClass?(T)]
overload objcInitIvarOffset(static T, ivarName) = PtrInt();



//
// Class objects as objc params
//

[T | Class?(T)] classHandle(static T) = objcClass[T];
[T | Class?(T)] overload objectHandle(static T) = ObjectHandle(classHandle(T));
[T | Class?(T)] overload ObjectParameterType(static Static[T]) = Id; // XXX Class

[T | ObjectRef?(T)] overload encodeIdentifier(static T) = #"@";

// XXX class container



//
// generic selectors
// XXX should be derived from class selector definitions
//

define selector;
define varargSelector;
define externalInstanceMethod;
define externalClassMethod;
define externalVarargInstanceMethod;
define externalVarargClassMethod;

FixedSelector?(sel) = CallDefined?(selector, Static[sel]);
VarargSelector?(sel) = CallDefined?(varargSelector, Static[sel]);
Selector?(sel) = FixedSelector?(sel) or VarargSelector?(sel);

selectorHandle(sel) = objcSelector[sel];

[sel | Selector?(sel)]
SelectorArgumentCount(static sel) = countValues(..GenericSelector(sel)) - 1;

[sel | FixedSelector?(sel)]
GenericSelector(static sel) = ..selector(sel);
[sel | VarargSelector?(sel)]
overload GenericSelector(static sel) = ..varargSelector(sel);



//
// typed selectors
//

[T, sel | ExternalClass?(T) and FixedSelector?(sel)]
TypedSelector(static T, static sel) = ..externalInstanceMethod(T, sel);
[T, sel | ExternalClass?(T) and FixedSelector?(sel)]
overload TypedSelector(static Static[T], static sel) = ..externalClassMethod(T, sel);
[T, sel | ClayClass?(T) and FixedSelector?(sel)]
overload TypedSelector(static T, static sel)
    = ..restValues(..assocValue(sel, ..ClassInstanceMethods(T)));
[T, sel | ClayClass?(T) and FixedSelector?(sel)]
overload TypedSelector(static Static[T], static sel)
    = ..restValues(..assocValue(sel, ..ClassClassMethods(T)));

[T, sel | ExternalClass?(T) and FixedSelector?(sel)]
TypedSelector?(static T, static sel)
    = CallDefined?(externalInstanceMethod, Static[T], Static[sel]);
[T, sel | ExternalClass?(T) and FixedSelector?(sel)]
overload TypedSelector?(static Static[T], static sel)
    = CallDefined?(externalClassMethod, Static[Static[T]], Static[sel]);
[T, sel | ClayClass?(T) and FixedSelector?(sel)]
overload TypedSelector?(static T, static sel)
    = assocCountValues(sel, ..ClassInstanceMethods(T)) > 1;
[T, sel | ClayClass?(T) and FixedSelector?(sel)]
overload TypedSelector?(static Static[T], static sel)
    = assocCountValues(sel, ..ClassClassMethods(T)) > 1;


[T, sel | ExternalClass?(T) and VarargSelector?(sel)]
overload TypedSelector(static T, static sel) = ..externalVarargInstanceMethod(T, sel);
[T, sel | ExternalClass?(T) and VarargSelector?(sel)]
overload TypedSelector(static Static[T], static sel) = ..externalVarargClassMethod(T, sel);

[T, sel | ExternalClass?(T) and VarargSelector?(sel)]
overload TypedSelector?(static T, static sel)
    = CallDefined?(externalVarargInstanceMethod, Static[T], Static[sel]);
[T, sel | ExternalClass?(T) and VarargSelector?(sel)]
overload TypedSelector?(static Static[T], static sel)
    = CallDefined?(externalVarargClassMethod, Static[Static[T]], Static[sel]);
 


//
// selector signatures by type
//

[sel | Selector?(sel)]
private Selector(static Id, static sel) = ..GenericSelector(sel);

[T, sel | Class?(T) and Selector?(sel)]
overload Selector(static T, static sel) = ..GenericSelector(sel);
[T, sel | Class?(T) and Selector?(sel)]
overload Selector(static Static[T], static sel) = ..GenericSelector(sel);

[T, sel | Class?(T) and SuperclassType(T) != Void]
overload Selector(static T, static sel) = ..Selector(SuperclassType(T), sel);
[T, sel |
    Class?(T)
    and TypedSelector?(T, sel)
]
overload Selector(static T, static sel) = ..TypedSelector(T, sel);

[T, sel | Class?(T) and SuperclassType(T) != Void]
overload Selector(static Static[T], static sel) = ..Selector(Static[SuperclassType(T)], sel);
[T, sel |
    Class?(T)
    and TypedSelector?(Static[T], sel)
]
overload Selector(static Static[T], static sel) = ..TypedSelector(Static[T], sel);

[T, sel]
SelectorReturnType(static T, static sel) = firstValue(..Selector(T, sel));

[T, sel]
SelectorArgumentTypes(static T, static sel) = ..restValues(..Selector(T, sel));



//
// retaining and forwarding selectors
//

AllocatingSelector?(sel) = false;
[sel |
    IdentifierStartsWith?(sel, #"alloc")
    or IdentifierStartsWith?(sel, #"new")
]
overload AllocatingSelector?(static sel) = true;

RetainingSelector?(sel) = false;
[sel |
    IdentifierStartsWith?(sel, #"copy")
    or IdentifierStartsWith?(sel, #"mutableCopy")
    or sel == #"retain"
]
overload RetainingSelector?(static sel) = true;

ForwardingSelector?(sel) = false;
[sel | IdentifierStartsWith?(sel, #"init")]
overload ForwardingSelector?(static sel) = true;

AutoreleasingSelector?(sel) = false;
overload AutoreleasingSelector?(static #"autorelease") = true;

[T, sel | Class?(T) and AllocatingSelector?(sel)]
overload SelectorReturnType(static Static[T], static sel) = T;

[T, sel | ObjectType?(T) and (
    RetainingSelector?(sel)
    or ForwardingSelector?(sel)
    or AutoreleasingSelector?(sel)
)]
overload SelectorReturnType(static T, static sel) = T;



//
// argument conversion
//

ObjectRef?(T) = false;
[T | ObjectType?(T)]
overload ObjectRef?(static T) = true;
[T | ObjectType?(T)]
overload ObjectRef?(static Retained[T]) = true;

MsgSendCompatible?(ParamType, ValueType) = false;

[T] overload MsgSendCompatible?(static T, static T) = true;

[Param, Value |
    ObjectRef?(Param)
    and ObjectParameterType?(Value)
    and (
        ObjectParameterType(Value) == Id
        or Subclass?(ObjectParameterType(Param), ObjectParameterType(Value))
    )
]
overload MsgSendCompatible?(static Param, static Value) = true;

[Value | ObjectParameterType?(Value)]
overload MsgSendCompatible?(static Id, static Value) = true;

[Value | ObjectParameterType?(Value)]
overload MsgSendCompatible?(static Retained[Id], static Value) = true;


// XXX selector container

MsgSendParamType(Param) = Param;

[Param | ObjectRef?(Param)]
overload MsgSendParamType(static Param) = ObjectHandle;

overload MsgSendParamType(static Void) = static [];
overload MsgSendParamType(static Bool) = BOOL;

[Param]
/*private*/ msgSendUnbox1(static Param, forward x: Param) = forward x;
[Param, Value | ObjectRef?(Param)]
overload msgSendUnbox1(static Param, forward x: Value) = objectHandle(x);

overload msgSendUnbox1(static Bool, forward x: Bool) = if (x) BOOL(1) else BOOL(0);

private varArgUnbox1(forward x) = forward x;
[Param | ObjectRef?(Param)]
overload varArgUnbox1(forward x: Param) = objectHandle(x);

[T, S | FixedSelector?(S)]
/*XXX private*/ msgSendUnbox(static T, static S, forward ..args)
    = forward ..mapValues2(msgSendUnbox1, static SelectorArgumentCount(S),
        ..SelectorArgumentTypes(T, S),
        ..args
    );
[T, S | VarargSelector?(S)]
overload msgSendUnbox(static T, static S, forward ..args) {
    var argCount = static SelectorArgumentCount(S);
    return forward
        ..mapValues2(msgSendUnbox1, argCount, ..SelectorArgumentTypes(T, S), ..args),
        ..mapValues(varArgUnbox1, ..dropValues(argCount, ..args));
}


[Return]
/*XXX private*/ msgSendBox1(static Return, forward x: Return) = forward x;

overload msgSendBox1(static Void) = ;

overload msgSendBox1(static Bool, forward x: BOOL) = x != NO;

[T | ObjectRef?(T)]
overload msgSendBox1(static T, forward x: ObjectHandle)
    = T(x);

[T, S | FixedSelector?(S)]
/*XXX private*/ msgSendBox(static T, static S, forward ..args)
    = forward ..mapValues2(msgSendBox1, static SelectorArgumentCount(S),
        ..SelectorArgumentTypes(T, S),
        ..args
    );
// XXX support overloading vararg selectors

SelectorReturnParamType(T, S) = MsgSendParamType(SelectorReturnType(T, S));
SelectorArgumentParamTypes(T, S) = ..mapValues(MsgSendParamType, ..SelectorArgumentTypes(T, S));

SelectorTypeEncoding(T, S)
    = encode(SelectorReturnParamType(T, S), ObjectHandle, SelectorHandle,
        ..SelectorArgumentParamTypes(T, S)
    );



//
// message sending
//

[T, S | Selector?(S)]
private objc_msgSend_for(static T, static S) = objc_msgSend;
[T, S | Selector?(S) and StRet?(SelectorReturnParamType(T, S))]
overload objc_msgSend_for(static T, static S) = objc_msgSend_stret;
[T, S | Selector?(S) and FpRet?(SelectorReturnParamType(T, S))]
overload objc_msgSend_for(static T, static S) = objc_msgSend_fpret;

[T, S | Selector?(S)]
private objc_msgSendSuper_for(static T, static S) = objc_msgSendSuper;
[T, S | Selector?(S) and StRet?(SelectorReturnParamType(T, S))]
overload objc_msgSendSuper_for(static T, static S) = objc_msgSendSuper_stret;

[T, S | Selector?(S)]
private class_getMethodImplementation_for(static T, static S) = class_getMethodImplementation;
[T, S | Selector?(S) and StRet?(SelectorReturnParamType(T, S))]
overload class_getMethodImplementation_for(static T, static S) = class_getMethodImplementation_stret;

SelectorCallableWith?(C, S, ..T) = false;

[C, S, ..TT | FixedSelector?(S) and countValues(..TT) == SelectorArgumentCount(S)]
overload SelectorCallableWith?(static C, static S, ..T: TT)
    = _SelectorCallableWith?(C, S, ..T);

[C, S, ..TT | VarargSelector?(S) and countValues(..TT) >= SelectorArgumentCount(S)]
overload SelectorCallableWith?(static C, static S, ..T: TT)
    = _SelectorCallableWith?(C, S, ..takeValues(static countValues(..varargSelector(S)) - 1, ..T));

private _SelectorCallableWith?(C, S, ..T) {
    alias Params = Tuple(..SelectorArgumentTypes(C, S));
    alias Values = Tuple(..T);
    ..for (n in staticIntegers(static countValues(..T))) {
        if (not MsgSendCompatible?(staticIndex(Params, n), staticIndex(Values, n))) 
            return false;
    }
    return true;
}

[C, S | FixedSelector?(S)]
SelectorImpType(static C, static S) = CCodePointer[
        [ObjectHandle, SelectorHandle, ..SelectorArgumentParamTypes(C, S)],
        SelectorReturnParamType(C, S)
    ];

[C, S | VarargSelector?(S)]
overload SelectorImpType(static C, static S) = VarArgsCCodePointer[
        [ObjectHandle, SelectorHandle, ..SelectorArgumentParamTypes(C, S)],
        SelectorReturnParamType(C, S)
    ];

private msgSender(C, S)
    = SelectorImpType(C, S)(objc_msgSend_for(C, S));

[C, S | FixedSelector?(S)]
private msgSenderSuper(static C, static S)
    = CCodePointer[
        [Pointer[ObjcSuper], SelectorHandle, ..SelectorArgumentParamTypes(C, S)],
        SelectorReturnParamType(C, S)
    ](objc_msgSendSuper_for(C, S));

[C, S | VarargSelector?(S)]
overload msgSenderSuper(static C, static S)
    = VarArgsCCodePointer[
        [Pointer[ObjcSuper], SelectorHandle, ..SelectorArgumentParamTypes(C, S)],
        SelectorReturnParamType(C, S)
    ](objc_msgSendSuper_for(C, S));

[SelfT, S, ..T]
msgSend(static SelfT, imp, self, static S, forward ..args: T) {
    var cmdHandle = selectorHandle(S);

    return forward ..mapValues(ret => msgSendBox1(SelectorReturnType(SelfT, S), ret),
        ..imp(self, cmdHandle, ..msgSendUnbox(SelfT, S, ..args))
    );
}



//
// identifier-selector conversion
//

private _identifierSelectorChar(c) = c;
overload _identifierSelectorChar(static #_) = #":";

[argCount, underscoreCount, lastCharacter]
private _extraColon(static argCount, static underscoreCount, static lastCharacter) = ;
[argCount, underscoreCount, lastCharacter |
    argCount > underscoreCount
    and lastCharacter != #_
    and lastCharacter != #":"
]
overload _extraColon(static argCount, static underscoreCount, static lastCharacter)
    = #":";

[argCount] private _identifierSelector(static argCount, ..chars) {
    var lastCharacter = lastValue(..chars);
    return IdentifierConcat(
        ..mapValues(_identifierSelectorChar, ..chars),
        .._extraColon(static argCount, static countOccurrences(#_, ..chars), lastCharacter)
    );
}

private var memoIdentifierSelector[identifier, argCount]
    = _identifierSelector(static argCount, ..IdentifierChars(identifier));

[argCount] identifierSelector(identifier, static argCount)
    = memoIdentifierSelector[identifier, argCount];

[identifier, minArity]
private _ValidMethodIdentifier?(static identifier, static minArity)
    = Selector?(identifierSelector(identifier, static minArity))
      or Selector?(identifierSelector(identifier, static minArity+1));

[identifier]
ValidMethodIdentifier?(static identifier) = true;
    //= _ValidMethodIdentifier?(
    //    static identifier,
    //    static countOccurrences(#_, ...IdentifierChars(identifier))
    //);



//
// method syntax
//

private record MethodRef[T, Captured, identifier] (self: Captured);

overload MethodRef(T, forward value, identifier)
    = MethodRef[T, Type(captureValue(value)), identifier](captureValue(value));

[T, cmd | ObjectRef?(T) and cmd != #__handle__ and ValidMethodIdentifier?(cmd)]
overload fieldRef(forward self: T, static cmd)
    = MethodRef(ObjectParameterType(T), self, cmd);
[T, cmd | Class?(T) and ValidMethodIdentifier?(cmd)]
overload fieldRef(static T, static cmd) = MethodRef(Static[T], T, cmd);

[T, selector, ..Args | ObjectParameterType?(T) and Selector?(selector)]
callMethod(forward self: T, static selector, forward ..args: Args)
    = forward ..msgSend(
        T,
        msgSender(T, selector),
        objectHandle(self),
        selector,
        ..args
    );

[T, Captured, identifier, ..Args |
    Selector?(identifierSelector(identifier, static countValues(..Args)))
    and SelectorCallableWith?(
        T,
        identifierSelector(identifier, static countValues(..Args)),
        ..Args
    )
]
overload call(method: MethodRef[T, Captured, identifier], forward ..args: Args) {
    var selector = identifierSelector(identifier, static countValues(..Args));
    return forward ..msgSend(
        T,
        msgSender(T, selector),
        objectHandle(forwardValue(method.self)),
        selector, 
        ..args
    );
}



//
// method delegate (cached method pointer)
//

record MethodDelegate[T, selector] (self: ObjectHandle, imp: IMP);

[T, selector | ObjectParameterType?(T) and Selector?(selector)]
overload MethodDelegate(self: T, static selector) {
    var selfHandle = objectHandle(self);
    var imp = class_getMethodImplementation_for(T, selector)(
        object_getClass(selfHandle), 
        selectorHandle(selector)
    );
    return MethodDelegate[T, selector](selfHandle, imp);
}

[T, selector]
overload call(delegate: MethodDelegate[T, selector], forward ..args)
    = forward ..msgSend(
        T,
        SelectorImpType(T, selector)(delegate.imp),
        delegate.self,
        selector,
        ..args
    );



//
// instance variable access
//

private record IvarsRef[T] (__handle__: ObjectHandle);

[T | Class?(T)]
overload dereference(obj: T) = IvarsRef[T](objectHandle(obj));
[T | Class?(T)]
overload dereference(obj: Retained[T]) = IvarsRef[T](objectHandle(obj));


[T, ivar | Class?(T) and ivar != #__handle__ and ClassInstanceVar?(T, ivar)]
overload fieldRef(ivars: IvarsRef[T], static ivar)
    = ref Pointer[ClassIvarType(T, ivar)](
        Pointer[UInt8](ivars.__handle__) + objcIvarOffset[T, ivar]
    )^;



//
// superclass method invocation
//

private record SuperRef[T, Captured] (__self__: Captured);

[T | Class?(T) and SuperclassType(T) != Void]
super(forward obj: T) = SuperRef[T, Type(captureValue(obj))](captureValue(obj));

[T | Class?(T) and SuperclassType(T) != Void]
overload super(forward obj: Retained[T]) = SuperRef[T, Type(captureValue(obj))](captureValue(obj));

private record SuperMethodRef[T, Captured, identifier] (__self__: Captured);

[T, Captured]
overload SuperMethodRef(forward super: SuperRef[T, Captured], identifier)
    = SuperMethodRef[T, Captured, identifier](super.__self__);

[T, Captured, cmd | Class?(T) and cmd != #__self__]
overload fieldRef(sup: SuperRef[T, Captured], static cmd)
    = SuperMethodRef(sup, cmd);

[T, Captured, selector, ..Args | Class?(T) and Selector?(selector)]
overload callMethod(sup: SuperRef[T, Captured], static selector, forward ..args: Args) {
    var super = ObjcSuper(
        objectHandle(forwardValue(sup.__self__)),
        classHandle(SuperclassType(T))
    );
    return forward ..msgSend(
        SuperclassType(T),
        msgSenderSuper(SuperclassType(T), selector),
        &super,
        selector,
        ..args
    );
}

[T, Captured, identifier, ..Args]
overload call(method: SuperMethodRef[T, Captured, identifier], forward ..args: Args) {
    var selector = identifierSelector(identifier, static countValues(..Args));
    var super = ObjcSuper(
        objectHandle(forwardValue(method.__self__)),
        classHandle(SuperclassType(T))
    );
    return forward ..msgSend(
        SuperclassType(T),
        msgSenderSuper(SuperclassType(T), selector),
        &super,
        selector,
        ..args
    );
}
