<?php

namespace xmvc\libDx;

/**
 * Dx SQL 规则
 */
class DxSqlExpr
{
    private static $AND_OP = "AND";
    private static $OR_OP = "OR";
    private static $LIKE_OP = "LIKE";
    private static $NOT_OP = "NOT";
    private static $IS_OP = "IS";
    private static function privateGetFieldName($field)
    {
        $fieldParts = explode(".", $field);
        $result = "";
        $fieldName = DxUtils::quoteStringValue(trim($fieldParts[0]));
        if (count($fieldParts) == 2) {
            $dateProperty = trim($fieldParts[1]);
            $sqlDateFunction = "";
            $fieldPattern = "";
            switch ($dateProperty) {
                case "year":
                case "month":
                case "day":
                    $sqlDateFunction = strtoupper($dateProperty);
                    $fieldPattern = "%s(%s)";
                    break;
                case "dayOfWeek":
                    $sqlDateFunction = strtoupper($dateProperty);
                    $fieldPattern = "%s(%s) - 1";
                    break;
                default:
                    throw new \Exception("The \"" . $dateProperty . "\" command is not supported");
            }
            $result = sprintf($fieldPattern, $sqlDateFunction, $fieldName);
        } else {
            $result = $fieldName;
        }
        return $result;
    }

    private static function privateGetSimpleExpr($expression, \Closure $callback = null): string
    {
        $result = "";
        $itemsCount = count($expression);
        $fieldName = self::privateGetFieldName(trim($expression[0]));
        if ($itemsCount == 2) {
            $val = $expression[1];
            $clause = "=";
            $pattern = "%s %s %s";
            $val = DxUtils::quoteStringValue($val, false);
        } elseif ($itemsCount == 3) {
            $clause = trim($expression[1]);
            $val = $expression[2];
            $pattern = "";
            if (is_null($val)) {
                $val = DxUtils::quoteStringValue($val, false);
                $pattern = "%s %s %s";
                switch ($clause) {
                    case "=":
                        $clause = self::$IS_OP;
                        break;
                    case "<>":
                        $clause = self::$IS_OP . " " . self::$NOT_OP;
                        break;
                }
            } else {
                switch ($clause) {
                    case "=":
                    case "<>":
                    case ">":
                    case ">=":
                    case "<":
                    case "<=":
                        $pattern = "%s %s %s";
                        $val = DxUtils::quoteStringValue($val, false);
                        break;
                    case "startswith":
                        $pattern = "%s %s '%s%%'";
                        $clause = self::$LIKE_OP;
                        $val = addcslashes($val, "%_");
                        break;
                    case "endswith":
                        $pattern = "%s %s '%%%s'";
                        $val = addcslashes($val, "%_");
                        $clause = self::$LIKE_OP;
                        break;
                    case "contains":
                        $pattern = "%s %s '%%%s%%'";
                        $val = addcslashes($val, "%_");
                        $clause = self::$LIKE_OP;
                        break;
                    case "notcontains":
                        $pattern = "%s %s '%%%s%%'";
                        $val = addcslashes($val, "%_");
                        $clause = sprintf("%s %s", self::$NOT_OP, self::$LIKE_OP);
                        break;
                    default:
                        $clause = "";
                }
            }
        }
        if ($callback) {
            $callback($fieldName, $clause, $val);
        }
        if ($clause) {
            $result = sprintf($pattern, $fieldName, $clause, $val);
        }
        return $result;
    }

    /**
     * 将 Dx Filter条件字符串规则 转 sql 的 where 规则
     *
     * @param string|array $expression
     * @param \Closure|null $callback $callback function(&$fieldName, &$op, &$value) 字段名,操作符,值, 操作符返回 '' 则废弃当前条件
     * @return string
     */
    public static function getWhere(string|array $expression, \Closure $callback = null): string
    {
        if (is_string($expression)) {
            $expression = json_decode($expression, true);
        }
        $result = "";
        $prevItemWasArray = false;
        foreach ($expression as $index => $item) {
            if (is_string($item)) {
                $prevItemWasArray = false;
                if ($index == 0) {
                    if ($item == "!") {
                        $result .= sprintf("%s ", self::$NOT_OP);
                        continue;
                    }
                    $result .=  (isset($expression) && is_array($expression)) ? self::privateGetSimpleExpr($expression, $callback) : "";
                    break;
                }
                if ($result) {
                    $strItem = strtoupper(trim($item));
                    if ($strItem == self::$AND_OP || $strItem == self::$OR_OP) {
                        $result .= sprintf(" %s ", $strItem);
                    }
                }
                continue;
            }
            if (is_array($item)) {
                if ($prevItemWasArray) {
                    $result .= sprintf(" %s ", self::$AND_OP);
                }
                $result .= self::getWhere($item, $callback);
                $prevItemWasArray = true;
            }
        }
        if ($result && $result != self::$NOT_OP . ' ') {
            $result = "(" . $result . ")";
        }
        return $result;
    }
    public static function getWhereByKey($key): string
    {
        $result = "";
        foreach ($key as $prop => $value) {
            $templ = strlen($result) == 0 ?
                "%s = %s" :
                " " . self::$AND_OP . " %s = %s";
            $result .= sprintf(
                $templ,
                DxUtils::quoteStringValue($prop),
                DxUtils::quoteStringValue($value, false)
            );
        }
        return $result;
    }
}
