import java.io.{BufferedWriter,ByteArrayInputStream,File,FileWriter,InputStreamReader,IOException,RandomAccessFile}
import java.util.{ArrayList}
import javax.xml.parsers.{ParserConfigurationException,SAXParser,SAXParserFactory}
import org.xml.sax.{Attributes,InputSource,SAXException,XMLReader}
import org.xml.sax.helpers.{DefaultHandler}
import collection.JavaConversions._

object FileObjectFiwalk
{
  val defaultFilePath = "sampleXML/schardtFiwalkOriginal.xml"

  def usage ()
  {
    System.err.println ("Usage: scala FileObjectFiwalk [filename]")
    System.exit (1)
  }

  def main (args:Array[String]) 
  {
    val mftEntries:ArrayList[Byte] = new ArrayList[Byte]
    val bitMap:ArrayList[Byte] = new ArrayList[Byte]

    val filePath:String = args.length match {
      case 0 => defaultFilePath
      case 1 => args (0)
      case _ => usage; "never used"
    }

    val outputFile:FileWriter = new FileWriter ("warnings.txt")
    val imageFile = new RandomAccessFile("../XML/SCHARDT.RAW", "r")

    try {
      val spf:SAXParserFactory = SAXParserFactory.newInstance
      val sp:SAXParser = spf.newSAXParser
      val xmlReader:XMLReader = sp.getXMLReader
      val handler:FileObjectFiwalk = new FileObjectFiwalk (imageFile, mftEntries, bitMap, outputFile)
      xmlReader.setContentHandler (handler)
      val url:String = new File (filePath).getAbsoluteFile.toURI.toURL.toString
      val inputSource:InputSource = new InputSource (url)
      xmlReader.parse (inputSource)
      
      val verifier = new Verifier (imageFile, mftEntries, bitMap, outputFile)
      for (fo <- handler.fos) {
	  println
          println (fo)
          verifier.verifyFO (fo)
      }

    } catch {
      case e:IOException => e.printStackTrace
      case e:ParserConfigurationException => e.printStackTrace
      case e:SAXException => e.printStackTrace
    } finally {
      imageFile.close
      outputFile.close
    }
  }
}

case class ByteRun (fileOffset:Option[Long], fsOffset:Option[Long], imgOffset:Option[Long], fill:Option[Long], len:Option[Long], typeAttr:Option[String])

case class FO (
  partitionOffset:Option[Long],
  imageOffset:Option[Long],
  parentInode:Long, 
  filename:String,
  nameType:String,
  fileSize:Option[Long],
  alloc:Option[Long],
  used:Option[Long],
  compressed:Long,
  inode:Option[Long],
  nlink:Option[Long],
  seq:Option[Long],
  byteRuns:List[ByteRun]
)

class FileObjectFiwalk (imageFile:RandomAccessFile, mftEntries:ArrayList[Byte], bitMap:ArrayList[Byte], outputFile:FileWriter) extends DefaultHandler 
{
  private val fos = new ArrayList[FO]

  private var partitionOffset:Option[Long] = None 
  private var partOffset:Option[Long] = None
  private var imageOffset:Option[Long] = None
  private var parentInode:Option[Long] = None
  private var filename:Option[String] = None
  private var nameType:Option[String] = None
  private var fileSize:Option[Long] = None
  private var alloc:Option[Long] = None
  private var used:Option[Long] = None
  private var compressed:Option[Long] = None
  private var inode:Option[Long] = None
  private var nlink:Option[Long] = None
  private var seq:Option[Long] = None
  private var byteRuns:Option[List[ByteRun]] = None

  def clearVariables () 
  {
    partOffset = None
    imageOffset = None
    parentInode = None      
    filename = None       
    nameType = None       
    fileSize = None         
    alloc = None            
    used = None             
    compressed = None
    inode = None            
    nlink = None            
    seq = None
    byteRuns = None
  }

  private var sb:StringBuilder = new StringBuilder 

  private var partitionOffsetFound:Boolean = false
  private var insideParentObject:Boolean = false

  override def startElement (namespaceURI:String, localName:String, qName:String, atts:Attributes) 
  {
    sb = new StringBuilder
    qName match {
      case "partition_offset" => partitionOffsetFound = true
      case "fileobject" =>  clearVariables
      case "parent_object" => insideParentObject = true

      case "byte_run" =>
        val byteRun = ByteRun (
	  atts.getValue ("file_offset") match {
	    case null => None
	    case y => Some(y.toLong) 	      
	  }, 
     	  atts.getValue ("fs_offset") match {
	    case null => None
	    case y => Some(y.toLong) 
	  }, 
      	  atts.getValue ("img_offset") match {
	    case null => None
	    case y => Some(y.toLong) 	      
	  }, 
	  atts.getValue ("fill") match {
	    case null => None
	    case y => Some(y.toLong)
	  },
	  atts.getValue ("len") match {
	    case null => 
	      atts.getValue ("uncompressed_len") match {
		case null => None
		case y => Some(y.toLong)
	      }
	    case y => Some(y.toLong)
	  },
	  atts.getValue ("type") match {
	    case null => None
	    case y => Some(y) 
	  }
	)
        byteRuns = Some (byteRun :: (byteRuns match { 
	  case None => Nil
	  case Some (existing) => existing
        }))
      case _ => ()
    }
  }
  
  override def endElement (namespaceURI:String, localName:String, qName:String) 
  {
    val value:String = sb.toString

    qName match {
      case "partition_offset" =>
        partitionOffset = Some (value.toLong)
      case "part_offset" =>
        partOffset = Some (value.toLong)
      case "fs_offset" =>
	imageOffset = Some (value.toLong)
      case "parent_object" => 
        insideParentObject = false
      case "filename" => 
        filename = Some (value)
      case "name_type" => 
        nameType = Some (value)
      case "filesize" => 
        fileSize = Some (value.toLong)
      case "alloc" => 
	alloc = value match {
	  case "-" => None
	  case _ => Some(value.toLong)
	}
      case "unalloc" => 
      	alloc = Some (0)
      case "used" => 
      	used = Some (value.toLong)	
      case "compressed" =>
	compressed = Some (value.toLong)
      case "inode" => 
        if (insideParentObject) {
	  parentInode = Some (value.toLong)
        } else {
	  inode = value match {
	    case "-" => None
	    case _ => Some (value.toLong)
	  }
        }
      case "nlink" => 
      	nlink = Some (value.toLong)
      case "seq" => 
      	seq = Some (value.toLong)
      case "byte_runs" => 
	byteRuns = byteRuns match {
	  case None => None
	  case Some (existing) => Some (existing.reverse)
	}
	if (inode == Some(0)) {
	  for (byteRun <- byteRuns) {
	    println ("filling MFT entries")
	    for (b <- byteRun) {
	      println ("within b: " + b)
	      imageFile.seek (b.imgOffset.get)
	      for (i <- b.fileOffset.get until (b.fileOffset.get + b.len.get)) {
		mftEntries.add(imageFile.readByte ())
	      }
	    }
	  }
	  createBitMap ()
	}
      case "fileobject" => 
	println (filename.get)
	if (inode == None && imageOffset == None) {
	  outputFile.write ("No Inode Or Image Offset " + filename.get + "\n")
	} else if (filename.get == "$OrphanFiles") {
	  println ("$OrphanFiles found")
	} else {
	  fos.add (
	    FO (
              partitionOffsetFound match {
                case false => partOffset
                case true => partitionOffset  
              },
	      imageOffset,
 	      parentInode.get,
 	      filename.get,
	      nameType.get,
	      fileSize,
	      alloc,
	      used,
	      compressed match {
		case None => 0
		case Some (existing) => compressed.get
	      },
	      inode,
	      nlink,
	      seq,
	      byteRuns match {
		case None => Nil
		case Some(existing) => byteRuns.get
	      }
	    )
	  )
	}
      case _ => ()
    }
  }
  
  override def characters (ch:Array[Char], start:Int, length:Int) 
  {
    sb.append (new String (ch, start, length))
  }
  
  def createBitMap () {
    var mftEntry:Array[Byte] = new Array[Byte](1024)
    for (i <- 0 until 1024) {
      mftEntry(i) = mftEntries(i)
    }

    val fixupOffset:Long = readBytes (4, 2, mftEntry)
    val numFixupArray:Long = readBytes (6, 2, mftEntry)
    var fixupArray:Array[Long] = new Array[Long](numFixupArray.toInt)
    for (i <- 0 until numFixupArray.toInt) {
      fixupArray(i) = readBytes (fixupOffset.toInt + (i*2), 2, mftEntry)
    }
    var signatureSlot:Long = 0
    for (i <- 0 until numFixupArray.toInt) {
      if ((i*512+510) < 1024) {
        signatureSlot = readBytes (i*512+510, 2, mftEntry)
        mftEntry(i*512+510) = fixupArray(i+1).toByte
        mftEntry(i*512+511) = (fixupArray(i+1) >> 8).toByte
      }
    }

    var offsetToAttribute:Int = readBytes (20, 2, mftEntry).toInt
    var attributeFound:Long = 0
    while (attributeFound != 0xFFFFFFFF) {
      attributeFound = readBytes (offsetToAttribute.toInt, 4, mftEntry)
      var lengthOfAttribute:Int = readBytes (offsetToAttribute+4, 4, mftEntry).toInt
      if (attributeFound == 0xB0) {
	if (readBytes (offsetToAttribute+8, 1, mftEntry) == 1) {
	  val offsetToRunList:Long = readBytes (offsetToAttribute+32, 2, mftEntry)
	  val attributeContentLength:Long = readBytes (offsetToAttribute+48, 8, mftEntry)
	  var contentLeft:Long = attributeContentLength

	  var offsetWithinRunlist:Int = offsetToRunList.toInt + offsetToAttribute
	  var overallOffset:Long = 0
	  
	  while (contentLeft > 0) {
	    var runOffset:Long = (readBytes (offsetWithinRunlist, 1, mftEntry) >> 4 & 0xF)
	    var runLength:Long = (readBytes (offsetWithinRunlist, 2, mftEntry) & 0xF)

	    var runLengthField:Long = readBytes (offsetWithinRunlist + 1, runLength.toInt, mftEntry)
	    var runOffsetField:Long = readBytes (offsetWithinRunlist + 1 + runLength.toInt, runOffset.toInt, mftEntry)

  	    runOffsetField = (runOffsetField << (8 * (8 - runOffset))) >> (8 * (8 - runOffset))
    	    overallOffset = overallOffset + runOffsetField
	    
	    imageFile.seek (overallOffset*512+63*512)
	    for (i <- 0 until runLengthField.toInt*512) {
	      if (bitMap.length < attributeContentLength) {
		bitMap.add (imageFile.readByte())
	      }
	    }

	    contentLeft = contentLeft - (runLengthField * 512)
	    offsetWithinRunlist = offsetWithinRunlist + (runOffset.toInt + runLength.toInt + 1)
	  }
	}
      } 
      offsetToAttribute = offsetToAttribute + lengthOfAttribute
    }
  }

  def readBytes (offset:Int, quantity:Int, mftEntry:Array[Byte]) : Long = {
    var bytes:Long = 0x00
    var i = 0
    for(i <- 0 until quantity) {
      bytes = bytes | (( (mftEntry(offset + i) & 0xFF) << i*8))
    }
    return bytes
  }

}

case class AttributeListEntry (attributeType:Int, VCN:Long, fileReference:Long)

class Verifier (imageFile:RandomAccessFile, mftEntries:ArrayList[Byte], bitMap:ArrayList[Byte], outputFile:FileWriter) {
  // size of MFT entry (2 clusters)
  private var mftEntry: Array[Byte] = new Array[Byte](1024)
  private var mftEntryFilled:Boolean = false
  private var filenameAttrOffsets:List[Int] = List()
  private var dataAttrOffset:Int = 0
  private var runlistOffset:Int = 0
  private var indexRootOffset:Int = 0
  private var attributeListDataAttr:Boolean = false
  private var attributeListIndexRoot:Boolean = false  
  private var attributeList: ArrayList[AttributeListEntry] = new ArrayList[AttributeListEntry]
  private var mftEntriesList:ArrayList[Array[Byte]] = new ArrayList[Array[Byte]]
  private var mftEntryIndexRoot:Array[Byte] = new Array[Byte](1024)
  private var baseRecord:Boolean = true
  
  def readBytes (offset:Int, quantity:Int) : Long =  
  {
    var bytes:Long = 0x00
    var i = 0
    for (i <- 0 until quantity) {
      bytes = bytes | (( (mftEntry(offset + i) & 0xFF) << i*8))
    }
    return bytes
  }

  def readBytes (offset:Int, quantity:Int, mftEntry:Array[Byte]) : Long =  
  {
    var bytes:Long = 0x00
    var i = 0
    for (i <- 0 until quantity) {
      bytes = bytes | (( (mftEntry(offset + i) & 0xFF) << i*8))
    }
    return bytes
  }
  
  def fixupArray (mftEntry:Array[Byte]) : Array[Byte] = {
    val fixupOffset:Long = readBytes (4, 2, mftEntry)
    val numFixupArray:Long = readBytes (6, 2, mftEntry)
    var fixupArray:Array[Long] = new Array[Long](numFixupArray.toInt)
    for (i <- 0 until numFixupArray.toInt) {
      fixupArray(i) = readBytes (fixupOffset.toInt + (i*2), 2, mftEntry)
    }

    var signatureSlot:Long = 0
    for (i <- 0 until numFixupArray.toInt) {
      if ((i*512+510) < 1024) {
        signatureSlot = readBytes (i*512+510, 2, mftEntry)
        mftEntry(i*512+510) = fixupArray(i+1).toByte
        mftEntry(i*512+511) = (fixupArray(i+1) >> 8).toByte
      }
    }
    return mftEntry
  }

  def readAttributeList (offsetToAttribute:Int) {
    println ("Reading attribute list")
    println (readBytes (offsetToAttribute, 4))
    val lengthOfAttribute = readBytes (offsetToAttribute+4, 4)
    if (readBytes (offsetToAttribute+8, 1) == 0) {
      println ("attribute list is resident")
      val sizeOfContent = readBytes (offsetToAttribute+16, 4)
      val offsetToContent = readBytes (offsetToAttribute+20, 2)

      var attributeFound = 0 
      var VCN:Long = 0
      var fileReference:Long = 0

      var offsetImAt = 0
      val offsetOfAttributeListContent = offsetToAttribute + offsetToContent

      var otherMFTEntry:Array[Byte] = new Array[Byte](1024)
      while (offsetImAt < sizeOfContent) {
	attributeFound = readBytes ((offsetOfAttributeListContent+offsetImAt).toInt, 4).toInt
	VCN = readBytes ((offsetOfAttributeListContent+offsetImAt+8).toInt, 8)
	fileReference = readBytes ((offsetOfAttributeListContent+offsetImAt+16).toInt, 6)
	println
	println ("attribute found: " + attributeFound)
	println ("VCN: " + VCN)
	println ("file reference: " + fileReference)
	attributeList.add (AttributeListEntry (attributeFound, VCN, fileReference))

	if (attributeFound == 0x80) {
	  attributeListDataAttr = true	  
	  otherMFTEntry = new Array[Byte](1024)
	  for (i <- 0 until 1024) {
	    otherMFTEntry(i) = mftEntries (fileReference.toInt*1024+i)
	  }
	  otherMFTEntry = fixupArray (otherMFTEntry)
	  mftEntriesList.add (otherMFTEntry)
	} else if (attributeFound == 0x90) {
	  attributeListIndexRoot = true 
	  for (i <- 0 until 1024) {
	    mftEntryIndexRoot(i) = mftEntries (fileReference.toInt*1024+i)
	  }

	  mftEntryIndexRoot = fixupArray(mftEntryIndexRoot)

	  var offsetToAttribute:Int = readBytes (20, 2, mftEntryIndexRoot).toInt
	  var attributeFound:Long = 0
	  while (attributeFound != 0xFFFFFFFF) {
	    attributeFound = readBytes (offsetToAttribute.toInt, 4, mftEntryIndexRoot)
	    var lengthOfAttribute:Int = readBytes (offsetToAttribute+4, 4, mftEntryIndexRoot).toInt
	    if (attributeFound == 0x90) {
	      indexRootOffset = offsetToAttribute
	    }
	    offsetToAttribute = offsetToAttribute + lengthOfAttribute
	  }	      

	}

	offsetImAt = offsetImAt + readBytes ((offsetOfAttributeListContent+offsetImAt+4).toInt, 2).toInt
      }
    }
  }

  def checkByteRunsDirectory (indexRootOffset:Int, fo:FO) {
    var passed:Boolean = false 
    val byteRuns:List[ByteRun] = fo.byteRuns

    var fileSizeRead:Long = 0
    var contentFileOffset:Long = 0
    var contentLen:Long = 0
    if (attributeListIndexRoot) {
      fileSizeRead = readBytes (indexRootOffset + 16, 4, mftEntryIndexRoot)
    } else if (indexRootOffset != 0) {
      fileSizeRead = readBytes (indexRootOffset + 16, 4)
    }
    // fiwalk splits up byte runs into 512 chunks when resident
    for (byteRun <- byteRuns) {
      if (fileSizeRead > 512) {
	contentLen = 512
	fileSizeRead = fileSizeRead - 512
      } else {
	contentLen = fileSizeRead
      }
      if ((byteRun.fileOffset.get == contentFileOffset) &&
	  (byteRun.fsOffset.get == 0) &&
	  (byteRun.imgOffset.get == fo.partitionOffset.get) &&
	  (byteRun.len.get == contentLen) &&
	  (byteRun.typeAttr.get == "resident")) {
	passed = true
      }
      contentFileOffset = contentFileOffset + contentLen
      printPassedStatus (passed, fo, "Directory Byteruns incorrect")
    }

  }

  def checkFileResidentByteRuns (dataAttrOffset:Int, fo:FO, mftEntry:Array[Byte]) {
    var passed:Boolean = false
    println ("i'm resident")
    val byteRuns:List[ByteRun] = fo.byteRuns
    var contentSizeRead:Long = readBytes (dataAttrOffset+16, 4, mftEntry)
    var contentFileOffset:Long = 0
    var contentLen:Long = 0
    for (byteRun <- byteRuns) {
      // fiwalk splits up byte runs into 512 chunks when resident
      if (contentSizeRead > 512) {
	contentLen = 512
	contentSizeRead = contentSizeRead - 512
      } else {
	contentLen = contentSizeRead
      }
      if ((byteRun.fileOffset.get == contentFileOffset) &&
	  (byteRun.fsOffset.get == 0) &&
	  (byteRun.imgOffset.get == fo.partitionOffset.get) &&
	  (byteRun.len.get == contentLen) &&
	  (byteRun.typeAttr.get == "resident")) {
	passed = true
      } 
      contentFileOffset = contentFileOffset + contentLen
      printPassedStatus (passed, fo, "File Byteruns resident incorrect")
    }

  }

  /*
   * if there is an attribute list that has the $DATA attribute,
   * returns the byteRuns thats are left to read after reading runlists
   * from that MFT entry that contains $DATA
   */
  def checkFileNonResidentByteRuns (fo:FO, byteRuns:List[ByteRun], mftEntryUsed:Array[Byte], dataAttrOffset:Int, fileOffsetInput:Long) : List[ByteRun] = {

    var iterThroughByteRuns:Long = 0 // length of current run
    var offsetWithinRunlist:Int = dataAttrOffset + readBytes(dataAttrOffset+32, 2, mftEntryUsed).toInt
    var fileOffset:Long = fileOffsetInput
    var runOffsetField:Long = 0
    var overallOffset:Long = 0
    var runOffset:Long = 0xFF
    var runLength:Long = 0
    var runLengthField:Long = 0

    var initializedSize = readBytes (dataAttrOffset+56,8, mftEntryUsed)
    var iter = 0
    for (byteRun <- byteRuns) {
      iter += 1
      var lastByteRun:Boolean = false
      if (byteRun == byteRuns.last) {
	lastByteRun = true
      }
      if (iterThroughByteRuns == 0) {
	val infoByte = readBytes (offsetWithinRunlist, 1, mftEntryUsed)
	if (infoByte == 0 && attributeListDataAttr) {
	  val newByteRuns:List[ByteRun] = byteRuns.drop(iter-1)
	  return newByteRuns
	}
    	runOffset = (readBytes (offsetWithinRunlist, 1, mftEntryUsed) >> 4 & 0xF)
    	runLength = (readBytes (offsetWithinRunlist, 1, mftEntryUsed) & 0xF)
	
    	runLengthField = readBytes (offsetWithinRunlist + 1, runLength.toInt, mftEntryUsed)
    	runOffsetField = readBytes (offsetWithinRunlist + 1 + runLength.toInt, runOffset.toInt,mftEntryUsed)
    	runOffsetField = (runOffsetField << (8 * (8 - runOffset))) >> (8 * (8 - runOffset))
    	overallOffset = overallOffset + runOffsetField

	iterThroughByteRuns = runOffset match {
	  case 0 => runLengthField
	  case _ => 0
	}
      }

      fo.compressed match {
	case 0 => 
	  checkUncompressedByteRuns (byteRun, fo, fileOffset, runLengthField, overallOffset, lastByteRun, initializedSize)
	case 1 => 
	  runOffset match {
	    case 0 => checkCompressedByteRuns (byteRun, fo, fileOffset, iterThroughByteRuns, lastByteRun, runLengthField)
	    case _ => checkUncompressedByteRuns (byteRun, fo, fileOffset, runLengthField, overallOffset, lastByteRun, initializedSize)
	  }
      }

      iterThroughByteRuns match {
	case 0 =>
	  fileOffset = fileOffset + (runLengthField.toInt * 512)
    	  offsetWithinRunlist = offsetWithinRunlist + runOffset.toInt + runLength.toInt+1
	case 1 =>
	  fileOffset = fileOffset + byteRun.len.get
    	  offsetWithinRunlist = offsetWithinRunlist + runOffset.toInt + runLength.toInt+1
	  iterThroughByteRuns = iterThroughByteRuns - 1
	case _ => 
	  fileOffset = fileOffset + byteRun.len.get
	  iterThroughByteRuns = iterThroughByteRuns - 1
      }
    }
    return Nil
  }

  def checkInitializedSize (byteRun:ByteRun, fo:FO, fileOffset:Long, runLengthField:Long, overallOffset:Long, lastByteRun:Boolean, initializedSize:Long) {
    var passed:Boolean = false
    if (lastByteRun) {
      if ((byteRun.fileOffset.get == (initializedSize+512)) &&
	  (byteRun.fill.get == 0) &&
	  (byteRun.len.get == (fo.fileSize.get-initializedSize-512)) ) {
	writeToOutputFile (fo, "initalizedSize is smaller than actual filesize. Runlist not read in correctly in fiwalk")
	passed = true
      } else if ((byteRun.fileOffset.get == (initializedSize)) &&
	  (byteRun.fill.get == 0) &&
	  (byteRun.len.get == (fo.fileSize.get-initializedSize)) ) {
	passed = true
      }

    } else if ((byteRun.fileOffset.get + byteRun.len.get) > initializedSize) {
      if ((byteRun.fileOffset.get == fileOffset) &&
    	  (byteRun.fsOffset.get == (overallOffset*512)) &&
    	  (byteRun.imgOffset.get == (overallOffset*512+fo.partitionOffset.get)) &&

	  (byteRun.len.get == (initializedSize-fileOffset+512))) {
	passed = true
      }

    } else if ((byteRun.fileOffset.get + byteRun.len.get) == initializedSize) {
      if ((byteRun.fileOffset.get == fileOffset) &&
    	  (byteRun.fsOffset.get == (overallOffset*512)) &&
    	  (byteRun.imgOffset.get == (overallOffset*512+fo.partitionOffset.get)) &&
	  (byteRun.len.get == (initializedSize-fileOffset))) {
	passed = true
      }

    } else {
      // normal byte run
      if ((byteRun.fileOffset.get == fileOffset) &&
    	  (byteRun.fsOffset.get == (overallOffset*512)) &&
    	  (byteRun.imgOffset.get == (overallOffset*512+fo.partitionOffset.get)) &&
	  (byteRun.len.get == (runLengthField * 512))) {
	passed = true
      }
    }
    printPassedStatus (passed, fo, " ByteRuns incorrect, initialized size < actual size")

  }

  def checkUncompressedByteRuns (byteRun:ByteRun, fo:FO, fileOffset:Long, runLengthField:Long, overallOffset:Long, lastByteRun:Boolean, initializedSize:Long) {
    var passed:Boolean = false
    if ((initializedSize != 0) && (initializedSize < fo.fileSize.get)) {
      checkInitializedSize(byteRun, fo, fileOffset, runLengthField, overallOffset, lastByteRun, initializedSize)
    } else {
      if ((byteRun.fileOffset.get == fileOffset) &&
    	  (byteRun.fsOffset.get == (overallOffset*512)) &&
    	  (byteRun.imgOffset.get == (overallOffset*512+fo.partitionOffset.get))) {
	if (lastByteRun) {
	  if (byteRun.len.get == (fo.fileSize.get-fileOffset)) {
    	    passed = true
	  }
	} else {
	  if (byteRun.len.get == (runLengthField * 512))
	    passed = true
	}	    
      } 
    printPassedStatus (passed, fo, "Byteruns not compressed incorrect")
    }
  }

  def checkCompressedByteRuns (byteRun:ByteRun, fo:FO, fileOffset:Long, iterThroughByteRuns:Long, lastByteRun:Boolean, runLengthField:Long) {
    var passed:Boolean = false
    if ((byteRun.fileOffset.get == fileOffset)) {
      if (lastByteRun) {
    	if (byteRun.len.get == (fo.fileSize.get-fileOffset))
    	  passed = true
      } else {
	if (iterThroughByteRuns > 0) {
	  if (byteRun.len.get == 512) 
    	    passed = true
	} else if  (byteRun.len.get == (runLengthField * 512)) {
	  passed = true
	} 
      }
    }
    printPassedStatus (passed, fo,  "Compressed Byteruns incorrect")  
  }

    /*
     * read through byteRuns ex. (byteRun <- byteRuns)
     * base it off of the VCN numbers to see when it is time to move on to next entry in attribute list
     * keep track of how many clusters have been read (VCN number)
     */
  def checkByteRunsAttrList (attributeList:ArrayList[AttributeListEntry], fo:FO) {
    var byteRuns:List[ByteRun] = fo.byteRuns    
    for (mftEntryList <- mftEntriesList) {
      println ("mftEntryList")

      var offsetToAttribute:Int = readBytes (20, 2, mftEntryList).toInt
      var attributeFound:Long = 0
      while (attributeFound != 0xFFFFFFFF) {
    	attributeFound = readBytes (offsetToAttribute.toInt, 4, mftEntryList)
    	var lengthOfAttribute:Int = readBytes (offsetToAttribute+4, 4, mftEntryList).toInt
    	if (attributeFound == 0x80) {
	  println ("$DATA found")
	  val resident = readBytes (offsetToAttribute+8,1,mftEntryList)
	  resident match {
	    case 0 => println ("resident")
	      checkFileResidentByteRuns (offsetToAttribute, fo, mftEntryList)
	    case 1 => println ("non-resident")
	      var startingVCN = readBytes(offsetToAttribute+16,8,mftEntryList) 
	      byteRuns = checkFileNonResidentByteRuns (fo, byteRuns, mftEntryList, offsetToAttribute,startingVCN*512)

	    case _ => writeToOutputFile (fo, "resident error")
	  }
    	}
    	offsetToAttribute = offsetToAttribute + lengthOfAttribute
      }
      
    }
  }

  def printPassedStatus (passed:Boolean, fo:FO, message:String) {
    passed match {
      case true => println ("    | passed!")
      case false =>
	writeToOutputFile (fo, message)
	println (message)
	//    		    throw new RuntimeException (message)
    }
  }

  def writeToOutputFile (fo:FO, message:String) {
    if (fo.inode != None) {
      outputFile.write (fo.inode.get + " " + message + "\n")
    } else if (fo.imageOffset != None) {
      outputFile.write (fo.imageOffset.get + " " + message + "\n")
    } else {
      outputFile.write (message + "\n")
    }
  }

  def verifyFO (fo:FO) : Unit = {
    mftEntry = new Array[Byte](1024)
    mftEntryFilled = false
    filenameAttrOffsets = List()
    dataAttrOffset = 0
    runlistOffset = 0
    indexRootOffset = 0
    attributeListDataAttr = false
    attributeListIndexRoot = false
    attributeList = new ArrayList[AttributeListEntry]
    mftEntriesList = new ArrayList[Array[Byte]]
    mftEntryIndexRoot = new Array[Byte](1024)


    // if offset is given instead of inode, fill in mftEntry using offset

    {
      if (fo.imageOffset != None) {
	var passed:Boolean = false
	print ("imageOffset: ")
	val imageOffset:Long = fo.imageOffset.get
	println (imageOffset)

	imageFile.seek(imageOffset + fo.partitionOffset.get)
	imageFile.readFully(mftEntry)
	mftEntryFilled = true
	mftEntry = fixupArray(mftEntry)
	var fileHeader: Array[Byte] = Array(0x46, 0x49, 0x4c, 0x45)
	if (mftEntry.startsWith (fileHeader))
	  passed = true
	printPassedStatus (passed, fo,  "Image offset incorrect")

      }
    }

    {
      if (fo.inode != None) {
	var passed:Boolean = false
	val readInode:Long = fo.inode.get
	print ("inode: ")
	print (readInode)
	for (i <- 0 until 1024) {
      	  mftEntry(i) = mftEntries(readInode.toInt*1024+i)
	}
	mftEntryFilled = true
	mftEntry = fixupArray(mftEntry)
	var fileHeader: Array[Byte] = Array(0x46, 0x49, 0x4c, 0x45)
	if (mftEntry.startsWith (fileHeader))
	  passed = true
	printPassedStatus (passed, fo,  "Image offset incorrect")
      }
    }

    baseRecord = readBytes(32,8) match {
      case 0 => true 
      case _ => false
    }
    baseRecord match {
      case true => println ("base record")
      case false => println ("not base record")
    }

    var offsetToAttribute:Int = readBytes (20, 2).toInt
    var attributeFound:Long = 0
    while (attributeFound != 0xFFFFFFFF) {
      attributeFound = readBytes (offsetToAttribute.toInt, 4)
      var lengthOfAttribute:Int = readBytes (offsetToAttribute+4, 4).toInt
      if (attributeFound == 0x20) {
	readAttributeList (offsetToAttribute.toInt)
      } else if (attributeFound == 0x30) {
	filenameAttrOffsets = offsetToAttribute::filenameAttrOffsets
      } else if (attributeFound == 0x80) {
	dataAttrOffset = offsetToAttribute
	if (readBytes (offsetToAttribute+8,1) == 1) {
	  runlistOffset = offsetToAttribute + readBytes (offsetToAttribute+32, 2).toInt
	}
      } else if (attributeFound == 0x90) {
	indexRootOffset = offsetToAttribute
      }
      offsetToAttribute = offsetToAttribute + lengthOfAttribute
    }

    {
      var passed:Boolean = false
      print ("filename: ")
      val filename:String = fo.filename
      println (filename)
      if ( fo.filename.length != 0) {
    	if (mftEntryFilled == true && !(mftEntry.isEmpty)) {
	  for (filenameAttrOffset <- filenameAttrOffsets) {
    	    val offsetToContent:Long = readBytes (filenameAttrOffset + 20, 2)
    	    val nameOffset:Int = (filenameAttrOffset + offsetToContent + 66).toInt
    	    val nameLength:Int = (readBytes ((filenameAttrOffset + offsetToContent + 64).toInt, 1)).toInt

    	    val bais:ByteArrayInputStream = new ByteArrayInputStream(mftEntry, nameOffset, mftEntry.length)
	    val bytes:Array[Byte] = new Array[Byte](nameLength+nameLength)
	    bais.read(bytes, 0, nameLength+nameLength)

	    val filenameRead:String = new String(bytes, "utf-16LE");
	    var filenameToCompare:String = ""
	    
	    if (filename.endsWith(".") || filename.endsWith("..")) {
	      passed = true                 // moves up in directory
	    } else {
	      if (filename.contains("/")) {
		val index:Int = filename.lastIndexOf("/")
		filenameToCompare = filename.drop(index+1)
	      } else {
		filenameToCompare = filename
	      }
	      if (filenameRead == filenameToCompare) {
		passed = true
	      } 
	    }
	    println ("filenameRead: " + filenameRead)
	    println ("filenameToCompare: " + filenameToCompare)
	  }	  
	  printPassedStatus (passed, fo, "Filename incorrect")
    	}      
      }
    }

    {
      var passed:Boolean = false
      if (fo.fileSize != None) {
	print ("filesize: ")
	val fileSize:Long = fo.fileSize.get
	print (fileSize)
	if (mftEntryFilled == true && !(mftEntry.isEmpty)) {
	  var fileSizeRead:Long = 0
	  if (fo.nameType == "d") {
	    if (attributeListIndexRoot) {
	      fileSizeRead = readBytes (indexRootOffset + 16, 4, mftEntryIndexRoot)
	    } else if (indexRootOffset != 0) {
	      fileSizeRead = readBytes (indexRootOffset + 16, 4)
	    } else {
	      writeToOutputFile (fo, "no indexRoot found")
	    }
	  } else if (fo.nameType == "r") {
	    /*need to know $MFT in order to fill in mftEntries in order to read attribute list
	      and set correct entries for record */
	    if ( attributeListDataAttr == true && !(mftEntries.isEmpty) ) {
	      val Inode:Long = fo.inode.get
	      for (mftEntryList <- mftEntriesList) {
		// offset 32: offset to base record
		readBytes(32,6,mftEntryList) match {
		  case 0 => 
		    println ("case 0")
		    readBytes (dataAttrOffset + 8, 1) match {
		      case 0 => fileSizeRead += readBytes (dataAttrOffset + 16, 4)
		      case 1 => fileSizeRead += readBytes (dataAttrOffset + 48, 8)
		      case _ => writeToOutputFile (fo, "resident info incorrect 1")
		    }
		  case Inode =>
		    var offsetToAttribute:Int = readBytes (20, 2, mftEntryList).toInt
		    var attributeFound:Long = 0
		    while (attributeFound != 0xFFFFFFFF) {
		      attributeFound = readBytes (offsetToAttribute.toInt, 4, mftEntryList)
		      var lengthOfAttribute:Int = readBytes (offsetToAttribute+4, 4, mftEntryList).toInt
		      if (attributeFound == 0x80) {
			readBytes (offsetToAttribute + 8, 1, mftEntryList) match {
		       	  case 0 => fileSizeRead += readBytes (offsetToAttribute + 16, 4, mftEntryList)
		       	  case 1 => fileSizeRead += readBytes (offsetToAttribute + 48, 8, mftEntryList)
		       	  case _ => writeToOutputFile (fo, "resident info incorrect 2")
		      	}
		      }
		      offsetToAttribute = offsetToAttribute + lengthOfAttribute
		    }
		  case _ => writeToOutputFile (fo, "base record incorrect")
		}
	      }
	    } else {
	      readBytes (dataAttrOffset + 8, 1) match {
		case 0 => fileSizeRead = readBytes (dataAttrOffset + 16, 4)
		case 1 => fileSizeRead = readBytes (dataAttrOffset + 48, 8)
		case _ => writeToOutputFile (fo, "resident info incorrect 3")
	      }
	    }
	  }

	  if (fileSizeRead == fileSize) {
	    passed = true
	  }
	  printPassedStatus (passed, fo, "Filesize incorrect")
	}
      }
    }

    {
      var passed:Boolean = false
      print ("Name_type: ")
      val nameType:String = fo.nameType
      print (nameType)
      if (mftEntryFilled == true && !(mftEntry.isEmpty)) {
        val nameTypeRead:Long = readBytes (22, 2)
        nameType match {
	  case "r" if ((nameTypeRead & 0x02) == 0) => passed = true
	  case "d" if ((nameTypeRead & 0x02) == 0x02) => passed = true
	  case _ => passed = false
        }
	
	printPassedStatus (passed, fo, "Name type incorrect: " + nameType)
      }      
    }

    {
      var passed:Boolean = false
      if (fo.alloc != None) {
	print ("alloc: ")
	val alloc:Long = fo.alloc.get
	val inodeRead:Long = fo.inode.get
	print (alloc)
	val bitMapOffset:Long = inodeRead/8
	val bitMapMod:Byte = (inodeRead % 8).toByte
	val byteFromMap:Byte = bitMap(bitMapOffset.toInt)
	val b:Byte = ((byteFromMap << (24+(7-bitMapMod))) >>> 31).toByte
	if (b == alloc) {
	  passed = true
	} 
	printPassedStatus (passed, fo, "Alloc incorrect")
      }
    }

    {
      var passed:Boolean = false
      if (fo.used != None) {
	print ("used: ")
	val used:Long = fo.used.get
	print (used)
	if (mftEntryFilled == true && !(mftEntry.isEmpty)) {
	  val usedRead:Long = readBytes (22, 2)
	  used match {
	    case 0 if ((usedRead & 0x01) == 0) => passed = true
	    case 1 if ((usedRead & 0x01) == 0x01) => passed = true
	    case _ => passed = false
	  }
	}
	printPassedStatus (passed, fo, "used incorrect")
      }
    }

    {
      var passed:Boolean = false
      print ("compressed: ")
      val compressed:Long = fo.compressed
      print (compressed)
      var compressedRead:Long = 0
      if (mftEntryFilled == true && !(mftEntry.isEmpty)) {	  
	if (attributeListDataAttr) {
	  var offsetToAttribute:Int = readBytes (20, 2, mftEntriesList(0)).toInt
	  var attributeFound:Long = 0
	  while (attributeFound != 0xFFFFFFFF) {
	    attributeFound = readBytes (offsetToAttribute.toInt, 4, mftEntriesList(0))
	    var lengthOfAttribute:Int = readBytes (offsetToAttribute+4, 4, mftEntriesList(0)).toInt
	    if (attributeFound == 0x80) {
	      dataAttrOffset = offsetToAttribute
	      compressedRead = readBytes (dataAttrOffset+12, 2, mftEntriesList(0))
	    }
	    offsetToAttribute = offsetToAttribute + lengthOfAttribute
	  }	
	} else {
	  compressedRead = readBytes (dataAttrOffset + 12, 2)
	}
	compressedRead = (compressedRead & 0x01)
	if (compressedRead == 1 && compressedRead == compressed) {
	  passed = true
	} else if (compressed == 0) {
	  passed = true
	} 
	printPassedStatus (passed, fo, "Compressed incorrect")
      }
    }
    
    {
      var passed:Boolean = false
      if (fo.nlink != None) {
	print ("nlink: ")
	val nlink:Long = fo.nlink.get
	print (nlink)
	if (mftEntryFilled == true && !(mftEntry.isEmpty)) {
	  val nlinkRead:Long = readBytes (18, 2)
	  if (nlinkRead == nlink) {
	    passed = true
	  } 
	}
	printPassedStatus (passed, fo, "Nlink incorrect")
      }
    }

    {
      var passed:Boolean = false
      if (fo.seq != None) {
	print ("seq: ")
	val seq:Long = fo.seq.get
	print (seq)
	if (mftEntryFilled == true && !(mftEntry.isEmpty)) {
	  val seqRead:Long = readBytes (16, 2)
	  if (seqRead == seq) {
	    passed = true
	  } 
	}
	printPassedStatus (passed, fo, "Seq incorrect")
      }
    }

    {
      print ("byte_runs: ")
      print (fo.byteRuns)
      if (fo.byteRuns == Nil) {
	println ("No byte runs to compare")
      } else if (baseRecord) { 
	val byteRuns:List[ByteRun] = fo.byteRuns

	if (mftEntryFilled == true && !(mftEntry.isEmpty)) {
	  fo.nameType match {
	    case "d" => checkByteRunsDirectory ( indexRootOffset, fo)
	    case "r" => 
	      attributeListDataAttr match {
		case true => checkByteRunsAttrList (attributeList, fo)
		case _ =>
		  readBytes (dataAttrOffset+8, 1) match {
		    case 0 => checkFileResidentByteRuns (dataAttrOffset, fo, mftEntry)
		    case 1 => 
		      checkFileNonResidentByteRuns (fo, byteRuns, mftEntry, dataAttrOffset, 0)
		    case _ => writeToOutputFile (fo, "resident info incorrect 4")
		  }
	      }
	    case _ => writeToOutputFile (fo, "Byteruns: neither directory nor file")
	  }
	}    
      }
    }
  }
}
