package org.zstack.tag;

import org.apache.commons.lang.StringUtils;
import org.zstack.header.exception.CloudRuntimeException;
import org.zstack.header.query.APIQueryMessage;
import org.zstack.header.query.QueryCondition;
import org.zstack.header.query.QueryOp;
import org.zstack.query.AbstractMysqlQuerySubQueryExtension;
import org.zstack.query.QueryUtils;
import org.zstack.utils.CollectionDSL;
import org.zstack.utils.gson.JSONObjectUtil;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 */
public class TagSubQueryExtension extends AbstractMysqlQuerySubQueryExtension {
    public static String USER_TAG_NAME = "__userTag__";
    public static String SYS_TAG_NAME = "__systemTag__";

    private static List<String> IN_CONDITIONS;
    private static List<String> NOT_IN_CONDITIONS;

    static {
        IN_CONDITIONS = CollectionDSL.list(
                QueryOp.EQ.toString(),
                QueryOp.GT.toString(),
                QueryOp.GT_AND_EQ.toString(),
                QueryOp.LT.toString(),
                QueryOp.LT_AND_EQ.toString(),
                QueryOp.IN.toString(),
                QueryOp.LIKE.toString(),
                QueryOp.NOT_NULL.toString()
        );

        NOT_IN_CONDITIONS = CollectionDSL.list(
                QueryOp.NOT_EQ.toString(),
                QueryOp.NOT_IN.toString(),
                QueryOp.IS_NULL.toString(),
                QueryOp.NOT_LIKE.toString()
        );
    }

    private String reverseOpIfNeed(QueryCondition cond) {
        if (QueryOp.NOT_EQ.equals(cond.getOp())) {
            return QueryOp.EQ.toString();
        } else if (QueryOp.NOT_IN.equals(cond.getOp())) {
            return QueryOp.IN.toString();
        } else if (QueryOp.IS_NULL.equals(cond.getOp())) {
            return QueryOp.NOT_NULL.toString();
        } else if (QueryOp.NOT_LIKE.equals(cond.getOp())) {
            return QueryOp.LIKE.toString();
        } else {
            return cond.getOp();
        }
    }

    private String buildCondition(String field, QueryCondition cond) {
        if (QueryOp.IN.equals(cond.getOp()) || QueryOp.NOT_IN.equals(cond.getOp())) {
            String[] values = cond.getValue().split(",");
            List<String> vals = new ArrayList<String>();
            for (String val : values) {
                vals.add(String.format("'%s'", val));
            }

            return String.format("%s %s (%s)", field, reverseOpIfNeed(cond), StringUtils.join(vals, ","));
        } else if (QueryOp.IS_NULL.equals(cond.getOp()) || QueryOp.NOT_NULL.equals(cond.getOp())) {
            return String.format("%s %s", field, reverseOpIfNeed(cond));
        } else {
            return String.format("%s %s '%s'", field, reverseOpIfNeed(cond), cond.getValue());
        }
    }

    private String chooseOp (QueryCondition cond) {
        if (IN_CONDITIONS.contains(cond.getOp())) {
            return "in";
        }

        if (NOT_IN_CONDITIONS.contains(cond.getOp())) {
            return "not in";
        }

        throw new CloudRuntimeException(String.format("invalid comparison operator[%s]; %s", cond.getOp(), JSONObjectUtil.toJsonString(cond)));
    }

    private String getResourceTypeString(Class entityClass) {
        List<String> rtypes = new ArrayList<String>();
        while (entityClass != Object.class) {
            rtypes.add(String.format("'%s'", entityClass.getSimpleName()));
            entityClass = entityClass.getSuperclass();
        }

        return StringUtils.join(rtypes, ",");
    }

    @Override
    public String makeSubquery(APIQueryMessage msg, Class inventoryClass) {
        List<String> resultQuery = new ArrayList<String>();
        Class entityClass = QueryUtils.getEntityClassFromInventoryClass(inventoryClass);
        String primaryKey = QueryUtils.getPrimaryKeyNameFromEntityClass(entityClass);
        String typeString = getResourceTypeString(entityClass);
        String invname = inventoryClass.getSimpleName().toLowerCase();

        for (QueryCondition cond : msg.getConditions()) {
            if (cond.getName().equals(USER_TAG_NAME)) {
                List<String> condStrs = new ArrayList<String>();
                condStrs.add(buildCondition("user.tag", cond));
                condStrs.add(String.format("user.resourceType in (%s)", typeString));
                resultQuery.add(String.format("%s.%s %s (select user.resourceUuid from UserTagVO user where %s)",
                        invname, primaryKey, chooseOp(cond), StringUtils.join(condStrs, " and ")));
            } else if (cond.getName().equals(SYS_TAG_NAME)) {
                List<String> condStrs = new ArrayList<String>();
                condStrs.add(buildCondition("sys.tag", cond));
                condStrs.add(String.format("sys.resourceType in (%s)", typeString));
                resultQuery.add(String.format("%s.%s %s (select sys.resourceUuid from SystemTagVO sys where %s)",
                        invname, primaryKey, chooseOp(cond), StringUtils.join(condStrs, " and ")));
            }
        }

        if (resultQuery.isEmpty()) {
            return null;
        } else {
            return StringUtils.join(resultQuery, " and ");
        }
    }

    @Override
    public List<String> getEscapeConditionNames() {
        return Arrays.asList(USER_TAG_NAME, SYS_TAG_NAME);
    }
}

@@ -1542,16 +1542,27 @@
 .NOT_EQ.
+toString().
 equals(c
@@ -1645,32 +1645,43 @@
 (QueryOp.NOT_IN.
+toString().
 equals(cond.getO
@@ -1757,32 +1757,43 @@
 QueryOp.IS_NULL.
+toString().
 equals(cond.getO
@@ -1884,16 +1884,27 @@
 OT_LIKE.
+toString().
 equals(c
@@ -2124,16 +2124,27 @@
 ryOp.IN.
+toString().
 equals(c
@@ -2174,16 +2174,27 @@
 .NOT_IN.
+toString().
 equals(c
@@ -2571,24 +2571,35 @@
 yOp.IS_NULL.
+toString().
 equals(cond.
@@ -2627,16 +2627,27 @@
 OT_NULL.
+toString().
 equals(c

update zstack-18522