/*
 * Copyright (c) 2019 TAOS Data, Inc. <jhtao@taosdata.com>
 *
 * This program is free software: you can use, redistribute, and/or modify
 * it under the terms of the GNU Affero General Public License, version 3
 * or later ("AGPL"), as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#include "functionMgt.h"
#include "parInt.h"
#include "scalar.h"
#include "ttime.h"

typedef struct SCalcConstContext {
  SParseContext* pParseCxt;
  SMsgBuf        msgBuf;
  int32_t        code;
} SCalcConstContext;

static int32_t calcConstQuery(SCalcConstContext* pCxt, SNode* pStmt, bool subquery);

static int32_t calcConstSubquery(SCalcConstContext* pCxt, STempTableNode* pTempTable) {
  return calcConstQuery(pCxt, pTempTable->pSubquery, true);
}

static int32_t calcConstNode(SNode** pNode) {
  if (NULL == *pNode) {
    return TSDB_CODE_SUCCESS;
  }

  SNode*  pNew = NULL;
  int32_t code = scalarCalculateConstants(*pNode, &pNew);
  if (TSDB_CODE_SUCCESS == code) {
    *pNode = pNew;
  }
  return code;
}

static int32_t calcConstList(SNodeList* pList) {
  SNode* pNode = NULL;
  FOREACH(pNode, pList) {
    SNode*  pNew = NULL;
    int32_t code = scalarCalculateConstants(pNode, &pNew);
    if (TSDB_CODE_SUCCESS == code) {
      REPLACE_NODE(pNew);
    } else {
      return code;
    }
  }
  return TSDB_CODE_SUCCESS;
}

static bool isCondition(const SNode* pNode) {
  if (QUERY_NODE_OPERATOR == nodeType(pNode)) {
    return nodesIsComparisonOp((const SOperatorNode*)pNode);
  }
  return (QUERY_NODE_LOGIC_CONDITION == nodeType(pNode));
}

static int32_t rewriteIsTrue(SNode* pSrc, SNode** pIsTrue) {
  SOperatorNode* pOp = (SOperatorNode*)nodesMakeNode(QUERY_NODE_OPERATOR);
  if (NULL == pOp) {
    return TSDB_CODE_OUT_OF_MEMORY;
  }
  pOp->opType = OP_TYPE_IS_TRUE;
  pOp->pLeft = pSrc;
  pOp->node.resType.type = TSDB_DATA_TYPE_BOOL;
  pOp->node.resType.bytes = tDataTypes[TSDB_DATA_TYPE_BOOL].bytes;
  *pIsTrue = (SNode*)pOp;
  return TSDB_CODE_SUCCESS;
}

static EDealRes doRewriteCondition(SNode** pNode, void* pContext) {
  if (QUERY_NODE_LOGIC_CONDITION == nodeType(*pNode)) {
    SNode* pParam = NULL;
    FOREACH(pParam, ((SLogicConditionNode*)*pNode)->pParameterList) {
      if (!isCondition(pParam)) {
        SNode* pIsTrue = NULL;
        if (TSDB_CODE_SUCCESS != rewriteIsTrue(pParam, &pIsTrue)) {
          ((SCalcConstContext*)pContext)->code = TSDB_CODE_OUT_OF_MEMORY;
          return DEAL_RES_ERROR;
        }
        REPLACE_NODE(pIsTrue);
      }
    }
  }
  return DEAL_RES_CONTINUE;
}

static int32_t rewriteCondition(SCalcConstContext* pCxt, SNode** pNode) {
  if (!isCondition(*pNode)) {
    return rewriteIsTrue(*pNode, pNode);
  }
  nodesRewriteExprPostOrder(pNode, doRewriteCondition, pCxt);
  return pCxt->code;
}

static int32_t calcConstCondition(SCalcConstContext* pCxt, SNode** pNode) {
  int32_t code = rewriteCondition(pCxt, pNode);
  if (TSDB_CODE_SUCCESS == code) {
    code = calcConstNode(pNode);
  }
  return code;
}

static int32_t rewriteConditionForFromTable(SCalcConstContext* pCxt, SNode* pTable) {
  int32_t code = TSDB_CODE_SUCCESS;
  switch (nodeType(pTable)) {
    case QUERY_NODE_TEMP_TABLE: {
      code = calcConstSubquery(pCxt, (STempTableNode*)pTable);
      break;
    }
    case QUERY_NODE_JOIN_TABLE: {
      SJoinTableNode* pJoin = (SJoinTableNode*)pTable;
      code = rewriteConditionForFromTable(pCxt, pJoin->pLeft);
      if (TSDB_CODE_SUCCESS == code) {
        code = rewriteConditionForFromTable(pCxt, pJoin->pRight);
      }
      if (TSDB_CODE_SUCCESS == code && NULL != pJoin->pOnCond) {
        code = calcConstCondition(pCxt, &pJoin->pOnCond);
      }
      // todo empty table
      break;
    }
    default:
      break;
  }
  return code;
}

static int32_t calcConstFromTable(SCalcConstContext* pCxt, SNode* pTable) {
  return rewriteConditionForFromTable(pCxt, pTable);
}

static void rewriteConstCondition(SNode** pCond, bool* pAlwaysFalse) {
  if (QUERY_NODE_VALUE != nodeType(*pCond)) {
    return;
  }
  if (((SValueNode*)*pCond)->datum.b) {
    nodesDestroyNode(*pCond);
    *pCond = NULL;
  } else {
    *pAlwaysFalse = true;
  }
}

static int32_t calcConstStmtCondition(SCalcConstContext* pCxt, SNode** pCond, bool* pAlwaysFalse) {
  if (NULL == *pCond) {
    return TSDB_CODE_SUCCESS;
  }

  int32_t code = rewriteCondition(pCxt, pCond);
  if (TSDB_CODE_SUCCESS == code) {
    code = calcConstNode(pCond);
  }
  if (TSDB_CODE_SUCCESS == code) {
    rewriteConstCondition(pCond, pAlwaysFalse);
  }
  return code;
}

static int32_t calcConstProject(SNode* pProject, bool dual, SNode** pNew) {
  SArray* pAssociation = NULL;
  if (NULL != ((SExprNode*)pProject)->pAssociation) {
    pAssociation = taosArrayDup(((SExprNode*)pProject)->pAssociation, NULL);
    if (NULL == pAssociation) {
      return TSDB_CODE_OUT_OF_MEMORY;
    }
  }

  char aliasName[TSDB_COL_NAME_LEN] = {0};
  strcpy(aliasName, ((SExprNode*)pProject)->aliasName);
  int32_t code = TSDB_CODE_SUCCESS;
  if (dual) {
    code = scalarCalculateConstantsFromDual(pProject, pNew);
  } else {
    code = scalarCalculateConstants(pProject, pNew);
  }
  if (TSDB_CODE_SUCCESS == code) {
    strcpy(((SExprNode*)*pNew)->aliasName, aliasName);
    if (QUERY_NODE_VALUE == nodeType(*pNew) && NULL != pAssociation) {
      int32_t size = taosArrayGetSize(pAssociation);
      for (int32_t i = 0; i < size; ++i) {
        SNode** pCol = taosArrayGetP(pAssociation, i);
        nodesDestroyNode(*pCol);
        *pCol = nodesCloneNode(*pNew);
        if (NULL == *pCol) {
          code = TSDB_CODE_OUT_OF_MEMORY;
          break;
        }
      }
    }
  }
  taosArrayDestroy(pAssociation);
  return code;
}

static bool isUselessCol(SExprNode* pProj) {
  if (QUERY_NODE_FUNCTION == nodeType(pProj) && !fmIsScalarFunc(((SFunctionNode*)pProj)->funcId) &&
      !fmIsPseudoColumnFunc(((SFunctionNode*)pProj)->funcId)) {
    return false;
  }
  return NULL == ((SExprNode*)pProj)->pAssociation;
}

static SNode* createConstantValue() {
  SValueNode* pVal = (SValueNode*)nodesMakeNode(QUERY_NODE_VALUE);
  if (NULL == pVal) {
    return NULL;
  }
  pVal->node.resType.type = TSDB_DATA_TYPE_INT;
  pVal->node.resType.bytes = tDataTypes[TSDB_DATA_TYPE_INT].bytes;
  const int32_t val = 1;
  nodesSetValueNodeValue(pVal, (void*)&val);
  pVal->translate = true;
  return (SNode*)pVal;
}

static int32_t calcConstProjections(SCalcConstContext* pCxt, SSelectStmt* pSelect, bool subquery) {
  SNode* pProj = NULL;
  WHERE_EACH(pProj, pSelect->pProjectionList) {
    if (subquery && !pSelect->isDistinct && isUselessCol((SExprNode*)pProj)) {
      ERASE_NODE(pSelect->pProjectionList);
      continue;
    }
    SNode*  pNew = NULL;
    int32_t code = calcConstProject(pProj, (NULL == pSelect->pFromTable), &pNew);
    if (TSDB_CODE_SUCCESS == code) {
      REPLACE_NODE(pNew);
    } else {
      return code;
    }
    WHERE_NEXT;
  }
  if (0 == LIST_LENGTH(pSelect->pProjectionList)) {
    return nodesListStrictAppend(pSelect->pProjectionList, createConstantValue());
  }
  return TSDB_CODE_SUCCESS;
}

static int32_t calcConstGroupBy(SCalcConstContext* pCxt, SSelectStmt* pSelect) {
  int32_t code = calcConstList(pSelect->pGroupByList);
  if (TSDB_CODE_SUCCESS == code) {
    SNode* pNode = NULL;
    FOREACH(pNode, pSelect->pGroupByList) {
      SNode* pGroupPara = NULL;
      FOREACH(pGroupPara, ((SGroupingSetNode*)pNode)->pParameterList) {
        if (QUERY_NODE_VALUE != nodeType(pGroupPara)) {
          return code;
        }
      }
    }
    NODES_DESTORY_LIST(pSelect->pGroupByList);
  }
  return code;
}

static int32_t calcConstSelectWithoutFrom(SCalcConstContext* pCxt, SSelectStmt* pSelect, bool subquery) {
  return calcConstProjections(pCxt, pSelect, subquery);
}

static int32_t calcConstSelectFrom(SCalcConstContext* pCxt, SSelectStmt* pSelect, bool subquery) {
  int32_t code = calcConstFromTable(pCxt, pSelect->pFromTable);
  if (TSDB_CODE_SUCCESS == code) {
    code = calcConstProjections(pCxt, pSelect, subquery);
  }
  if (TSDB_CODE_SUCCESS == code) {
    code = calcConstStmtCondition(pCxt, &pSelect->pWhere, &pSelect->isEmptyResult);
  }
  if (TSDB_CODE_SUCCESS == code) {
    code = calcConstList(pSelect->pPartitionByList);
  }
  if (TSDB_CODE_SUCCESS == code) {
    code = calcConstList(pSelect->pTags);
  }
  if (TSDB_CODE_SUCCESS == code) {
    code = calcConstNode(&pSelect->pSubtable);
  }
  if (TSDB_CODE_SUCCESS == code) {
    code = calcConstNode(&pSelect->pWindow);
  }
  if (TSDB_CODE_SUCCESS == code) {
    code = calcConstGroupBy(pCxt, pSelect);
  }
  if (TSDB_CODE_SUCCESS == code) {
    code = calcConstStmtCondition(pCxt, &pSelect->pHaving, &pSelect->isEmptyResult);
  }
  if (TSDB_CODE_SUCCESS == code) {
    code = calcConstList(pSelect->pOrderByList);
  }
  return code;
}

static int32_t calcConstSelect(SCalcConstContext* pCxt, SSelectStmt* pSelect, bool subquery) {
  if (NULL == pSelect->pFromTable) {
    return calcConstSelectWithoutFrom(pCxt, pSelect, subquery);
  } else {
    return calcConstSelectFrom(pCxt, pSelect, subquery);
  }
}

static int32_t calcConstDelete(SCalcConstContext* pCxt, SDeleteStmt* pDelete) {
  int32_t code = calcConstFromTable(pCxt, pDelete->pFromTable);
  if (TSDB_CODE_SUCCESS == code) {
    code = calcConstStmtCondition(pCxt, &pDelete->pWhere, &pDelete->deleteZeroRows);
  }
  if (code == TSDB_CODE_SUCCESS && pDelete->timeRange.skey > pDelete->timeRange.ekey) {
    pDelete->deleteZeroRows = true;
  }
  return code;
}

static int32_t calcConstInsert(SCalcConstContext* pCxt, SInsertStmt* pInsert) {
  int32_t code = calcConstFromTable(pCxt, pInsert->pTable);
  if (TSDB_CODE_SUCCESS == code) {
    code = calcConstQuery(pCxt, pInsert->pQuery, false);
  }
  return code;
}

static SNodeList* getChildProjection(SNode* pStmt) {
  switch (nodeType(pStmt)) {
    case QUERY_NODE_SELECT_STMT:
      return ((SSelectStmt*)pStmt)->pProjectionList;
    case QUERY_NODE_SET_OPERATOR:
      return ((SSetOperator*)pStmt)->pProjectionList;
    default:
      break;
  }
  return NULL;
}

static void eraseSetOpChildProjection(SSetOperator* pSetOp, int32_t index) {
  SNodeList* pLeftProjs = getChildProjection(pSetOp->pLeft);
  nodesListErase(pLeftProjs, nodesListGetCell(pLeftProjs, index));
  if (QUERY_NODE_SET_OPERATOR == nodeType(pSetOp->pLeft)) {
    eraseSetOpChildProjection((SSetOperator*)pSetOp->pLeft, index);
  }
  SNodeList* pRightProjs = getChildProjection(pSetOp->pRight);
  nodesListErase(pRightProjs, nodesListGetCell(pRightProjs, index));
  if (QUERY_NODE_SET_OPERATOR == nodeType(pSetOp->pRight)) {
    eraseSetOpChildProjection((SSetOperator*)pSetOp->pRight, index);
  }
}

typedef struct SNotRefByOrderByCxt {
  SColumnNode* pCol;
  bool         hasThisCol;
} SNotRefByOrderByCxt;

static EDealRes notRefByOrderByImpl(SNode* pNode, void* pContext) {
  if (QUERY_NODE_COLUMN == nodeType(pNode)) {
    SNotRefByOrderByCxt* pCxt = (SNotRefByOrderByCxt*)pContext;
    if (nodesEqualNode((SNode*)pCxt->pCol, pNode)) {
      pCxt->hasThisCol = true;
      return DEAL_RES_END;
    }
  }
  return DEAL_RES_CONTINUE;
}

static bool notRefByOrderBy(SColumnNode* pCol, SNodeList* pOrderByList) {
  SNotRefByOrderByCxt cxt = {.pCol = pCol, .hasThisCol = false};
  nodesWalkExprs(pOrderByList, notRefByOrderByImpl, &cxt);
  return !cxt.hasThisCol;
}

static bool isDistinctSubQuery(SNode* pNode) {
  if (NULL == pNode) {
    return false;
  }
  switch (nodeType(pNode)) {
    case QUERY_NODE_SELECT_STMT:
      return ((SSelectStmt*)pNode)->isDistinct;
    case QUERY_NODE_SET_OPERATOR:
      return isDistinctSubQuery((((SSetOperator*)pNode)->pLeft)) || isDistinctSubQuery((((SSetOperator*)pNode)->pLeft));
    default:
      break;
  }
  return false;
}

static bool isSetUselessCol(SSetOperator* pSetOp, int32_t index, SExprNode* pProj) {
  if (!isUselessCol(pProj)) {
    return false;
  }

  SNodeList* pLeftProjs = getChildProjection(pSetOp->pLeft);
  if (!isUselessCol((SExprNode*)nodesListGetNode(pLeftProjs, index)) || isDistinctSubQuery(pSetOp->pLeft)) {
    return false;
  }

  SNodeList* pRightProjs = getChildProjection(pSetOp->pRight);
  if (!isUselessCol((SExprNode*)nodesListGetNode(pRightProjs, index)) || isDistinctSubQuery(pSetOp->pLeft)) {
    return false;
  }

  return true;
}

static int32_t calcConstSetOpProjections(SCalcConstContext* pCxt, SSetOperator* pSetOp, bool subquery) {
  if (subquery && pSetOp->opType == SET_OP_TYPE_UNION) {
    return TSDB_CODE_SUCCESS;
  }
  int32_t index = 0;
  SNode*  pProj = NULL;
  WHERE_EACH(pProj, pSetOp->pProjectionList) {
    if (subquery && notRefByOrderBy((SColumnNode*)pProj, pSetOp->pOrderByList) &&
        isSetUselessCol(pSetOp, index, (SExprNode*)pProj)) {
      ERASE_NODE(pSetOp->pProjectionList);
      eraseSetOpChildProjection(pSetOp, index);
      continue;
    }
    ++index;
    WHERE_NEXT;
  }
  if (0 == LIST_LENGTH(pSetOp->pProjectionList)) {
    return nodesListStrictAppend(pSetOp->pProjectionList, createConstantValue());
  }
  return TSDB_CODE_SUCCESS;
}

static int32_t calcConstSetOperator(SCalcConstContext* pCxt, SSetOperator* pSetOp, bool subquery) {
  int32_t code = calcConstSetOpProjections(pCxt, pSetOp, subquery);
  if (TSDB_CODE_SUCCESS == code) {
    code = calcConstQuery(pCxt, pSetOp->pLeft, false);
  }
  if (TSDB_CODE_SUCCESS == code) {
    code = calcConstQuery(pCxt, pSetOp->pRight, false);
  }
  if (TSDB_CODE_SUCCESS == code) {
    code = calcConstList(pSetOp->pOrderByList);
  }
  return code;
}

static int32_t calcConstQuery(SCalcConstContext* pCxt, SNode* pStmt, bool subquery) {
  int32_t code = TSDB_CODE_SUCCESS;
  switch (nodeType(pStmt)) {
    case QUERY_NODE_SELECT_STMT:
      code = calcConstSelect(pCxt, (SSelectStmt*)pStmt, subquery);
      break;
    case QUERY_NODE_EXPLAIN_STMT:
      code = calcConstQuery(pCxt, ((SExplainStmt*)pStmt)->pQuery, subquery);
      break;
    case QUERY_NODE_SET_OPERATOR: {
      code = calcConstSetOperator(pCxt, (SSetOperator*)pStmt, subquery);
      break;
    }
    case QUERY_NODE_DELETE_STMT:
      code = calcConstDelete(pCxt, (SDeleteStmt*)pStmt);
      break;
    case QUERY_NODE_INSERT_STMT:
      code = calcConstInsert(pCxt, (SInsertStmt*)pStmt);
      break;
    default:
      break;
  }
  return code;
}

static bool isEmptyResultQuery(SNode* pStmt) {
  bool isEmptyResult = false;
  switch (nodeType(pStmt)) {
    case QUERY_NODE_SELECT_STMT:
      isEmptyResult = ((SSelectStmt*)pStmt)->isEmptyResult;
      break;
    case QUERY_NODE_EXPLAIN_STMT:
      isEmptyResult = isEmptyResultQuery(((SExplainStmt*)pStmt)->pQuery);
      break;
    case QUERY_NODE_SET_OPERATOR: {
      SSetOperator* pSetOp = (SSetOperator*)pStmt;
      isEmptyResult = isEmptyResultQuery(pSetOp->pLeft);
      if (isEmptyResult) {
        isEmptyResult = isEmptyResultQuery(pSetOp->pRight);
      }
      break;
    }
    case QUERY_NODE_DELETE_STMT:
      isEmptyResult = ((SDeleteStmt*)pStmt)->deleteZeroRows;
      break;
    default:
      break;
  }
  return isEmptyResult;
}

static void resetProjectNullTypeImpl(SNodeList* pProjects) {
  SNode* pProj = NULL;
  FOREACH(pProj, pProjects) {
    SExprNode* pExpr = (SExprNode*)pProj;
    if (TSDB_DATA_TYPE_NULL == pExpr->resType.type) {
      pExpr->resType.type = TSDB_DATA_TYPE_VARCHAR;
      pExpr->resType.bytes = 0;
    }
  }
}

static void resetProjectNullType(SNode* pStmt) {
  switch (nodeType(pStmt)) {
    case QUERY_NODE_SELECT_STMT:
      resetProjectNullTypeImpl(((SSelectStmt*)pStmt)->pProjectionList);
      break;
    case QUERY_NODE_SET_OPERATOR: {
      resetProjectNullTypeImpl(((SSetOperator*)pStmt)->pProjectionList);
      break;
    }
    default:
      break;
  }
}

int32_t calculateConstant(SParseContext* pParseCxt, SQuery* pQuery) {
  SCalcConstContext cxt = {.pParseCxt = pParseCxt,
                           .msgBuf.buf = pParseCxt->pMsg,
                           .msgBuf.len = pParseCxt->msgLen,
                           .code = TSDB_CODE_SUCCESS};
  int32_t           code = calcConstQuery(&cxt, pQuery->pRoot, false);
  if (TSDB_CODE_SUCCESS == code) {
    resetProjectNullType(pQuery->pRoot);
    if (isEmptyResultQuery(pQuery->pRoot)) {
      pQuery->execMode = QUERY_EXEC_MODE_EMPTY_RESULT;
    }
  }
  return code;
}
