//
//  SourceTargetCodeGen.cpp
//  MNNCodegen
//
//  Created by MNN on 2020/11/27.
//

#include "cpu/CPUAst.hpp"
#include <sstream>

using namespace AST;
std::string PrototypeAST::codegen(SourceTarget *target) {
    std::stringstream ss;
    ss << target->getIndent();
    ss << "void " << Name << "(";
    ss << "float** inputs, float** outputs";
    ss << ")\n";
    return ss.str();
}

std::string FunctionAST::codegen(SourceTarget* target) {
    std::stringstream ss;
    ss << Proto->codegen(target) << "{\n";
    target->addIndent();
    ss << Body->codegen(target);
    target->subIndent();
    ss << "}\n";
    return ss.str();
}

std::string ListExprAST::codegen(SourceTarget* target) {
    std::stringstream ss;
    for (auto& expr : exprs) {
        ss << expr->codegen(target);
    }
    return ss.str();
}

std::string VarExprAST::codegen(SourceTarget* target) {
}

std::string ForExprAST::codegen(SourceTarget* target) {
    std::stringstream ss;
    ss << target->getIndent() << "for (int ";
    ss << VarName << " = " << Start->codegen(target) << "; ";
    ss << VarName << " < " << End->codegen(target) << "; ";
    ss << VarName << " += " << Step->codegen(target) << ") {\n";
    target->addIndent();
    ss << Body->codegen(target);
    target->subIndent();
    ss << target->getIndent() << "}\n";
    return ss.str();
}

std::string IfExprAST::codegen(SourceTarget* target) {
}

std::string CallExprAST::codegen(SourceTarget* target) {
}

std::string AssignExprAST::codegen(SourceTarget* target) {
    std::stringstream ss;
    ss << target->getIndent() << LHS->codegen(target) << " = " << RHS->codegen(target) << ";\n";
    return ss.str();
}

std::string BinaryExprAST::codegen(SourceTarget *target) {
    std::stringstream ss;
    auto l = LHS->codegen(target);
    auto r = RHS->codegen(target);
    switch (Op) {
        case MNN::BinaryOpOperation_ADD:
            ss << "(" << l << " + " << r << ")";
            break;
        case MNN::BinaryOpOperation_SUB:
            ss << "(" << l << " - " << r << ")";
            break;
        case MNN::BinaryOpOperation_MUL:
            ss << "(" << l << " * " << r << ")";
            break;
        case MNN::BinaryOpOperation_DIV:
        case MNN::BinaryOpOperation_REALDIV:
            ss << "(" << l << " / " << r << ")";
            break;
        case MNN::BinaryOpOperation_FLOORDIV:
            ss << "floor(" << l << " / " << r << ")";
            break;
        case MNN::BinaryOpOperation_POW:
            ss << "pow(" << l << ", " << r << ")";
            break;
        case MNN::BinaryOpOperation_MINIMUM:
            ss << "fmin(" << l << ", " << r << ")";
            break;
        case MNN::BinaryOpOperation_MAXIMUM:
            ss << "fmax(" << l << ", " << r << ")";
            break;
        case MNN::BinaryOpOperation_GREATER:
            ss << "(" << l << " > " << r << ")";
            break;
        case MNN::BinaryOpOperation_GREATER_EQUAL:
            ss << "(" << l << " >= " << r << ")";
            break;
        case MNN::BinaryOpOperation_LESS:
            ss << "(" << l << " < " << r << ")";
            break;
        case MNN::BinaryOpOperation_LESS_EQUAL:
            ss << "(" << l << " <= " << r << ")";
            break;
        case MNN::BinaryOpOperation_EQUAL:
            ss << "(" << l << " == " << r << ")";
            break;
        default:
            MNN_ASSERT(false);
    }
    return ss.str();
}

std::string ReluExprAST::codegen(SourceTarget *target) {
    std::stringstream ss;
    auto x = Operand->codegen(target);
    if (maxVal == 0.f) {
        // slope = minVal
        // relu(x) = ((x < 0) * slope * x + (x >= 0) * x)
        ss << "((" << x << " < 0 ) * " << minVal << " * " << x << " + (" << x << " >= 0 ) * " << x << ")";
    } else {
        // relu6(x) = min(max(x, minv), maxv)
        ss << "fmin(fmax(" << x << ", " << minVal << "), " << maxVal << ")";
    }
    return ss.str();
}

std::string UnaryExprAST::codegen(SourceTarget *target) {
    std::stringstream ss;
    auto x = Operand->codegen(target);
    switch (Op) {
        case MNN::UnaryOpOperation_ABS:
            ss << "abs(" << x << ")";
            break;
        case MNN::UnaryOpOperation_FLOOR:
            ss << "floor(" << x << ")";
            break;
        case MNN::UnaryOpOperation_CEIL:
            ss << "ceil(" << x << ")";
            break;
        case MNN::UnaryOpOperation_SQRT:
            ss << "sqrt(" << x << ")";
            break;
        case MNN::UnaryOpOperation_EXP:
            ss << "exp(" << x << ")";
            break;
        case MNN::UnaryOpOperation_LOG:
            ss << "log(" << x << ")";
            break;
        case MNN::UnaryOpOperation_SIN:
            ss << "sin(" << x << ")";
            break;
        case MNN::UnaryOpOperation_COS:
            ss << "cos(" << x << ")";
            break;
        case MNN::UnaryOpOperation_ROUND:
            ss << "round(" << x << ")";
            break;
        case MNN::UnaryOpOperation_NEG:
            ss << "(-" << x << ")";
            break;
        case MNN::UnaryOpOperation_SQUARE:
            ss << "(" << x << " * " << x << ")";
            break;
        case MNN::UnaryOpOperation_RSQRT:
            ss << "(1.f / sqrt(" << x << "))";
            break;
        case MNN::UnaryOpOperation_RECIPROCAL:
            ss << "(1.f / " << x << ")";
            break;
        case MNN::UnaryOpOperation_SIGMOID:
            ss << "(1.f / (1.f + exp(-" << x << ")))";
            break;
        case MNN::UnaryOpOperation_TANH:
            ss << "tanh(" << x << ")";
            break;
        default:
            MNN_ASSERT(false);
    }
    return ss.str();
}

std::string SubscriptExprAST::codegen(SourceTarget *target) {
    std::stringstream ss;
    ss << Base->codegen(target) << "[" << Offset->codegen(target) << "]";
    return ss.str();
}

std::string VariableExprAST::codegen(SourceTarget *target) {
    std::stringstream ss;
    ss << Name;
    return ss.str();
}

std::string NumberExprAST::codegen(SourceTarget *target) {
    std::stringstream ss;
    switch (mType) {
        case FP32:
            ss << mVal.f32Val;
            break;
        case FP64:
            ss << mVal.f64Val;
            break;
        case INT32:
            ss << mVal.i32Val;
            break;
        case INT64:
            ss << mVal.i64Val;
            break;
        default:
            return nullptr;
    }
    return ss.str();
}
