/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed 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.
 */

declare function print(arg:any):string;
declare interface ArkTools {
    isAOTCompiled(args: any): boolean;
}
function replace(a : number, b : number)
{
    return a;
}

function doImul(x: any, y: any): number {
    return Math.imul(x, y);
}

function printImul(x: any, y: any) {
    try {
        print(doImul(x, y));
    } finally {
    }
}

let len:number = 1;

len = Math.imul(2, 3)
//aot: [trace] aot inline builtin: Math.imul, caller function name:func_main_0@builtinMathImul
print(len) //: 6

// Check without params
len = Math.imul();
//aot: [trace] aot inline builtin: Math.imul, caller function name:func_main_0@builtinMathImul
print(len); //: 0

// Check with single param
len = Math.imul(0);
//aot: [trace] aot inline builtin: Math.imul, caller function name:func_main_0@builtinMathImul
print(len); //: 0

// Check with three param
len = Math.imul(2, 4, 6);
//aot: [trace] aot inline builtin: Math.imul, caller function name:func_main_0@builtinMathImul
print(len); //: 8

// If an input is NaN, return 0
len = Math.imul(2, NaN)
//aot: [trace] aot inline builtin: Math.imul, caller function name:func_main_0@builtinMathImul
print(len) //: 0

// Check with 0
len = Math.imul(3, +0.0);
//aot: [trace] aot inline builtin: Math.imul, caller function name:func_main_0@builtinMathImul
print(len); //: 0

len = Math.imul(3, -0.0);
//aot: [trace] aot inline builtin: Math.imul, caller function name:func_main_0@builtinMathImul
print(len); //: 0

// If an input is Infinity or -Infinity, return 0
len = Math.imul(-Infinity, 5);
//aot: [trace] aot inline builtin: Math.imul, caller function name:func_main_0@builtinMathImul
print(len); //: 0
len = Math.imul(Infinity, 6);
//aot: [trace] aot inline builtin: Math.imul, caller function name:func_main_0@builtinMathImul
print(len); //: 0
len = Math.imul(3, -Infinity);
//aot: [trace] aot inline builtin: Math.imul, caller function name:func_main_0@builtinMathImul
print(len); //: 0
len = Math.imul(5, Infinity);
//aot: [trace] aot inline builtin: Math.imul, caller function name:func_main_0@builtinMathImul
print(len); //: 0

// Check int
len = Math.imul(-2, -10);
//aot: [trace] aot inline builtin: Math.imul, caller function name:func_main_0@builtinMathImul
print(len); //: 20
len = Math.imul(5, 10000);
//aot: [trace] aot inline builtin: Math.imul, caller function name:func_main_0@builtinMathImul
print(len); //: 50000
len = Math.imul(-600, -20);
//aot: [trace] aot inline builtin: Math.imul, caller function name:func_main_0@builtinMathImul
print(len); //: 12000

// Check double
len = Math.imul(5.3, 2.7);
//aot: [trace] aot inline builtin: Math.imul, caller function name:func_main_0@builtinMathImul
print(len); //: 10
len = Math.imul(-2.4, -4.7);
//aot: [trace] aot inline builtin: Math.imul, caller function name:func_main_0@builtinMathImul
print(len); //: 8

len = Math.imul(-7.3, 6.2);
//aot: [trace] aot inline builtin: Math.imul, caller function name:func_main_0@builtinMathImul
print(len); //: -42

//big double
len = Math.imul(2, 1.9e80);
//aot: [trace] aot inline builtin: Math.imul, caller function name:func_main_0@builtinMathImul
print(len); //: 0

//small double
len = Math.imul(2, 1.9e-80);
//aot: [trace] aot inline builtin: Math.imul, caller function name:func_main_0@builtinMathImul
print(len); //: 0


// Check edge cases
const INT_MAX: number = 2147483647;
const INT_MIN: number = -INT_MAX - 1;
//aot: [trace] aot inline builtin: Math.imul, caller function name:func_main_0@builtinMathImul
print(Math.imul(INT_MAX, 1)); //: 2147483647
//aot: [trace] aot inline builtin: Math.imul, caller function name:func_main_0@builtinMathImul
print(Math.imul(2147483648 , 2)); //: 0
//aot: [trace] aot inline builtin: Math.imul, caller function name:func_main_0@builtinMathImul
print(Math.imul(-INT_MAX, 2)); //: 2
//aot: [trace] aot inline builtin: Math.imul, caller function name:func_main_0@builtinMathImul
print(Math.imul(INT_MIN, 2)); //: 0
//aot: [trace] aot inline builtin: Math.imul, caller function name:func_main_0@builtinMathImul
print(Math.imul(INT_MIN - 1, 2)); //: -2

len = Math.imul(2, "three");
//aot: [trace] aot inline builtin: Math.imul, caller function name:func_main_0@builtinMathImul
//aot: [trace] Check Type: NotNumber1
print(len); //: 0

// Replace standard builtin
let true_imul = Math.imul
Math.imul= replace
print(Math.imul(2, 40)); //: 2
Math.imul = true_imul

len = Math.imul(3, 3)
print(len) //: 9
//aot: [trace] aot inline builtin: Math.imul, caller function name:#*#doImul@builtinMathImul
printImul(-12, 2); //: -24
// Call standard builtin with non-number param
//aot: [trace] aot inline builtin: Math.imul, caller function name:#*#doImul@builtinMathImul
//aot: [trace] Check Type: NotNumber1
printImul("abc", 2); //: 0
//aot: [trace] aot inline builtin: Math.imul, caller function name:#*#doImul@builtinMathImul
//aot: [trace] Check Type: NotNumber1
printImul("-12", 2); //: -24

if (ArkTools.isAOTCompiled(printImul)) {
    // Replace standard builtin after call to standard builtin was profiled
    Math.imul= replace
}
printImul(-12, 2); //pgo: -24
//aot: [trace] Check Type: NotCallTarget1
//aot: -12
printImul("abc", 2); //pgo: 0
//aot: [trace] Check Type: NotCallTarget1
//aot: abc

Math.imul = true_imul

// Check IR correctness inside try-block
try {
    //aot: [trace] aot inline builtin: Math.imul, caller function name:#*#doImul@builtinMathImul
    printImul(-12, 2); //: -24
    //aot: [trace] aot inline builtin: Math.imul, caller function name:#*#doImul@builtinMathImul
    //aot: [trace] Check Type: NotNumber1
    printImul("abc", 2); //: 0
} catch (e) {
}

let obj = {};
obj.valueOf = (() => { return -23; })
//aot: [trace] aot inline builtin: Math.imul, caller function name:#*#doImul@builtinMathImul
//aot: [trace] Check Type: NotNumber1
printImul(obj, -2); //: 46

function Throwing() {
    this.value = -14;
}
Throwing.prototype.valueOf = function() {
    if (this.value > 0) {
        throw new Error("already positive");
    }
    return this.value;
}
let throwingObj = new Throwing();

try {
    print(Math.imul(throwingObj, 2)); //: -28
    throwingObj.value = 10;
    print(Math.imul(throwingObj), 2); //: Error: already positive
} catch(e) {
    print(e);
} finally {
    print(Math.imul(obj, 2)); //: -46
}