<?php

namespace Clonable\Objects;

use Throwable;

class CurlBuilder {
    private $curl;

    const IP_MODE_46 = "ipv4_ipv6";
    const IP_MODE_4 = "ipv4";
    const IP_MODE_6 = "ipv6";

    public function __construct($url) {
        $this->curl = curl_init($url);
    }

    public function set_request_type($request_type) {
        curl_setopt($this->curl, CURLOPT_CUSTOMREQUEST, $request_type);
        return $this;
    }

    public function set_post_fields($post_fields) {
        curl_setopt($this->curl, CURLOPT_POSTFIELDS, $post_fields);
        return $this;
    }

    public function set_standard_headers($site_url, $max_timeout = 15) {
        curl_setopt($this->curl, CURLOPT_TIMEOUT, $max_timeout);
        curl_setopt($this->curl, CURLOPT_DNS_USE_GLOBAL_CACHE, false);
        curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, true);           // get body response
        curl_setopt($this->curl, CURLOPT_HEADER, true);                   // get header response
        curl_setopt($this->curl, CURLOPT_SSL_VERIFYPEER, false);          // don't care about SSL

        switch (self::ipMode()) {
            case self::IP_MODE_46:
                curl_setopt($this->curl, CURLOPT_RESOLVE, ["$site_url:443:" . ClonableConfig::SERVER_IP . ",[" . ClonableConfig::SERVER_IPV6 . "]"]);
                break;
            case self::IP_MODE_6:
                curl_setopt($this->curl, CURLOPT_RESOLVE, ["$site_url:443:[" . ClonableConfig::SERVER_IPV6 . "]"]);
                break;
            case self::IP_MODE_4:
            default:
                curl_setopt($this->curl, CURLOPT_RESOLVE, ["$site_url:443:" . ClonableConfig::SERVER_IP]);
                break;
        }

        return $this;
    }

    private static function ipMode() {
        $cached = get_transient("clonable_connection_mode");
        if ($cached === false) {
            $mode = self::IP_MODE_4;
            $version = curl_version();
            if ($version) { // Version succeeded
                if ($version['features'] & CURL_VERSION_IPV6) { // Version supports IPv6
                    if (self::canUseIpv6()) {
                        // Only if we know for sure IPv6 works, use it
                        $mode =  self::IP_MODE_6;
                    }
                }
            }

            set_transient("clonable_connection_mode", $mode, 3600);
            return $mode;
        } else {
            return $cached;
        }
    }

    private static function canUseIpv6(): bool{
        // This resource is a simple connectivity check, so http is fine
        $url='http://ipv6.icanhazip.com/';
        return self::canReachUrl($url);
    }

    private static function canUseIpv4(): bool {
        // This resource is a simple connectivity check, so http is fine
        $url='http://ipv4.icanhazip.com/';
        return self::canReachUrl($url);
    }

    private static function canReachUrl(string $url): bool {
        try {
            $ch=curl_init();
            $timeout=3;

            curl_setopt($ch, CURLOPT_URL, $url);
            curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
            curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);

            $result=curl_exec($ch);
            curl_close($ch);

            return $result !== false;
        } catch (Throwable $e) {
            error_log("Clonable failed to check connectivity: " . $e->getMessage());
            return false;
        }
    }

    public function set_header_response(&$headers) {
        // @codingStandardsIgnoreStart
        curl_setopt($this->curl, CURLOPT_HEADERFUNCTION,
            function($curl, $header) use (&$headers) {
                $len = strlen($header);
                $header = explode(':', $header, 2);
                if (count($header) < 2) // ignore invalid headers
                    return $len;

                $headers[strtolower(trim($header[0]))][] = trim($header[1]);

                return $len;
            }
        );
        // @codingStandardsIgnoreEnd
        return $this;
    }

    public function set_http_headers($request_headers) {
        $headers = array();
        $request_headers['Accept-Encoding'] = 'gzip';
        foreach ($request_headers as $key => $header) {
            if (strtolower($key) == 'content-length') {
                // Let curl do this
                continue;
            }
            $headers[] = "$key: $header";
        }
        curl_setopt($this->curl, CURLOPT_HTTPHEADER, $headers);
        return $this;
    }

    public function build() {
        return $this->curl;
    }
}