//go:build darwin

package webview

/*
#cgo CFLAGS: -x objective-c
#cgo LDFLAGS: -framework Foundation -framework WebKit

#import <Foundation/Foundation.h>
#import <WebKit/WebKit.h>

typedef void (^schemeTaskCaller)(id<WKURLSchemeTask>);

static bool urlSchemeTaskCall(void *wkUrlSchemeTask, schemeTaskCaller fn) {
	id<WKURLSchemeTask> urlSchemeTask = (id<WKURLSchemeTask>) wkUrlSchemeTask;
    if (urlSchemeTask == nil) {
        return false;
    }

	@autoreleasepool {
		@try {
			fn(urlSchemeTask);
		} @catch (NSException *exception) {
			// This is very bad to detect a stopped schemeTask this should be implemented in a better way
			// But it seems to be very tricky to not deadlock when keeping a lock curing executing fn()
			// It seems like those call switch the thread back to the main thread and then deadlocks when they reentrant want
			// to get the lock again to start another request or stop it.
			if ([exception.reason isEqualToString: @"This task has already been stopped"]) {
				return false;
			}

			@throw exception;
		}

		return true;
	}
}

static bool URLSchemeTaskDidReceiveData(void *wkUrlSchemeTask, void* data, int datalength) {
	return urlSchemeTaskCall(
		wkUrlSchemeTask,
		^(id<WKURLSchemeTask> urlSchemeTask) {
			NSData *nsdata = [NSData dataWithBytes:data length:datalength];
			[urlSchemeTask didReceiveData:nsdata];
    	});
}

static bool URLSchemeTaskDidFinish(void *wkUrlSchemeTask) {
	return urlSchemeTaskCall(
		wkUrlSchemeTask,
		^(id<WKURLSchemeTask> urlSchemeTask) {
			[urlSchemeTask didFinish];
    	});
}

static bool URLSchemeTaskDidReceiveResponse(void *wkUrlSchemeTask, int statusCode, void *headersString, int headersStringLength) {
	return urlSchemeTaskCall(
		wkUrlSchemeTask,
		^(id<WKURLSchemeTask> urlSchemeTask) {
			NSData *nsHeadersJSON = [NSData dataWithBytes:headersString length:headersStringLength];
			NSDictionary *headerFields = [NSJSONSerialization JSONObjectWithData:nsHeadersJSON options: NSJSONReadingMutableContainers error: nil];
        	NSHTTPURLResponse *response = [[[NSHTTPURLResponse alloc] initWithURL:urlSchemeTask.request.URL statusCode:statusCode HTTPVersion:@"HTTP/1.1" headerFields:headerFields] autorelease];

			[urlSchemeTask didReceiveResponse:response];
    	});
}
*/
import "C"

import (
	"encoding/json"
	"fmt"
	"net/http"
	"unsafe"
)

var _ ResponseWriter = &responseWriter{}

type responseWriter struct {
	r *request

	header      http.Header
	wroteHeader bool

	finished bool
}

func (rw *responseWriter) Header() http.Header {
	if rw.header == nil {
		rw.header = http.Header{}
	}
	return rw.header
}

func (rw *responseWriter) Write(buf []byte) (int, error) {
	if rw.finished {
		return 0, errResponseFinished
	}

	rw.WriteHeader(http.StatusOK)

	var contentLen int
	if buf != nil {
		contentLen = len(buf)
	}

	if contentLen > 0 {
		// Create a C array to hold the data
		cBuf := C.malloc(C.size_t(contentLen))
		if cBuf == nil {
			return 0, fmt.Errorf("memory allocation failed for %d bytes", contentLen)
		}
		defer C.free(cBuf)

		// Copy the Go slice to the C array
		C.memcpy(cBuf, unsafe.Pointer(&buf[0]), C.size_t(contentLen))

		if !C.URLSchemeTaskDidReceiveData(rw.r.task, cBuf, C.int(contentLen)) {
			return 0, errRequestStopped
		}
	} else {
		if !C.URLSchemeTaskDidReceiveData(rw.r.task, nil, 0) {
			return 0, errRequestStopped
		}
	}

	return contentLen, nil
}

func (rw *responseWriter) WriteHeader(code int) {
	if rw.wroteHeader || rw.finished {
		return
	}
	rw.wroteHeader = true

	header := map[string]string{}
	for k := range rw.Header() {
		header[k] = rw.Header().Get(k)
	}
	headerData, _ := json.Marshal(header)

	var headers unsafe.Pointer
	var headersLen int
	if len(headerData) != 0 {
		headers = unsafe.Pointer(&headerData[0])
		headersLen = len(headerData)
	}

	C.URLSchemeTaskDidReceiveResponse(rw.r.task, C.int(code), headers, C.int(headersLen))
}

func (rw *responseWriter) Finish() error {
	if !rw.wroteHeader {
		rw.WriteHeader(http.StatusNotImplemented)
	}

	if rw.finished {
		return nil
	}
	rw.finished = true

	C.URLSchemeTaskDidFinish(rw.r.task)
	return nil
}
