### ----------------------------------------------------
### -- LibCurl
### -- Licenses: NASA-1.3
### -- Authors: Curt Tilmes <Curt.Tilmes@nasa.gov>
### -- File: lib/LibCurl/EasyHandle.rakumod
### ----------------------------------------------------
### -- Chunk 2 of 2
;
        my @parts = $version.split('.');
        return not ($major < (@parts[0])
                 || $minor < (@parts[1] // 0)
                 || $patch < (@parts[2] // 0))
    }
}

class LibCurl::version is repr('CPointer')
{
    sub curl_version_info(uint32 $type) returns LibCurl::version
        is native(LIBCURL) { * }

    method new() { curl_version_info(CURLVERSION_NOW) }

    method info() { nativecast(LibCurl::version-info, self) }
}

class LibCurl::EasyHandle is repr('CPointer')
{
    sub curl_easy_init() returns LibCurl::EasyHandle is native(LIBCURL) { * }

    sub curl_easy_cleanup(LibCurl::EasyHandle) is native(LIBCURL) { * }

    sub curl_easy_reset(LibCurl::EasyHandle) is native(LIBCURL) { * }

    sub curl_easy_duphandle(LibCurl::EasyHandle) returns LibCurl::EasyHandle
        is native(LIBCURL) { * }

    sub curl_easy_escape(LibCurl::EasyHandle, Buf, int32) returns Pointer
        is native(LIBCURL) { * }

    sub curl_easy_unescape(LibCurl::EasyHandle, Buf, int32, int32 is rw)
        returns Pointer is native(LIBCURL) { * }

    sub curl_easy_setopt_str(LibCurl::EasyHandle, uint32, Str) returns uint32
        is native(LIBCURL) is symbol('curl_easy_setopt') { * }

    sub curl_easy_setopt_long(LibCurl::EasyHandle, uint32, long) returns uint32
        is native(LIBCURL) is symbol('curl_easy_setopt') { * }

    sub curl_easy_setopt_ptr(LibCurl::EasyHandle, uint32, Pointer)
        returns uint32 is native(LIBCURL) is symbol('curl_easy_setopt') { * }

    sub curl_easy_setopt_slist(LibCurl::EasyHandle, uint32, LibCurl::slist)
        returns uint32 is native(LIBCURL) is symbol('curl_easy_setopt') { * }

    sub curl_easy_setopt_array(LibCurl::EasyHandle, uint32, CArray[uint8])
        returns uint32 is native(LIBCURL) is symbol('curl_easy_setopt') { * }

    sub curl_easy_setopt_data-cb(LibCurl::EasyHandle, uint32,
        &cb (Pointer, uint32, uint32, Pointer --> uint32))
        returns uint32 is native(LIBCURL) is symbol('curl_easy_setopt') { * }

    sub curl_easy_setopt_debug-cb(LibCurl::EasyHandle, uint32,
	&cb (Pointer, uint32, Pointer, size_t, Pointer --> int32))
        returns uint32 is native(LIBCURL) is symbol('curl_easy_setopt') { * }

    sub curl_easy_setopt_xfer-cb(LibCurl::EasyHandle, uint32,
	&cb (Pointer, long, long, long, long --> int32))
        returns uint32 is native(LIBCURL) is symbol('curl_easy_setopt') { * }

    sub curl_easy_perform(LibCurl::EasyHandle) returns uint32
        is native(LIBCURL) { * }

    sub curl_easy_getinfo_long(LibCurl::EasyHandle, int32, long is rw)
        returns uint32 is native(LIBCURL) is symbol('curl_easy_getinfo') { * }

    sub curl_easy_getinfo_double(LibCurl::EasyHandle, int32, num64 is rw)
        returns uint32 is native(LIBCURL) is symbol('curl_easy_getinfo') { * }

    sub curl_easy_getinfo_str(LibCurl::EasyHandle, int32, CArray[Str])
        returns uint32 is native(LIBCURL) is symbol('curl_easy_getinfo') { * }

    sub curl_easy_getinfo_ptr(LibCurl::EasyHandle, int32, CArray[Pointer])
        returns uint32 is native(LIBCURL) is symbol('curl_easy_getinfo') { * }

    sub curl_easy_getinfo_socket(LibCurl::EasyHandle, int32, int32 is rw)
        returns uint32 is native(LIBCURL) is symbol('curl_easy_getinfo') { * }

    sub curl_easy_send(LibCurl::EasyHandle, Blob, size_t, size_t is rw
        --> uint32) is native(LIBCURL) {}

    sub curl_easy_recv(LibCurl::EasyHandle, Blob, size_t, size_t is rw
        --> uint32) is native(LIBCURL) {}

    method new() returns LibCurl::EasyHandle { curl_easy_init }

    method id() returns Int {
        return +( nativecast(Pointer, self) // -1);
    }

    method cleanup() { curl_easy_cleanup(self) }

    method reset() { curl_easy_reset(self) }

    method duphandle() { curl_easy_duphandle(self) }

    method escape(Str $str, $encoding = 'utf-8')
    {
        my $buf = $str.encode($encoding);
        my $ptr = curl_easy_escape(self, $buf, $buf.elems);
        my $esc = nativecast(Str, $ptr);
        curl_free($ptr);
        return $esc;
    }

    method unescape(Str $str, $encoding = 'utf-8')
    {
        my $buf = $str.encode($encoding);
        my int32 $outlength;
        my $ptr = curl_easy_unescape(self, $buf, $buf.elems, $outlength);
        my $arr = nativecast(CArray[int8], $ptr);
        my $outstr = Buf.new($arr[0 ..^ $outlength]).decode($encoding);
        curl_free($ptr);
        return $outstr;
    }

    method perform() { curl_easy_perform(self); }

    multi method setopt($option, Str $param) {
        my $ret = curl_easy_setopt_str(self, $option, $param);
        die X::LibCurl.new(code => $ret) unless $ret == CURLE_OK;
    }

    multi method setopt($option, Int $param) {
        my $ret = curl_easy_setopt_long(self, $option, $param);
        die X::LibCurl.new(code => $ret) unless $ret == CURLE_OK;
    }

    multi method setopt($option, &callback) {
        my $ret = do given $option {
	   when CURLOPT_WRITEFUNCTION | CURLOPT_READFUNCTION |
	        CURLOPT_HEADERFUNCTION {
	       curl_easy_setopt_data-cb(self, $option, &callback);
	   }
	   when CURLOPT_DEBUGFUNCTION {
	       curl_easy_setopt_debug-cb(self, $option, &callback);
	   }
	   when CURLOPT_XFERINFOFUNCTION {
	       curl_easy_setopt_xfer-cb(self, $option, &callback);
	   }
	};
    }

    multi method setopt($option, Pointer $ptr) {
        my $ret = curl_easy_setopt_ptr(self, $option, $ptr);
        die X::LibCurl.new(code => $ret) unless $ret == CURLE_OK;
    }

    multi method setopt($option, LibCurl::slist $slist) {
        my $ret = curl_easy_setopt_slist(self, $option, $slist);
        die X::LibCurl.new(code => $ret) unless $ret == CURLE_OK;
    }

    multi method setopt($option, CArray[uint8] $ptr) {
        my $ret = curl_easy_setopt_array(self, $option, $ptr);
        die X::LibCurl.new(code => $ret) unless $ret == CURLE_OK;
    }

    multi method setopt($option, LibCurl::EasyHandle $ptr) {
        my $ret = curl_easy_setopt_ptr(self, $option, $ptr);
        die X::LibCurl.new(code => $ret) unless $ret == CURLE_OK;
    }

    multi method setopt($option, LibCurl::mime $ptr) {
        my $ret = curl_easy_setopt_ptr(self, $option, $ptr);
        die X::LibCurl.new(code => $ret) unless $ret == CURLE_OK;
    }

    method getinfo_long($option) returns long {
        my long $value;
        my $ret = curl_easy_getinfo_long(self, $option, $value);
        die X::LibCurl.new(code => $ret) unless $ret == CURLE_OK;
        return $value;
    }

    method getinfo_socket($option) returns int32 {
        my int32 $sock;
        my $ret = curl_easy_getinfo_socket(self, $option, $sock);
        die X::LibCurl.new(code => $ret) unless $ret == CURLE_OK;
        return $sock;
    }

    method getinfo_double($option) {
        my num64 $value;
        my $ret = curl_easy_getinfo_double(self, $option, $value);
        die X::LibCurl.new(code => $ret) unless $ret == CURLE_OK;
        return $value[0];
    }

    method getinfo_str($option) {
        my $value = CArray[Str].new;
        $value[0] = '';
        my $ret = curl_easy_getinfo_str(self, $option, $value);
        die X::LibCurl.new(code => $ret) unless $ret == CURLE_OK;
        return $value[0];
    }

    method getinfo_slist($option) {
        my $value = CArray[Pointer].new;
        $value[0] = Pointer.new;
        my $ret = curl_easy_getinfo_ptr(self, $option, $value);
        die X::LibCurl.new(code => $ret) unless $ret == CURLE_OK;
        return [] unless $value[0];
        my $slist = nativecast(LibCurl::slist, $$value[0]);
        my @list = $slist.list;
        $slist.free;
        return @list;
    }

    method mime-init(--> LibCurl::mime)
        is native(LIBCURL) is symbol('curl_mime_init') { * }

    method getinfo_certinfo() {
	... # TODO
        my Pointer $ptr;
        my $ret = curl_easy_getinfo_ptr(self, CURLINFO_CERTINFO, $ptr);
        die X::LibCurl.new(code => $ret) unless $ret == CURLE_OK;
        return nativecast(LibCurl::certinfo, $ptr);
    }

    multi method send(Str $str) { $.send($str.encode) }

    multi method send(Blob $buf, :$timeout = 60000) {
        my $sockfd = $.getinfo_socket(CURLINFO_ACTIVESOCKET);
        return False if $sockfd  == CURL_SOCKET_BAD;
        my size_t $tosend = $buf.elems;
        my size_t $sent;
        my $ret;
        repeat
        {
            $ret = curl_easy_send(self,
                                  $buf.subbuf($buf.elems-$tosend),
                                  $tosend, $sent);
            $tosend -= $sent;
            if $ret == CURLE_AGAIN
                && wait_on_socket($sockfd, :$timeout) == 0
            {
                die "Timeout";
            }
        } while $ret == CURLE_AGAIN && $tosend > 0;
        die X::LibCurl.new(code => $ret) unless $ret == CURLE_OK
    }

    method recv(:$bufsiz = 8192, :$timeout = 60000) {
        my $sockfd = $.getinfo_socket(CURLINFO_ACTIVESOCKET);
        return if $sockfd  == CURL_SOCKET_BAD;
        my $buf = buf8.allocate($bufsiz);
        my size_t $nread;
        my $ret;
        repeat
        {
            $ret = curl_easy_recv(self, $buf, $bufsiz, $nread);
            if $ret == CURLE_AGAIN &&
                wait_on_socket($sockfd, :recv, :$timeout) == 0
            {
                die "Timeout";
            }
        } while $ret == CURLE_AGAIN;
        $ret == CURLE_OK ?? $buf.subbuf(^$nread) !! False
    }
}


