/*
 * Copyright 2017 JetBrains s.r.o.
 *
 * 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.
 */

package kotlinx.atomicfu.transformer

import org.objectweb.asm.Opcodes.*
import org.objectweb.asm.tree.*
import org.objectweb.asm.util.*

val AbstractInsnNode.line: Int? get() {
    var cur = this
    while (true) {
        if (cur is LineNumberNode) return cur.line
        cur = cur.previous ?: return null
    }
}

fun AbstractInsnNode.atIndex(insnList: InsnList?): String {
    var cur = insnList?.first
    var index = 1
    while (cur != null && cur != this) {
        if (!cur.isUseless()) index++
        cur = cur.next
    }
    if (cur == null) return ""
    return "inst #$index: "
}

val AbstractInsnNode.nextUseful: AbstractInsnNode? get() {
    var cur: AbstractInsnNode? = next
    while (cur.isUseless()) cur = cur!!.next
    return cur
}

val AbstractInsnNode?.thisOrPrevUseful: AbstractInsnNode? get() {
    var cur: AbstractInsnNode? = this
    while (cur.isUseless()) cur = cur!!.previous
    return cur
}

private fun AbstractInsnNode?.isUseless() = this is LabelNode || this is LineNumberNode || this is FrameNode

fun InsnList.listUseful(limit: Int = Int.MAX_VALUE) : List<AbstractInsnNode> {
    val result = ArrayList<AbstractInsnNode>(limit)
    var cur = first
    while (cur != null && result.size < limit) {
        if (!cur.isUseless()) result.add(cur)
        cur = cur.next
    }
    return result
}

fun AbstractInsnNode.isAload(index: Int) =
    this is VarInsnNode && this.opcode == ALOAD && this.`var` == index

fun AbstractInsnNode.isGetField(owner: String) =
    this is FieldInsnNode && this.opcode == GETFIELD && this.owner == owner

fun AbstractInsnNode.isAreturn() =
    this.opcode == ARETURN

fun AbstractInsnNode.isReturn() =
    this.opcode == RETURN

@Suppress("UNCHECKED_CAST")
fun MethodNode.localVar(v: Int): LocalVariableNode? =
    (localVariables as List<LocalVariableNode>).firstOrNull { it.index == v }

inline fun forVarLoads(v: Int, start: LabelNode, end: LabelNode, block: (VarInsnNode) -> AbstractInsnNode?) {
    var cur: AbstractInsnNode? = start
    while (cur != null && cur !== end) {
        if (cur is VarInsnNode && cur.opcode == ALOAD && cur.`var` == v) {
            cur = block(cur)
        } else
            cur = cur.next
    }
}

fun nextVarLoad(v: Int, start: AbstractInsnNode): VarInsnNode {
    var cur: AbstractInsnNode? = start
    while (cur != null) {
        when (cur.opcode) {
            GOTO, TABLESWITCH, LOOKUPSWITCH, ATHROW, IFEQ, IFNE, IFLT, IFGE, IFGT, IFLE, IFNULL, IFNONNULL,
            IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, IF_ACMPEQ, IF_ACMPNE,
            IRETURN, FRETURN, ARETURN, RETURN, LRETURN, DRETURN -> {
                abort("Unsupported branching/control while searching for load of spilled variable #$v", cur)
            }
            ALOAD -> {
                if ((cur as VarInsnNode).`var` == v) return cur
            }
        }
        cur = cur.next
    }
    abort("Flow control falls after the end of the method while searching for load of spilled variable #$v")
}

fun accessToInvokeOpcode(access: Int) =
    if (access and ACC_STATIC != 0) INVOKESTATIC else INVOKEVIRTUAL

fun AbstractInsnNode.toText(): String {
    val printer = Textifier()
    accept(TraceMethodVisitor(printer))
    return (printer.getText()[0] as String).trim()
}

val String.ownerPackageName get() = substringBeforeLast('/')
