
# TODO: levelDir should probably be an attribute of LevelFileInfo

#
# LevelFileInfo contains information about a level file.
#
class LevelFileInfo
  attr_accessor :nr, :type, :packed

  #
  # makes new LevelFileInfo object. If 'nr' is a number, this sets @nr to the
  # value and tries to find the level file of that number in current levelset.
  #  
  def initialize(nr = nil)
    @nr       = 0
    @type     = LEVEL_FILE_TYPE_UNKNOWN
    @packed   = false
    @basename = nil
    @filename = nil
    if nr
      @nr = nr
      determineFilename
      determineFiletype
    end
  end
  
  def basename
    @basename
  end
  def basename=(value)
    @filename = nil
    @basename = value
  end
  
  def filename
    @filename = getLevelFilenameFromBasename(@basename) if ! @filename
    @filename
  end  
  def filename=(value)
    @filename = value
  end
  
  #
  # Converts level basename into filename (prefixing it with current level
  # directory).
  #
  private def LevelFileInfo.getLevelFilenameFromBasename(basename)
    getCurrentLevelDir + '/' + basename
  end

  #
  # This tries to detect if the file 'basename' is a packed level, and if it
  # is, it returns its type. If it doesn't detect anything, it returns
  # LEVEL_FILE_TYPE_UNKNOWN.
  #
  private def LevelFileInfo.getFileTypeFromBasename(basename)
    # ---------- try to determine file type from filename ----------

    # check for typical filename of a Supaplex level package file
    return LEVEL_FILE_TYPE_SP if basename.length == 10 && basename[0,8].downcase == "levels.d"

    # ---------- try to determine file type from filesize ----------

    begin
      return LEVEL_FILE_TYPE_SP if File.size(getLevelFilenameFromBasename(basename)) == 170496
    rescue Exception
      # if we can't stat the file, we won't stop the program, we just return incorrect
      # value (LEVEL_FILE_TYPE_UNKNOWN instead of LEVEL_FILE_TYPE_SP). I'm not sure if
      # this is "right" behavior, but it was so in original R'n'D code.
    end

    LEVEL_FILE_TYPE_UNKNOWN
  end

  #
  # This searches for packed level files in current level directory.
  # getFileTypeFromBasename() is used to determine if a file is a packed level.
  #
  private def LevelFileInfo.getPackedLevelBasename(type)
    begin
      dir = Dir.entries(getCurrentLevelDir)
    rescue Exception
      error(ERR_WARN, "cannot read current level directory '#{getCurrentLevelDir}'")
    end
  
    dir.each do |basename|
      ftype = getFileTypeFromBasename(basename)
      if(ftype != LEVEL_FILE_TYPE_UNKNOWN)
        return basename if type == LEVEL_FILE_TYPE_UNKNOWN || type == ftype
      end
    end
  
    UNDEFINED_FILENAME # default: undefined file
  end

  #
  # This returns a default filename of a native R'n'D level (placed in current
  # level directory). For example, basename for level #3 is '003.level'.
  #
  def LevelFileInfo.getDefaultLevelFilename(nr)
    bn = (nr < 0 ? "template" : sprintf("%03d",nr)) + "." + LEVELFILE_EXTENSION
    getLevelFilenameFromBasename(getSingleLevelBasename(nr))
  end
  
  #
  # This seems to be a helper function called only from determineFilename()
  # that's used to set @type and @basename in levels that aren't packed (aka
  # single levels).
  # You can use sprintf syntax to make the basename.
  # Returns true if a file with that filename actually exists.
  #
  private def setSingleLevelFilename(type, basename_fmt, *basename_args)
    bn = sprintf(basename_fmt, *basename_args)
    @type = type
    @packed = false
    @basename = bn
    
    fileExists(filename)
  end
  
  #
  # This seems to be a helper function called only from determineFilename()
  # that's used to set @type and @basename in packed levels. Because of their
  # nature, all packed levels are in one file, and that file is the only level
  # file in the levelset, so it can be searched for.
  # Returns true if a file with that filename actually exists.
  #
  private def setPackedLevelFilename(type)
    @type = type
    @packed = true
    @basename = getPackedLevelBasename(type)
    
    fileExists(filename)
  end

  #
  # This function is used in LevelFileInfo#determineFilename() to map level-
  # -type codes from levelinfo.conf to LEVEL_FILE_TYPE_* constants.
  #
  private def LevelFileInfo.getFiletypeFromId(filetype_id)
    filetypeIdList = {
      "RND" => LEVEL_FILE_TYPE_RND,
      "BD"  => LEVEL_FILE_TYPE_BD,
      "EM"  => LEVEL_FILE_TYPE_EM,
      "SP"  => LEVEL_FILE_TYPE_SP,
      "DX"  => LEVEL_FILE_TYPE_DX,
      "SB"  => LEVEL_FILE_TYPE_SB,
      "DC"  => LEVEL_FILE_TYPE_DC,
    }
    filetypeIdList.default = LEVEL_FILE_TYPE_UNKNOWN
  
    filetypeIdList[filetype_id.upcase]
  end
  
  #
  # This function tries to find the file for level '@nr', trying possible values
  # and types for various level formats. For example, if @nr is 2, it searches
  # for '002.level' (native R'n'D level), then 'aa2', 'AA2', '2', '02s' (EM
  # levels from various EM versions), etc. When an existing file is found, the
  # function returns.
  #
  private def determineFilename
    # special case: level number is negative => check for level template file
    if @nr < 0
      setSingleLevelFilename(LEVEL_FILE_TYPE_RND, "template.%s", LEVELFILE_EXTENSION)

      # no fallback if template file not existing
      return
    end

    # special case: check for file name/pattern specified in "levelinfo.conf"
    if leveldir_current.level_filename
      filetype = LevelFileInfo.getFiletypeFromID(leveldir_current.level_filetype)

      # TODO: try if it's possible to use level_filename to crash R'n'D (or to create buffer overflow), it appears to not be checked
      return if setSingleLevelFilename(filetype, leveldir_current.level_filename, @nr)
    end

    # check for native Rocks'n'Diamonds level file
    return if setSingleLevelFilename(LEVEL_FILE_TYPE_RND, "%03d.%s", @nr, LEVELFILE_EXTENSION)

    # check for Emerald Mine level file (V1)
    return if setSingleLevelFilename(LEVEL_FILE_TYPE_EM, "a%c%c", ?a + ((@nr / 10) % 26), ?0 + (@nr % 10))
    return if setSingleLevelFilename(LEVEL_FILE_TYPE_EM, "A%c%c", ?A + ((@nr / 10) % 26), ?0 + (@nr % 10))

    # check for Emerald Mine level file (V2 to V5)
    return if setSingleLevelFilename(LEVEL_FILE_TYPE_EM, "%d", @nr)
  
    # check for Emerald Mine level file (V6 / single mode)
    return if setSingleLevelFilename(LEVEL_FILE_TYPE_EM, "%02ds", @nr)
    return if setSingleLevelFilename(LEVEL_FILE_TYPE_EM, "%02dS", @nr)

    # check for Emerald Mine level file (V6 / teamwork mode)
    return if setSingleLevelFilename(LEVEL_FILE_TYPE_EM, "%02dt", @nr)
    return if setSingleLevelFilename(LEVEL_FILE_TYPE_EM, "%02dT", @nr)

    # check for various packed level file formats
    return if setPackedLevelFilename(LEVEL_FIlE_TYPE_UNKNOWN)

    # no known level file found -- use default values (and fail later)
    setSingleLevelFilename(LEVEL_FILE_TYPE_RND, "%03d.%s", @nr, LEVELFILE_EXTENSION)
  end
  
  #
  # If the type is LEVEL_FILE_TYPE_UNKNOWN, this method tries to guess it using
  # getFileTypeFromBasename().
  #
  private def determineFiletype
    @type = getFileTypeFromBasename(@basename) if @type == LEVEL_FILE_TYPE_UNKNOWN
  end
end

