
-- Availability.lua: logic for the availability of recipes to characters
-- $Id: Availability.lua 700 2007-01-18 04:57:09Z jnmiller $

-----------------------------------------------------------------------------
-- Generic, abstracted availability entry points
-----------------------------------------------------------------------------

function RecipeRadar_Availability_Init()

   if (RecipeRadar_Options.RealmAvailability) then
      RecipeRadar_RealmAvailDropDown:Show()
      RecipeRadar_PersonAvailDropDown:Hide()
   else
      RecipeRadar_PersonAvailDropDown:Show()
      RecipeRadar_RealmAvailDropDown:Hide()
   end

end

function RecipeRadar_Availability_Toggle()

   if (RecipeRadar_RealmAvailDropDown:IsVisible()) then
      RecipeRadar_PersonAvailDropDown:Show()
      RecipeRadar_RealmAvailDropDown:Hide()
      RecipeRadar_Options.RealmAvailability = false
   else
      RecipeRadar_RealmAvailDropDown:Show()
      RecipeRadar_PersonAvailDropDown:Hide()
      RecipeRadar_Options.RealmAvailability = true
   end
   
   RecipeRadar_FrameUpdate()

end

-----------------------------------------------------------------------------
-- Indicator for vendor/recipe item lists
-----------------------------------------------------------------------------

-- Returns a colored indication of a recipe's availability.
function RecipeRadar_Availability_GetIndicator(recipe)

   if (RecipeRadar_Options.RealmAvailability) then
      return RecipeRadar_RealmAvail_GetIndicator(recipe)
   else
      return RecipeRadar_PersonAvail_GetIndicator(recipe)
   end

end

-- Returns a string with the recipe's skill in parens.
function RecipeRadar_PersonAvail_GetIndicator(recipe)

   return format(TEXT(PARENS_TEMPLATE), recipe.Skill)

end

-- Returns a string with a little colored realm-wide availability
-- indicator for the main vendor/recipe list.
function RecipeRadar_RealmAvail_GetIndicator(recipe)

   local key = RecipeRadar_Availability_GetKey(recipe)
   if (not key) then return "" end

   return RecipeRadar_ColorToCode(RecipeRadar_Availabilities[key].Color) ..
         "*" .. FONT_COLOR_CODE_CLOSE
   
end

-----------------------------------------------------------------------------
-- Availability tooltip code
-----------------------------------------------------------------------------

-- Populates the tooltip that displays on recipe button mouseover.
function RecipeRadar_Availability_CreateTooltip(recipe)

   local recipe_name = RecipeRadar_GetSafeItemInfo(recipe.ID)

   if (recipe_name == RRS("Uncached Recipe")) then

      -- uncached recipes have a special tooltip and warning
      -- I'm just coloring 'Warning:' red here for max contrast
      local warn = RRS("Warning: if your server has")
      local colon = string.find(warn, ":")
      local warning =
            RecipeRadar_ColorToCode(RecipeRadar_Colors.TooltipWarning) ..
            string.sub(warn, 1, colon) .. FONT_COLOR_CODE_CLOSE ..
            string.sub(warn, colon + 1)

      -- the layout of the tooltip, with colors for each line
      local tooltip = {
            { text = RRS("Uncached Recipe"), color = RecipeRadar_Colors.UncachedRecipe },
            { text = "", color = nil },
            { text = RRS("You may mouse over the [?]"), color = RecipeRadar_Colors.TooltipHeading },
            { text = RRS("icon to lookup this recipe."), color = RecipeRadar_Colors.TooltipHeading },
            { text = "", color = nil },
            { text = warning, color = RecipeRadar_Colors.TooltipHeading },
            { text = RRS("not yet seen this item, you"), color = RecipeRadar_Colors.TooltipHeading },
            { text = RRS("will be disconnected!"), color = RecipeRadar_Colors.TooltipHeading }, }
      for _, line in pairs(tooltip) do
         RecipeRadar_AvailabilityTooltip_AddLine(line.text, line.color)
      end
      return

   end

   local avail, prosp, known, line_info = { }, { }, { }, { }
   local num_avail, num_prosp, num_known = 0, 0, 0

   -- recipe labels are not saved in the skill database
   recipe_name = RecipeRadar_TrimRecipeLabel(recipe_name)

   -- iterate through each character on this realm
   for player, player_info in pairs(RecipeRadar_SkillDB[GetRealmName()]) do

      -- do the rest only if the character has the profession
      if (player_info.Professions[recipe.Type] and
            player_info.Professions[recipe.Type].Rank > 0 and
            RecipeRadar_SkillDB_HasMisc(player, recipe)) then

         -- get player's rank
         local rank = player_info.Professions[recipe.Type].Rank
         
         -- get player's relevant faction standing, if any
         local rep = nil
         if (recipe.Faction and player_info.Factions[recipe.Faction]) then
            rep = getglobal("FACTION_STANDING_LABEL" ..
                  player_info.Factions[recipe.Faction])
         end
         
         -- get player's specialty, if any
         local spec = nil
         if (recipe.Specialty) then
            spec = player_info.Professions[recipe.Type].Specialty
         end

         -- now test all of the possible recipe statuses
         if (RecipeRadar_Availability_IsAvailable(player, recipe)) then
            num_avail = num_avail + 1
            avail[num_avail] = {
                  Name = player, Rank = rank, Rep = rep, Spec = spec }

         elseif (RecipeRadar_Availability_IsProspect(player, recipe)) then
            num_prosp = num_prosp + 1
            prosp[num_prosp] = {
                  Name = player, Rank = rank, Rep = rep, Spec = spec }

         elseif (RecipeRadar_Availability_IsKnown(player, recipe)) then
            num_known = num_known + 1
            known[num_known] = {
                  Name = player, Rank = rank, Rep = rep, Spec = spec }

         else
            local bleh = 0
            if (knows) then bleh = 1 end
            RecipeRadar_Print("ASSERT FAILED: " .. player .. " - " ..
                  recipe.Type .. ", " .. recipe_name .. ", " .. recipe.Skill ..
                  " (have " .. rank .. ") knows = " .. bleh)
         end
         
      end

   end

   -- populate the "available now" entries
   if (num_avail > 0) then

      RecipeRadar_AvailabilityTooltip_AddLine(
            RecipeRadar_Availabilities["LearnableByPlayer"].Tooltip.Heading,
            RecipeRadar_Colors.TooltipHeading)
      table.sort(avail, RecipeRadar_Availability_NameSort)

      for _, line in pairs(avail) do

         local color = RecipeRadar_Colors.LearnableByAlt
         if (line.Name == UnitName("player")) then
            color = RecipeRadar_Colors.LearnableByPlayer
         end

         local rank_suffix = line.Rank
         if (line.Rep) then  -- reputation is relevant to this line
            rank_suffix = rank_suffix .. " - " .. line.Rep
         end
         rank_suffix = format(TEXT(PARENS_TEMPLATE), rank_suffix)

         RecipeRadar_AvailabilityTooltip_AddLine(
               "   " .. line.Name .. " " .. rank_suffix, color)

      end

   end

   -- populate the "available soon" entries
   if (num_prosp > 0) then

      if (num_avail > 0) then
         RecipeRadar_AvailabilityTooltip_AddLine()
      end
      RecipeRadar_AvailabilityTooltip_AddLine(
            RecipeRadar_Availabilities["ProspectForPlayer"].Tooltip.Heading,
            RecipeRadar_Colors.TooltipHeading)
      table.sort(prosp, RecipeRadar_Availability_NameSort)

      for _, line in pairs(prosp) do

         local color = RecipeRadar_Colors.ProspectForAlt
         if (line.Name == UnitName("player")) then
            color = RecipeRadar_Colors.ProspectForPlayer
         end

         local factors = RecipeRadar_Availability_GetLimitingFactors(
               line.Name, recipe)

         local rank = string.format(RRS("%d of %d"), line.Rank, recipe.Skill)
         if (factors["rank"]) then
            rank = RecipeRadar_HighlightString(rank, color)
         end
         local suffix = rank  -- always include rank in the tooltip

         if (line.Rep) then
            local rep = line.Rep
            if (factors["rep"]) then
               rep = RecipeRadar_HighlightString(rep, color)
            end
            suffix = suffix .. " - " .. rep
         end
         
         if (line.Spec) then
            local spec = line.Spec
            if (factors["spec"]) then
               spec = RecipeRadar_HighlightString(spec, color)
            end
            suffix = suffix .. " - " .. spec
         end

         suffix = format(TEXT(PARENS_TEMPLATE), suffix)
         RecipeRadar_AvailabilityTooltip_AddLine(
               "   " .. line.Name .. " " .. suffix, color)
               
      end

   end

   -- populate the "already known" entries
   if (num_known > 0) then

      if (num_prosp > 0 or num_avail > 0) then
         RecipeRadar_AvailabilityTooltip_AddLine()
      end
      RecipeRadar_AvailabilityTooltip_AddLine(
            RecipeRadar_Availabilities["KnownByPlayer"].Tooltip.Heading,
            RecipeRadar_Colors.TooltipHeading)
      table.sort(known, RecipeRadar_Availability_NameSort)

      for _, line in pairs(known) do

         local color = RecipeRadar_Colors.KnownByAlt
         if (line.Name == UnitName("player")) then
            color = RecipeRadar_Colors.KnownByPlayer
         end

         local rank_suffix = line.Rank
         if (line.Rep) then  -- reputation is relevant to this line
            rank_suffix = rank_suffix .. " - " .. line.Rep
         end
         rank_suffix = format(TEXT(PARENS_TEMPLATE), rank_suffix)

         RecipeRadar_AvailabilityTooltip_AddLine(
               "   " .. line.Name .. " " .. rank_suffix, color)

      end

   end

end

-- Boolean test for availability used by CreateTooltip(), above
function RecipeRadar_Availability_NameSort(line1, line2)

   return line1.Name < line2.Name

end

-- Returns a set of booleans from which one can derive the availability
-- of any recipe relative to any player.
function RecipeRadar_Availability_GetBooleans(player, recipe)

   local info = RecipeRadar_SkillDB_GetSafePlayerDB(player)

   -- if the player doesn't even have the profession, just return
   if (not info.Professions[recipe.Type]) then return false end

   local recipe_name = RecipeRadar_GetSafeItemInfo(recipe.ID)
   recipe_name = RecipeRadar_TrimRecipeLabel(recipe_name)

   if (recipe_name == RRS("Uncached Recipe")) then
      return nil
   end

   -- otherwise, get the other relevant booleans
   local has_rec = info.Professions[recipe.Type].Recipes[recipe_name]
   local has_rank = info.Professions[recipe.Type].Rank >= recipe.Skill
   local has_rep = RecipeRadar_SkillDB_HasRep(player, recipe)
   local has_spec = RecipeRadar_SkillDB_HasSpec(player, recipe)
   local has_misc = RecipeRadar_SkillDB_HasMisc(player, recipe)

   return true, has_rec, has_rank, has_rep, has_spec, has_misc

end

-- Boolean test for availability used by CreateTooltip() and GetKey()
function RecipeRadar_Availability_IsAvailable(player, recipe)

   local has_prof, has_rec, has_rank, has_rep, has_spec, has_misc =
         RecipeRadar_Availability_GetBooleans(player, recipe)

   return (has_prof and not has_rec and has_rank and has_rep
         and has_spec and has_misc)

end

-- Boolean test for availability used by CreateTooltip() and GetKey()
function RecipeRadar_Availability_IsProspect(player, recipe)

   local has_prof, has_rec, has_rank, has_rep, has_spec, has_misc =
         RecipeRadar_Availability_GetBooleans(player, recipe)
         
   return (has_prof and not has_rec and
         (not has_rank or not has_rep) and has_spec and has_misc)

end

-- Boolean test for availability used by CreateTooltip() and GetKey()
function RecipeRadar_Availability_IsKnown(player, recipe)

   local has_prof, has_rec, has_rank, has_rep, has_spec, has_misc =
         RecipeRadar_Availability_GetBooleans(player, recipe)

   return (has_prof and has_rec)

end

-- Returns a table of strings that specify why the given recipe is not
-- available to the given player.
function RecipeRadar_Availability_GetLimitingFactors(player, recipe)

   local has_prof, has_rec, has_rank, has_rep, has_spec, has_misc =
         RecipeRadar_Availability_GetBooleans(player, recipe)
   local factors = { }
         
   if (not has_prof) then factors["prof"] = 1 end
   if (not has_rec) then factors["rec"] = 1 end
   if (not has_rank) then factors["rank"] = 1 end
   if (not has_rep) then factors["rep"] = 1 end
   if (not has_spec) then factors["spec"] = 1 end
   if (not has_misc) then factors["misc"] = 1 end

   return factors

end

-----------------------------------------------------------------------------
-- Availability testing for filtering and dropdowns
-----------------------------------------------------------------------------

-- This function tests whether the recipe is learnable either by
-- the player or an alt if realm-wide availability is on.
function RecipeRadar_Availability_IsLearnable(recipe, set_filter)

   local name = RecipeRadar_GetSafeItemInfo(recipe.ID)

   local avail = RecipeRadar_Availability_GetKey(recipe)
   if (not avail) then return false end
   
   if (set_filter) then
      recipe.IsFilteredByAvailability =
            RecipeRadar_IsAvailabilityFiltered(avail)
   end
   
   if (RecipeRadar_Options.RealmAvailability) then
      return avail == "LearnableByPlayer" or avail == "LearnableByAlt"
   else
      return avail == "LearnableByPlayer"
   end

end

-- Wrapper for the above that makes its functionality clearer.
function RecipeRadar_Availability_SetAvailabilityFilter(recipe)

   RecipeRadar_Availability_IsLearnable(recipe, true)

end

-- Generic function returns the key to the RecipeRadar_Availabilities
-- table so the caller can get whatever related info she needs.
function RecipeRadar_Availability_GetKey(recipe)

   local player = UnitName("player")

   if (RecipeRadar_Availability_IsAvailable(player, recipe)) then
      return "LearnableByPlayer"

   elseif (RecipeRadar_Options.RealmAvailability and
         RecipeRadar_Availability_IsSatisfiedByAlt(recipe,
         RecipeRadar_Availability_IsAvailable)) then
      return "LearnableByAlt"

   elseif (RecipeRadar_Availability_IsProspect(player, recipe)) then
      return "ProspectForPlayer"

   elseif (RecipeRadar_Options.RealmAvailability and
         RecipeRadar_Availability_IsSatisfiedByAlt(recipe,
         RecipeRadar_Availability_IsProspect)) then
      return "ProspectForAlt"

   elseif (RecipeRadar_Availability_IsKnown(player, recipe)) then
      return "KnownByPlayer"

   elseif (RecipeRadar_Options.RealmAvailability and
         RecipeRadar_Availability_IsSatisfiedByAlt(recipe,
         RecipeRadar_Availability_IsKnown)) then
      return "KnownByAlt"

   else
      return "NotApplicable"
   end

end

-- Boolean test for availability used by GetKey(), above.
function RecipeRadar_Availability_IsSatisfiedByAlt(recipe, sat_func)

   for player, player_info in pairs(RecipeRadar_SkillDB[GetRealmName()]) do

      if (player_info.Team ~= RecipeRadar_GetOpposingFaction("player") and
            player ~= UnitName("player") and
            sat_func(player, recipe)) then
         return true
      end
   
   end
   return false

end
