require 'tempfile'

UTILS_VISIONA = "1.0.0"

module Utils_visiona

  DEBUG = 1
  CCSDS_HEADER_LENGTH = 8

  class BadLengthError < StandardError; end
  class VerifyError < StandardError; end
  class NoTargetError < StandardError; end

  def hasSymbol?(hashe, symbol, data_type, bits)

    return false if hashe[symbol].nil?
    return false unless hashe[symbol].is_a?(data_type)

    if data_type == Integer

      # Check for int (byte) stuff
      return false if hashe[symbol] < 0
      return false if hashe[symbol] >= (2**bits)
    elsif data_type == Array

      # Check for array stuff
      for i in 0..hashe[symbol].length-1 do

        return false if hashe[symbol][i] < 0
        return false if hashe[symbol][i] >= (2**bits)
      end
    end

    return true
  end

  def checkByteArray(array)

    raise VerifyError, "Invalid input #{array.class}" unless array.is_a?(Array)

    array.each do |ele|

      verifyInput(Integer, ele.class)
      raise VerifyError, "Not a byte" unless (ele >= 0 and ele < 256)
    end
  end

  def getBits (binSequence, bitStart, bitEnd)

    return false if bitStart > bitEnd

    if bitStart==bitEnd
      return 1 if (binSequence & (1 << bitStart-1) != 0)
      return 0
    end

    soma = 0
    for i in 0..(bitEnd-bitStart)
      soma+=2**i if (binSequence & (1 << (bitStart+i)-1) != 0)
    end

    return soma
  end

  def strToDecArray(string)

    arr = Array.new
    string.each_char do |c|
      arr << c.ord
    end
    return arr
  end

  def getDecValueFromHexa (str)

    hexaDict = {"A"=>10, "B"=>11, "C"=>12, "D"=>13, "E"=>14, "F"=>15}
    return hexaDict[str].nil? ? str.to_i : hexaDict[str]
  end

  # This functions return 2 hexas for each byte in array; e.g. [12, 1] = 0C01
  def decArrayToHexaStr(array)

    str = ""
    array.each do |dec|
      aux = dec.to_s(16)
      if aux.length != 2
        aux = "0#{aux}"
      else end
      str << aux
    end
    return str.delete('" [],').upcase
  end

  def hexaStrToDecArray(hexaString)

    packet = Array.new
    for i in (0..hexaString.length-2).step(2)
      aux1 = getDecValueFromHexa(hexaString[i])
      aux2 = getDecValueFromHexa(hexaString[i+1])
      packet << ((aux1 << 4) + aux2)
    end
    return packet
  end

  def verifyLength(operation, actual, expected)

    case operation.upcase
    when "LESS"

      if actual < expected
        OS_print(0, "Expected min length \"#{expected}\". Got \"#{actual}\"")
        raise BadLengthError, "Expected min length \"#{expected}\". Got \"#{actual}\""
      end
    when "DIFF"

      if actual != expected
        OS_print(0, "Expected exactly length \"#{expected}\". Got \"#{actual}\"")
        raise BadLengthError, "Expected exactly length \"#{expected}\". Got \"#{actual}\""
      end
    when "BIGGER"

      if actual > expected
        OS_print(0, "Expected max length \"#{expected}\". Got \"#{actual}\"")
        raise BadLengthError, "Expected max length \"#{expected}\". Got \"#{actual}\""
      end
    else
      raise ArgumentError, "Invalid operation for verifyLength function"
    end
  end

  def compareValues(actual, expected, msg)

    unless actual == expected
      OS_print(0, "Should have \"#{expected}\" #{msg}. Got \"#{actual}\"")
      raise VerifyError, "Should have \"#{expected}\" #{msg}. Got \"#{actual}\""
    end
  end

  def removeCCSDSHeader(pdu)

    return pdu[CCSDS_HEADER_LENGTH..(pdu.length-1)]
  end

  def writeFile(filename, data)

    # i'm expecting an array of binary data array
    raise ArgumentError, "Data is not a Hash" unless data.is_a?(Hash)

    data.each do |key, value|
      data[key] = value.pduPayload.data
    end

    begin
      File.open(filename, 'wb+') do |file|
        IO.binwrite(file, data.sort.to_h.values.flatten.pack('c*'))
      end
    rescue Exception => err
      file = Tempfile.new('cfdp')
      IO.binwrite(file, data.sort.to_h.values.flatten.pack('c*'))
      file.close
      puts err
      puts "Error while writing file. It has been saved in /tmp with basename 'cfdp'"
    end
  end

  def OS_print(errorCode, string)

    if DEBUG==1
      outputStr = ""
      errorCodeHash = {0=>"ERROR", 1=>"WARNING", 2=>"SUCCESS"}
      outputStr << errorCodeHash[errorCode] << ": " << string << "\nGenerated by " << caller_locations(1,1).to_s << "\n\n"
      #puts outputStr
    end
  end

  def completeBytes(array, expectedSize, type)

    raise "Array is bigger than expected." if array.length > expectedSize

    # I will complete bytes to the left here
    if type.upcase.eql?("LITTLE_ENDIAN")

      while (array.length < expectedSize)
        array.unshift(0)
      end
    # I will complete bytes to the right here
    elsif type.upcase.eql?("BIG_ENDIAN")

      while (array.length < expectedSize)
        array << 0
      end
    end

    return array
  end

  # this function performs operation to transform id X to 0.x str binary array (eg 21 to "0.21 = [48, 46, 50, 49]"")
  def IDtoStrArray(id)

    strArray = Array.new
    strArray << 48
    strArray << 46

    id.to_s.each_char do |c|
      strArray << c.ord
    end
    return strArray
  end

  def verifyInput(expected, actual)

    if expected.is_a?(Array)

      if !expected.include?(actual)

        puts "#{caller}\n. Expected object of classes #{expected.to_s}." + " Received " + (actual.nil? ? "nil" : "#{actual}")
        raise ArgumentError, "#{caller}\n. Expected object of classes #{expected.to_s}. Received " + (actual.nil? ? "nil" : "#{actual}")
      end
    else

      if expected != actual

        puts "#{caller}\n. Expected object of class " + (expected.nil? ? "nil." : "#{expected}.") + " Received " + (actual.nil? ? "nil" : "#{actual}")
        raise ArgumentError, "#{caller}\n. Expected object of class " + (expected.nil? ? "nil." : "#{expected}.") + " Received " + (actual.nil? ? "nil" : "#{actual}")
      end
    end
  end

  def calculateFileChecksum(fileName)

    begin

      sum = 0
      File.open(fileName, 'rb') do |file|

        until file.eof?

            buffer = file.read(4)

        (sum += buffer.unpack('L>')[0]; next) if buffer.length == 4
        for i in 0..(buffer.length-1); sum += (buffer[i].ord<<(8*(4-i-1))); end
        end
      end
      return Utils_visiona.getBits(sum, 1, 32)
    rescue Exception => err

      puts "Error is" + err
    end
  end

  module_function :hasSymbol?
  module_function :checkByteArray
  module_function :getBits
  module_function :strToDecArray
  module_function :getDecValueFromHexa
  module_function :decArrayToHexaStr
  module_function :hexaStrToDecArray
  module_function :writeFile
  module_function :OS_print
  module_function :verifyLength
  module_function :compareValues
  module_function :removeCCSDSHeader
  module_function :completeBytes
  module_function :IDtoStrArray
  module_function :verifyInput
  module_function :calculateFileChecksum
end # module Utils_visiona