package parser

import java.io.File

import dex._
import org.jf.dexlib2.dexbacked.{
  DexBackedClassDef,
  DexBackedDexFile,
  DexBackedField,
  DexBackedMethod
}
import org.jf.dexlib2.{DexFileFactory, Opcodes}
import utils.Utils

import scala.collection.JavaConverters._

class Parser(apkLocation: String) {

  val apkFile = new File(apkLocation)
  val dexFile: DexBackedDexFile =
    DexFileFactory.loadDexFile(apkFile, Opcodes.forApi(21))

  def dexApp: DexApp = {
    val classes = parseClasses
    new DexApp(
      new AndroidXmlParser(apkLocation, classes).parseManifest(),
      classes,
      new EntryPointsProvider(classes).entryPoints(),
      new SourcesSinksProvider().sourcesSinks()
    )
  }

  private def parseClasses: Seq[DexClass] = {
    dexFile.getClasses.asScala.toSeq
      .filter(classFilter())
      .map(
        c =>
          DexClass(
            c.getType,
            c.getSuperclass,
            c.getInterfaces.asScala,
            classFields(c.getFields.asScala.toSeq),
            classMethods(c.getMethods.asScala.toSeq)
        ))
  }

  private def classFilter(): DexBackedClassDef => Boolean =
    (t: DexBackedClassDef) => !t.getType.startsWith("Landroid/support")

  private def methodFilter(): DexBackedMethod => Boolean =
    (m: DexBackedMethod) => m.getImplementation != null

  private def classFields(fields: Seq[DexBackedField]): Seq[DexField] =
    fields
      .map(
        f =>
          DexField(
            f.getName,
            Utils.removeClassPrefix(f.toString),
            DexType(f.getType)
        ))

  private def classMethods(methods: Seq[DexBackedMethod]): Seq[DexMethod] = {
    methods
      .filter(methodFilter())
      .map(
        m =>
          DexMethod(
            m.getName,
            Utils.removeClassPrefix(m.toString),
            m.getParameterTypes.asScala.map(t => DexType(t)),
            DexType(m.getReturnType),
            m.getImplementation.getRegisterCount,
            m.getParameters.size(),
            m.getImplementation.getInstructions.asScala.toSeq
        ))
  }

}
