using Php;

namespace PhpNative {
    public string date_static (string format, string php_file, int php_line) throws Error {
        var now = new DateTime.now_local ();
        switch (format) {
        case "Y-m-d H:i:s":
            return now.format ("%Y-%m-%d %H:%M:%S");
        case "c":
            return now.format ("%Y-%m-%dT%H:%M:%S%z");
        case "U":
            return now.to_unix ().to_string ();
        default:
            string result = format;
            result = result.replace ("Y", now.format ("%Y"));
            result = result.replace ("m", now.format ("%m"));
            result = result.replace ("d", now.format ("%d"));
            result = result.replace ("H", now.format ("%H"));
            result = result.replace ("i", now.format ("%M"));
            result = result.replace ("s", now.format ("%S"));
            return result;
        }
    }

    public zval date_zval (zval format, string php_file, int php_line) throws Error {
        return zval.string_value (date_static (zval_to_string (format), php_file, php_line));
    }

    public string escapeshellarg_static (string value, string php_file, int php_line) throws Error {
        return "'" + value.replace ("'", "'\\''") + "'";
    }

    public zval escapeshellarg_zval (zval value, string php_file, int php_line) throws Error {
        return zval.string_value (escapeshellarg_static (zval_to_string (value), php_file, php_line));
    }

    public bool is_numeric_static (string value, string php_file, int php_line) throws Error {
        double parsed;
        return double.try_parse (value.strip (), out parsed);
    }

    public zval is_numeric_zval (zval value, string php_file, int php_line) throws Error {
        return zval.boolean (is_numeric_static (zval_to_string (value), php_file, php_line));
    }

    public bool ctype_digit_static (string value, string php_file, int php_line) throws Error {
        if (value.length == 0) {
            return false;
        }
        for (int i = 0; i < value.length; i++) {
            if (!value[i].isdigit ()) {
                return false;
            }
        }
        return true;
    }

    public zval ctype_digit_zval (zval value, string php_file, int php_line) throws Error {
        return zval.boolean (ctype_digit_static (zval_to_string (value), php_file, php_line));
    }

    public string exec_static (string command, string php_file, int php_line) throws Error {
        zval output;
        int status;
        return exec_output_status_static (command, out output, out status, php_file, php_line);
    }

    public zval exec_zval (zval command, out zval output, out zval status_value, string php_file, int php_line) throws Error {
        int status;
        string result = exec_output_status_static (zval_to_string (command), out output, out status, php_file, php_line);
        status_value = zval.integer (status);
        return zval.string_value (result);
    }

    public string exec_output_status_static (string command, out zval output, out int status, string php_file, int php_line) throws Error {
        output = zval.array ();
        status = 0;

        string standard_output = "";
        string standard_error = "";
        int wait_status = 0;
        try {
            string[] argv = { "/bin/sh", "-c", command };
            Process.spawn_sync (null, argv, null, SpawnFlags.SEARCH_PATH, null, out standard_output, out standard_error, out wait_status);
        } catch (SpawnError e) {
            status = 1;
            array_append (output, zval.string_value (e.message));
            return "";
        }

        status = wait_status;
        string last_line = "";
        foreach (string raw_line in standard_output.split ("\n")) {
            if (raw_line == "") {
                continue;
            }
            last_line = raw_line;
            array_append (output, zval.string_value (raw_line));
        }

        return last_line;
    }

    public string sprintf_static (string format, string value, int number, string php_file, int php_line) throws Error {
        return sprintf_string_int_static (format, value, number, php_file, php_line);
    }

    public string sprintf_string_int_static (string format, string value, int number, string php_file, int php_line) throws Error {
        return format.printf (value, number);
    }

    public zval sprintf_zval (zval format, zval value, zval number, string php_file, int php_line) throws Error {
        return zval.string_value (sprintf_string_int_static (zval_to_string (format), zval_to_string (value), zval_to_int (number), php_file, php_line));
    }

    public string implode_static (string separator, zval values, string php_file, int php_line) throws Error {
        var builder = new StringBuilder ();
        bool first = true;
        foreach (zval value in array_values (values)) {
            if (!first) {
                builder.append (separator);
            }
            builder.append (zval_to_string (value));
            first = false;
        }
        return builder.str;
    }

    public zval implode_zval (zval separator, zval values, string php_file, int php_line) throws Error {
        return zval.string_value (implode_static (zval_to_string (separator), values, php_file, php_line));
    }

    public zval array_slice_static (zval values, int offset, string php_file, int php_line) throws Error {
        zval result = zval.array ();
        Gee.ArrayList<zval> array_values_list = array_values (values);
        int start = offset >= 0 ? offset : array_values_list.size + offset;
        if (start < 0) {
            start = 0;
        }
        for (int i = start; i < array_values_list.size; i++) {
            array_append (result, array_values_list[i]);
        }
        return result;
    }

    public zval array_slice_zval (zval values, zval offset, string php_file, int php_line) throws Error {
        return array_slice_static (values, zval_to_int (offset), php_file, php_line);
    }

    public Gee.ArrayList<string> array_slice_string_list_static (Gee.ArrayList<string> values, int offset, string php_file, int php_line) throws Error {
        var result = new Gee.ArrayList<string> ();
        int start = offset >= 0 ? offset : values.size + offset;
        if (start < 0) {
            start = 0;
        }
        for (int i = start; i < values.size; i++) {
            result.add (values[i]);
        }
        return result;
    }

    private int compare_zvals_numeric (zval left, zval right) {
        double left_number = zval_to_double (left);
        double right_number = zval_to_double (right);
        if (left_number < right_number) {
            return -1;
        }
        if (left_number > right_number) {
            return 1;
        }
        return 0;
    }

    public zval min_static (zval values, string php_file, int php_line) throws Error {
        Gee.ArrayList<zval> array_values_list = array_values (values);
        if (array_values_list.size == 0) {
            return zval.null_value ();
        }
        zval result = array_values_list[0];
        for (int i = 1; i < array_values_list.size; i++) {
            if (compare_zvals_numeric (array_values_list[i], result) < 0) {
                result = array_values_list[i];
            }
        }
        return result;
    }

    public zval min_zval (zval values, string php_file, int php_line) throws Error {
        return min_static (values, php_file, php_line);
    }

    public zval max_static (zval values, string php_file, int php_line) throws Error {
        Gee.ArrayList<zval> array_values_list = array_values (values);
        if (array_values_list.size == 0) {
            return zval.null_value ();
        }
        zval result = array_values_list[0];
        for (int i = 1; i < array_values_list.size; i++) {
            if (compare_zvals_numeric (array_values_list[i], result) > 0) {
                result = array_values_list[i];
            }
        }
        return result;
    }

    public zval max_zval (zval values, string php_file, int php_line) throws Error {
        return max_static (values, php_file, php_line);
    }
}
