/**
 * Copyright (c) 2021 OceanBase
 * OceanBase CE is licensed under Mulan PubL v2.
 * You can use this software according to the terms and conditions of the Mulan PubL v2.
 * You may obtain a copy of Mulan PubL v2 at:
 *          http://license.coscl.org.cn/MulanPubL-2.0
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PubL v2 for more details.
 */

#define USING_LOG_PREFIX SQL_ENG
#include "sql/engine/expr/ob_expr_time.h"
#include "lib/timezone/ob_time_convert.h"
#include "sql/engine/ob_exec_context.h"
#include "sql/engine/expr/ob_datum_cast.h"
#include "sql/engine/expr/ob_expr_day_of_func.h"

using namespace oceanbase::common;
using namespace oceanbase::sql;

namespace oceanbase {
namespace sql {

ObExprTime::ObExprTime(ObIAllocator& alloc) : ObFuncExprOperator(alloc, T_FUN_SYS_TIME, N_TIME, 1, NOT_ROW_DIMENSION)
{}

ObExprTime::~ObExprTime()
{}

int ObExprTime::calc_result_type1(ObExprResType& type, ObExprResType& type1, ObExprTypeCtx& type_ctx) const
{
  int ret = OB_SUCCESS;
  // param will be casted to ObTimeType before calculation
  // see ObExprOperator::cast_operand_type for more details if necessary
  type1.set_calc_type(ObTimeType);
  type.set_type(ObTimeType);
  // deduce scale now.
  int16_t scale1 = MIN(type1.get_scale(), MAX_SCALE_FOR_TEMPORAL);
  int16_t scale = (SCALE_UNKNOWN_YET == scale1) ? MAX_SCALE_FOR_TEMPORAL : scale1;
  type.set_scale(scale);
  type_ctx.set_cast_mode(type_ctx.get_cast_mode() | CM_NULL_ON_WARN);
  return ret;
}

int ObExprTime::calc_result1(ObObj& result, const ObObj& obj, ObExprCtx& expr_ctx) const
{
  int ret = OB_SUCCESS;
  if (OB_UNLIKELY(obj.is_null())) {
    result.set_null();
  } else {
    TYPE_CHECK(obj, ObTimeType);
    result = obj;
  }
  UNUSED(expr_ctx);
  return ret;
}

int ObExprTime::cg_expr(ObExprCGCtx& op_cg_ctx, const ObRawExpr& raw_expr, ObExpr& rt_expr) const
{
  UNUSED(op_cg_ctx);
  UNUSED(raw_expr);
  int ret = OB_SUCCESS;
  if (rt_expr.arg_cnt_ != 1) {
    ret = OB_INVALID_ARGUMENT;
    LOG_WARN("time expr should have one param", K(ret), K(rt_expr.arg_cnt_));
  } else if (OB_ISNULL(rt_expr.args_) || OB_ISNULL(rt_expr.args_[0])) {
    ret = OB_ERR_UNEXPECTED;
    LOG_WARN("children of time expr is null", K(ret), K(rt_expr.args_));
  } else {
    rt_expr.eval_func_ = ObExprTime::calc_time;
  }
  return ret;
}

int ObExprTime::calc_time(const ObExpr& expr, ObEvalCtx& ctx, ObDatum& expr_datum)
{
  int ret = OB_SUCCESS;
  ObDatum* param_datum = NULL;
  if (OB_FAIL(expr.args_[0]->eval(ctx, param_datum))) {
    LOG_WARN("eval param value failed");
  } else if (OB_UNLIKELY(param_datum->is_null())) {
    expr_datum.set_null();
  } else {
    expr_datum.set_time(param_datum->get_time());
  }
  return ret;
}

ObExprTimeBase::ObExprTimeBase(ObIAllocator& alloc, int32_t date_type, ObExprOperatorType type, const char* name)
    : ObFuncExprOperator(alloc, type, name, 1, NOT_ROW_DIMENSION)
{
  dt_type_ = date_type;
}

ObExprTimeBase::~ObExprTimeBase()
{}

static int ob_expr_convert_to_dt_or_time(
    const ObObj& obj, ObExprCtx& expr_ctx, ObTime& ot, bool with_date, bool is_dayofmonth)
{
  int ret = OB_SUCCESS;
  if (with_date) {
    ObTime ot1;
    if (OB_ISNULL(expr_ctx.my_session_) || OB_ISNULL(expr_ctx.exec_ctx_)) {
      ret = OB_NOT_INIT;
      LOG_WARN("session or exec ctx is null", K(ret), K(expr_ctx.my_session_));
    } else if (OB_FAIL(ob_obj_to_ob_time_with_date(obj, get_timezone_info(expr_ctx.my_session_), ot1,
                    get_cur_time(expr_ctx.exec_ctx_->get_physical_plan_ctx()), is_dayofmonth))) {
      LOG_WARN("convert to obtime failed", K(ret));
    } else {
      ot = ot1;
    }
  } else {
    ObTime ot1(DT_TYPE_TIME);
    if (OB_FAIL(ob_obj_to_ob_time_without_date(obj, get_timezone_info(expr_ctx.my_session_), ot1))) {
      LOG_WARN("convert to obtime failed", K(ret));
    } else {
      ot = ot1;
    }
  }
  return ret;
}

int ObExprTimeBase::calc_result1(ObObj& result, const ObObj& obj, ObExprCtx& expr_ctx) const
{
  int ret = OB_SUCCESS;
  if (OB_UNLIKELY(obj.is_null())) {
    result.set_null();
  } else {
    ObTime ot;
    bool with_date = false;
    switch (dt_type_) {
      case DT_HOUR:
      case DT_MIN:
      case DT_SEC:
      case DT_USEC:
        with_date = false;
        break;
      case DT_YDAY:
      case DT_WDAY:
      case DT_MDAY:
      case DT_YEAR:
      case DT_MON:
        with_date = true;
        break;
      case DT_MON_NAME:
        with_date = true;
        break;
      default:
        LOG_WARN("ObExprTimeBase calc result1 switch default", K(dt_type_));
    }
    bool ignoreZero = (DT_MDAY == dt_type_ || DT_MON == dt_type_ || DT_MON_NAME == dt_type_);
    if (OB_FAIL(ob_expr_convert_to_dt_or_time(obj, expr_ctx, ot, with_date, ignoreZero))) {
      LOG_WARN("cast to ob time failed", K(ret), K(obj), K(expr_ctx.cast_mode_));
      if (CM_IS_WARN_ON_FAIL(expr_ctx.cast_mode_)) {
        LOG_WARN("cast to ob time failed", K(ret), K(expr_ctx.cast_mode_));
        LOG_USER_WARN(OB_ERR_CAST_VARCHAR_TO_TIME);
        ret = OB_SUCCESS;
        result.set_null();
      }
    } else {
      if (with_date && DT_MDAY != dt_type_ && ot.parts_[DT_DATE] + DAYS_FROM_ZERO_TO_BASE < 0) {
        result.set_null();
      } else if (DT_WDAY == dt_type_) {
        int32_t res = ot.parts_[DT_WDAY];
        res = res % 7 + 1;
        result.set_int32(res);
      } else if (DT_MON_NAME == dt_type_) {
        int32_t mon = ot.parts_[DT_MON];
        if (mon < 1 || mon > 12) {
          LOG_WARN("invalid month value", K(ret), K(mon));
          result.set_null();
        } else {
          const char* month_name = ObExprMonthName::get_month_name(ot.parts_[DT_MON]);
          result.set_string(common::ObVarcharType, month_name, strlen(month_name));
          result.set_collation_type(result_type_.get_collation_type());
          result.set_collation_level(result_type_.get_collation_level());
        }
      } else {
        result.set_int32(ot.parts_[dt_type_]);
      }
    }
  }
  return ret;
}

int ObExprTimeBase::cg_expr(ObExprCGCtx& op_cg_ctx, const ObRawExpr& raw_expr, ObExpr& rt_expr) const
{
  UNUSED(op_cg_ctx);
  UNUSED(raw_expr);
  int ret = OB_SUCCESS;
  if (rt_expr.arg_cnt_ != 1) {
    ret = OB_INVALID_ARGUMENT;
    LOG_WARN("hour/minute/second expr should have one param", K(ret), K(rt_expr.arg_cnt_));
  } else if (OB_ISNULL(rt_expr.args_) || OB_ISNULL(rt_expr.args_[0])) {
    ret = OB_ERR_UNEXPECTED;
    LOG_WARN("children of hour/minute/second expr is null", K(ret), K(rt_expr.args_));
  } else {
    switch (dt_type_) {
      case DT_HOUR:
        rt_expr.eval_func_ = ObExprHour::calc_hour;
        break;
      case DT_MIN:
        rt_expr.eval_func_ = ObExprMinute::calc_minute;
        break;
      case DT_SEC:
        rt_expr.eval_func_ = ObExprSecond::calc_second;
        break;
      case DT_USEC:
        rt_expr.eval_func_ = ObExprMicrosecond::calc_microsecond;
        break;
      case DT_MDAY:
        rt_expr.eval_func_ = ObExprDayOfMonth::calc_dayofmonth;
        break;
      case DT_WDAY:
        rt_expr.eval_func_ = ObExprDayOfWeek::calc_dayofweek;
        break;
      case DT_YDAY:
        rt_expr.eval_func_ = ObExprDayOfYear::calc_dayofyear;
        break;
      case DT_YEAR:
        rt_expr.eval_func_ = ObExprYear::calc_year;
        break;
      case DT_MON:
        rt_expr.eval_func_ = ObExprMonth::calc_month;
        break;
      case DT_MON_NAME:
        rt_expr.eval_func_ = ObExprMonthName::calc_month_name;
        break;
      default:
        LOG_WARN("ObExprTimeBase cg_expr switch default", K(dt_type_));
        break;
    }
  }
  return ret;
}

static int ob_expr_convert_to_time(
    const ObDatum& datum, ObObjType type, bool with_date, bool is_dayofmonth, ObEvalCtx& ctx, ObTime& ot)
{
  int ret = OB_SUCCESS;
  if (with_date) {
    ObTime ot2;
    if (OB_FAIL(ob_datum_to_ob_time_with_date(datum,
            type,
            get_timezone_info(ctx.exec_ctx_.get_my_session()),
            ot2,
            get_cur_time(ctx.exec_ctx_.get_physical_plan_ctx()),
            is_dayofmonth))) {
      LOG_WARN("cast to ob time failed", K(ret));
    } else {
      ot = ot2;
    }
  } else {
    ObTime ot2(DT_TYPE_TIME);
    if (OB_FAIL(
            ob_datum_to_ob_time_without_date(datum, type, get_timezone_info(ctx.exec_ctx_.get_my_session()), ot2))) {
      LOG_WARN("cast to ob time failed", K(ret));
    } else {
      ot = ot2;
    }
  }
  return ret;
}

int ObExprTimeBase::calc(const ObExpr& expr, ObEvalCtx& ctx, ObDatum& expr_datum, int32_t type, bool with_date,
    bool is_dayofmonth /* false */)
{
  int ret = OB_SUCCESS;
  ObDatum* param_datum = NULL;
  const ObSQLSessionInfo* session = NULL;
  if (OB_ISNULL(session = ctx.exec_ctx_.get_my_session())) {
    ret = OB_ERR_UNEXPECTED;
    LOG_WARN("session is null", K(ret));
  } else if (OB_FAIL(expr.args_[0]->eval(ctx, param_datum))) {
    LOG_WARN("eval param value failed");
  } else if (OB_UNLIKELY(param_datum->is_null())) {
    expr_datum.set_null();
  } else {
    ObTime ot;
    if (OB_FAIL(ob_expr_convert_to_time(
            *param_datum, expr.args_[0]->datum_meta_.type_, with_date, is_dayofmonth, ctx, ot))) {
      LOG_WARN("cast to ob time failed", K(ret), K(lbt()), K(session->get_stmt_type()));
      LOG_USER_WARN(OB_ERR_CAST_VARCHAR_TO_TIME);
      uint64_t cast_mode = 0;
      ObSQLUtils::get_default_cast_mode(session->get_stmt_type(), session, cast_mode);
      if (CM_IS_WARN_ON_FAIL(cast_mode)) {
        ret = OB_SUCCESS;
        expr_datum.set_null();
      }
    } else if (with_date && !is_dayofmonth && ot.parts_[DT_DATE] + DAYS_FROM_ZERO_TO_BASE < 0) {
      expr_datum.set_null();
    } else {
      expr_datum.set_int32(ot.parts_[type]);
    }
  }
  return ret;
}

ObExprHour::ObExprHour(ObIAllocator& alloc) : ObExprTimeBase(alloc, DT_HOUR, T_FUN_SYS_HOUR, N_HOUR){};

ObExprHour::~ObExprHour()
{}

int ObExprHour::calc_hour(const ObExpr& expr, ObEvalCtx& ctx, ObDatum& expr_datum)
{
  return ObExprTimeBase::calc(expr, ctx, expr_datum, DT_HOUR, false);
}

ObExprMinute::ObExprMinute(ObIAllocator& alloc) : ObExprTimeBase(alloc, DT_MIN, T_FUN_SYS_MINUTE, N_MINUTE){};

ObExprMinute::~ObExprMinute()
{}

int ObExprMinute::calc_minute(const ObExpr& expr, ObEvalCtx& ctx, ObDatum& expr_datum)
{
  return ObExprTimeBase::calc(expr, ctx, expr_datum, DT_MIN, false);
}

ObExprSecond::ObExprSecond(ObIAllocator& alloc) : ObExprTimeBase(alloc, DT_SEC, T_FUN_SYS_SECOND, N_SECOND){};

ObExprSecond::~ObExprSecond()
{}

int ObExprSecond::calc_second(const ObExpr& expr, ObEvalCtx& ctx, ObDatum& expr_datum)
{
  return ObExprTimeBase::calc(expr, ctx, expr_datum, DT_SEC, false);
}

ObExprMicrosecond::ObExprMicrosecond(ObIAllocator& alloc)
    : ObExprTimeBase(alloc, DT_USEC, T_FUN_SYS_MICROSECOND, N_MICROSECOND){};

ObExprMicrosecond::~ObExprMicrosecond()
{}

int ObExprMicrosecond::calc_microsecond(const ObExpr& expr, ObEvalCtx& ctx, ObDatum& expr_datum)
{
  return ObExprTimeBase::calc(expr, ctx, expr_datum, DT_USEC, false);
}

ObExprYear::ObExprYear(ObIAllocator& alloc) : ObExprTimeBase(alloc, DT_YEAR, T_FUN_SYS_YEAR, N_YEAR){};

ObExprYear::~ObExprYear()
{}

int ObExprYear::calc_year(const ObExpr& expr, ObEvalCtx& ctx, ObDatum& expr_datum)
{
  return ObExprTimeBase::calc(expr, ctx, expr_datum, DT_YEAR, true);
}

ObExprMonth::ObExprMonth(ObIAllocator& alloc) : ObExprTimeBase(alloc, DT_MON, T_FUN_SYS_MONTH, N_MONTH){};

ObExprMonth::~ObExprMonth()
{}

int ObExprMonth::calc_month(const ObExpr& expr, ObEvalCtx& ctx, ObDatum& expr_datum)
{
  return ObExprTimeBase::calc(expr, ctx, expr_datum, DT_MON, true, true);
}

ObExprMonthName::ObExprMonthName(ObIAllocator& alloc)
    : ObExprTimeBase(alloc, DT_MON_NAME, T_FUN_SYS_MONTH_NAME, N_MONTH_NAME){};

ObExprMonthName::~ObExprMonthName()
{}

const ObExprMonthName::ObSqlMonthNameMap ObExprMonthName::MONTH_NAME_MAP[] = {{1, "January"},
    {2, "February"},
    {3, "March"},
    {4, "April"},
    {5, "May"},
    {6, "June"},
    {7, "July"},
    {8, "August"},
    {9, "September"},
    {10, "October"},
    {11, "November"},
    {12, "December"}};

OB_INLINE const char* ObExprMonthName::get_month_name(int month)
{
  return MONTH_NAME_MAP[month - 1].str_val;
}

int ObExprMonthName::calc_month_name(const ObExpr& expr, ObEvalCtx& ctx, ObDatum& expr_datum)
{
  int ret = OB_SUCCESS;
  // NOTE: the last param should be true otherwise '2020-09-00' will not work
  if (OB_FAIL(calc(expr, ctx, expr_datum, DT_MON, true, true))) {
    LOG_WARN("eval month in monthname failed", K(ret), K(expr));
  } else if (!expr_datum.is_null()) {
    int32_t mon = expr_datum.get_int32();
    if (mon < 1 || mon > 12) {
      LOG_WARN("invalid month value", K(ret), K(expr));
      expr_datum.set_null();
    } else {
      const char* month_name = get_month_name(mon);
      expr_datum.set_string(month_name, strlen(month_name));
    }
  }

  return ret;
}

int ObExprMonthName::calc_result_type1(ObExprResType& type, ObExprResType& type1, common::ObExprTypeCtx& type_ctx) const
{
  ObCollationType cs_type = type_ctx.get_coll_type();
  type.set_varchar();
  type.set_collation_type(cs_type);
  type.set_collation_level(CS_LEVEL_IMPLICIT);

  common::ObObjTypeClass tc1 = ob_obj_type_class(type1.get_type());
  if (ob_is_enumset_tc(type1.get_type())) {
    type1.set_calc_type(common::ObVarcharType);
  } else if ((common::ObFloatTC == tc1) || (common::ObDoubleTC == tc1)) {
    type1.set_calc_type(common::ObIntType);
  }

  return OB_SUCCESS;
}

}  // namespace sql
}  // namespace oceanbase
