/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.iotdb.db.protocol.influxdb.handler;

import org.apache.iotdb.db.protocol.influxdb.function.InfluxFunction;
import org.apache.iotdb.db.protocol.influxdb.function.InfluxFunctionValue;
import org.apache.iotdb.db.protocol.influxdb.meta.InfluxDBMetaManagerFactory;
import org.apache.iotdb.db.protocol.influxdb.util.FilterUtils;
import org.apache.iotdb.db.protocol.influxdb.util.QueryResultUtils;
import org.apache.iotdb.db.protocol.influxdb.util.StringUtils;
import org.apache.iotdb.db.service.thrift.impl.NewInfluxDBServiceImpl;
import org.apache.iotdb.service.rpc.thrift.TSExecuteStatementResp;
import org.apache.iotdb.tsfile.read.expression.IExpression;
import org.apache.iotdb.tsfile.read.expression.impl.SingleSeriesExpression;

import org.influxdb.dto.QueryResult;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/** Query Handler for NewIoTDB When schema region is tag schema region */
public class TagQueryHandler extends NewQueryHandler {

  /**
   * If the function in the influxdb query request is also supported by IoTDB, use the IoTDB syntax
   * to get the result
   *
   * @param database database of influxdb
   * @param measurement measurement of influxdb
   * @param function influxdb function
   * @param sessionid session id
   * @return influxdb function value
   */
  @Override
  public InfluxFunctionValue updateByIoTDBFunc(
      String database, String measurement, InfluxFunction function, long sessionid) {
    String path = "root." + database + ".measurement." + measurement;
    return updateByIoTDBFunc(path, function, sessionid);
  }

  /**
   * Query the result according to the query SQL supported by IoTDB
   *
   * @param querySql query sql
   * @param database database of influxdb
   * @param measurement measurement of influxdb
   * @param tagOrders tag orders
   * @param fieldOrders field orders
   * @param sessionId session id
   * @return query result
   */
  @Override
  public QueryResult queryByConditions(
      String querySql,
      String database,
      String measurement,
      Map<String, Integer> tagOrders,
      Map<String, Integer> fieldOrders,
      long sessionId) {
    TSExecuteStatementResp executeStatementResp =
        NewInfluxDBServiceImpl.executeStatement(querySql, sessionId);
    return QueryResultUtils.iotdbResultConvertInfluxResult(
        executeStatementResp, database, measurement, tagOrders, fieldOrders);
  }

  @Override
  public QueryResult queryByConditions(
      List<IExpression> expressions,
      String database,
      String measurement,
      Map<String, Integer> fieldOrders,
      Long sessionId) {
    List<SingleSeriesExpression> fieldExpressions = new ArrayList<>();
    List<SingleSeriesExpression> tagExpressions = new ArrayList<>();
    Map<String, Integer> tagOrders =
        InfluxDBMetaManagerFactory.getInstance().getTagOrders(database, measurement, sessionId);
    for (IExpression expression : expressions) {
      SingleSeriesExpression singleSeriesExpression = ((SingleSeriesExpression) expression);
      // the current condition is in tag
      if (tagOrders.containsKey(singleSeriesExpression.getSeriesPath().getFullPath())) {
        tagExpressions.add(singleSeriesExpression);
      } else {
        fieldExpressions.add(singleSeriesExpression);
      }
    }
    // construct the actual query path
    StringBuilder curQueryPath =
        new StringBuilder("root." + database + ".measurement." + measurement);
    for (SingleSeriesExpression singleSeriesExpression : tagExpressions) {
      String tagKey = singleSeriesExpression.getSeriesPath().getFullPath();
      String tagValue =
          StringUtils.removeQuotation(
              FilterUtils.getFilterStringValue(singleSeriesExpression.getFilter()));
      curQueryPath.append(".").append(tagKey).append(".").append(tagValue);
    }
    curQueryPath.append(".**");

    // construct actual query condition
    StringBuilder realIotDBCondition = new StringBuilder();
    for (int i = 0; i < fieldExpressions.size(); i++) {
      SingleSeriesExpression singleSeriesExpression = fieldExpressions.get(i);
      if (i != 0) {
        realIotDBCondition.append(" and ");
      }
      realIotDBCondition
          .append(singleSeriesExpression.getSeriesPath().getFullPath())
          .append(" ")
          .append((FilterUtils.getFilerSymbol(singleSeriesExpression.getFilter())))
          .append(" ")
          .append(FilterUtils.getFilterStringValue(singleSeriesExpression.getFilter()));
    }
    // actual query SQL statement
    String realQuerySql;

    realQuerySql = "select * from " + curQueryPath;
    if (!(realIotDBCondition.length() == 0)) {
      realQuerySql += " where " + realIotDBCondition;
    }
    realQuerySql += " align by device";
    return queryByConditions(
        realQuerySql, database, measurement, tagOrders, fieldOrders, sessionId);
  }
}
