/*
   Copyright 2010 Aaron J. Radke

   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.
*/
// vim: set ts=3 sw=3 et:

package cc.drx

/**Get information about ELF binary files*/
case class ELF(val file:File){

  private var isValid = !false

  // private val bb = file.in.toByteBuffer  //bb by inputstream
  private val bb = Input(file.in.byteIt.take(2.k).toArray).toByteBuffer

  //--search/skip to a PE32, PE32+ header
  bb.asLittle;  //TODO is the little needed?
  while(bb.getInt != 0x00004550){} //PE00  PE32 or PE32+ magic number header start

  //--get helpers
  def getVersion16  = Version(bb.getU8.toInt,  bb.getU8.toInt) //get major then minor
  def getVersion32  = Version(bb.getU16.toInt, bb.getU16.toInt) //get major then minor

  //---std header
  val machine         = bb.getU16
  val sections        = bb.getU16
  val dateSec         = bb.getU32
  val date            = Date.fromSec(dateSec.toLong)
  val symbolTable     = bb.getU32 //size ???
  val symbols         = bb.getU32 //size ???
  val optSize         = bb.getU16 //size ??
  val characteristics = bb.getU16
  //---opt header
  val magicNum     = bb.getU16 //PE32 => 0x010B
  //PE32 => 0x010 otherwise switch to PE64 format TODO ???
  val isPE32:Boolean = magicNum == U16(0x010B)
  val isPE64:Boolean = magicNum == U16(0x020B)
  val isMagicValid   = isPE32 || isPE64
  isValid &&= isMagicValid //running collection of all validity checks

  def getPointer:Long = if(isPE32) bb.getU32.toLong else bb.getU64.toLong

  val linkerVer    = getVersion16
  val codeSize     = bb.getU32  //---
  val dataSize     = bb.getU32
  val dataBSize    = bb.getU32
  val entryPoint   = bb.getU32
  val codeBase     = bb.getU32
  val dataBase     = bb.getU32
  val imgBase      = getPointer
  val secAlign     = bb.getU32
  val fileAlign    = bb.getU32

  val osVer        = getVersion32
  val imgVer       = getVersion32
  val subVer       = getVersion32
  val win32Ver     = getVersion32

  val imgSize      = bb.getU32
  val headerSize   = bb.getU32
  val checksum     = bb.getU32
  val subsystem    = bb.getU16
  val dllChars     = bb.getU16
  //.... 

  //--derived values
  val machineName = machine.toInt match {
    case 0x8664 => "AMD64" //x64
    case 0x014c => "I386"  //x64
    case 0x0200 => "IA64"
    case 0xaa64 => "ARM64"  //most common at the top
    case 0x01c0 => "ARM"
    case 0x01d3 => "AM33"
    case 0x01c4 => "ARMV"
    case 0x0ebc => "EBC"
    case 0x9041 => "M32R"
    case 0x0266 => "MIPS16"
    case 0x0366 => "MIPSFPU"
    case 0x0466 => "MIPSFPU16"
    case 0x01f0 => "POWERPC"
    case 0x01f1 => "POWERPCFP"
    case 0x01c2 => "THUMB"
    case 0x0169 => "WCEMIPSV2"
    case _      => "unknown"
  }

  val linkerName:String = {
    val major:Int = Try{linkerVer.take(1).toString.toInt}.getOrElse(0)
  // -- https://en.wikipedia.org/wiki/Microsoft_Visual_Studio#History
    val nameOf = Map[Int,String](
       2 -> "gcc", //common for gnu projects like git & ffmpeg
       5 -> "Visual Studio 97 (1997)",
       6 -> "Visual Studio 6.0 (1998)",
       7 -> "Visual Studio .NET (2002)",
       8 -> "Visual Studio 2005",
       9 -> "Visual Studio 2008",
      10 -> "Visual Studio 2010",
      11 -> "Visual Studio 2012",
      12 -> "Visual Studio 2013",
      14 -> "Visual Studio 2015",
      15 -> "Visual Studio 2017",
      16 -> "Visual Studio 2019",
      48 -> "??" //a common value for amd64 ...???
    )
    val vStr = "v" + linkerVer.toString
    if(!isValid) f"??? (magic 0x${magicNum.toInt}%04X)" else if(nameOf.contains(major) ) s"${nameOf(major)} ($vStr)" else vStr
  }

  // val magicPos = bb.position() - 8 - 1
  override def toString = s"ELF date:${date.iso} linker:$linkerName file:$file"

  def niceRow = f" ${date.isoDay}  $machineName%-8s  $linkerVer%-8s  $linkerName%-35s  $file"
}