
-- RecipeRadar.lua: main event code and general utility functions
-- $Id: RecipeRadar.lua 1060 2008-11-16 04:27:27Z jnmiller $

function RecipeRadar_OnLoad()

   tinsert(UISpecialFrames, "RecipeRadarFrame")
   UIPanelWindows["RecipeRadarFrame"] = nil

   SLASH_RecipeRadar1 = "/rrdr"
   SLASH_RecipeRadar2 = "/reciperadar"
   SlashCmdList["RecipeRadar"] = RecipeRadar_SlashHandler

   this:RegisterEvent("VARIABLES_LOADED")
   this:RegisterEvent("ZONE_CHANGED_NEW_AREA")
   this:RegisterEvent("WORLD_MAP_UPDATE")
   this:RegisterEvent("CRAFT_SHOW")
   this:RegisterEvent("TRADE_SKILL_SHOW")
   this:RegisterEvent("CHAT_MSG_SKILL")
   this:RegisterEvent("CHAT_MSG_SYSTEM")
   this:RegisterEvent("PLAYER_ENTERING_WORLD")
--   this:RegisterEvent("ADDON_LOADED")

   -- RecipeRadar_Print("Recipe Radar loaded!")

end
--[[
do
-- Options Frame
	-- adds RecipeRadar config to default UI Interface Options
	local temp
	-- Create the RecipeRadar Options frame
	RecipeRadar_OptionsFrame = CreateFrame("Frame", "RecipeRadar_OptionsFrame", UIParent)
	RecipeRadar_OptionsFrame:Hide()
	RecipeRadar_OptionsFrame:SetWidth(300)
	RecipeRadar_OptionsFrame:SetHeight(410)
	RecipeRadar_OptionsFrame:SetFrameStrata("DIALOG")

	-- Title text
	temp = RecipeRadar_OptionsFrame:CreateFontString(nil, "ARTWORK", "GameFontNormalLarge")
	temp:SetPoint("TOPLEFT", 16, -16)
	temp:SetText("RecipeRadar " .. RECIPERADAR_VERSION)

	-- Check availability for alts checkbox
	CreateFrame("CheckButton", "RecipeRadar_OptionsFrame_Availability", RecipeRadar_OptionsFrame, "InterfaceOptionsCheckButtonTemplate")
	RecipeRadar_OptionsFrame_Availability:SetPoint("TOPLEFT", 16, -35)
	RecipeRadar_OptionsFrame_Availability:SetHitRectInsets(0, -300, 0, 0)
	RecipeRadar_OptionsFrame_AvailabilityText:SetText(RRS("Check Availability for Alts"))
	RecipeRadar_OptionsFrame_Availability:SetChecked(RecipeRadar_Options.RealmAvailability)
	RecipeRadar_OptionsFrame_Availability:SetScript("OnClick", RecipeRadar_Availability_Toggle)

	-- Minimap Button checkbox
	CreateFrame("CheckButton", "RecipeRadar_OptionsFrame_MinimapButton", RecipeRadar_OptionsFrame, "InterfaceOptionsCheckButtonTemplate")
	RecipeRadar_OptionsFrame_MinimapButton:SetPoint("TOPLEFT", 16, -57)
	RecipeRadar_OptionsFrame_MinimapButton:SetHitRectInsets(0, -300, 0, 0)
	RecipeRadar_OptionsFrame_MinimapButtonText:SetText(RRS("Show Minimap Button"))
	RecipeRadar_OptionsFrame_MinimapButton:SetChecked(RecipeRadar_Options.ShowMinimapButton)
	RecipeRadar_OptionsFrame_MinimapButton:SetScript("OnClick", RecipeRadar_MinimapButton_Toggle)

	-- Minimap Button Radius slider
	CreateFrame("Slider", "RecipeRadar_MinimapButtonPositionSlider", RecipeRadar_OptionsFrame, "OptionsSliderTemplate")
	RecipeRadar_MinimapButtonPositionSlider:SetWidth(335)
	RecipeRadar_MinimapButtonPositionSlider:SetHeight(16)
	RecipeRadar_MinimapButtonPositionSlider:SetPoint("TOPLEFT", 25, -90)
	RecipeRadar_MinimapButtonPositionSliderText:SetText(RRS("Minimap Button Position"))
	RecipeRadar_MinimapButtonPositionSliderLow:SetText("0")
	RecipeRadar_MinimapButtonPositionSliderHigh:SetText("360")
	RecipeRadar_MinimapButtonPositionSlider:SetMinMaxValues(0, 360)
	RecipeRadar_MinimapButtonPositionSlider:SetValueStep(1)
	RecipeRadar_MinimapButtonPositionSlider:SetScript("OnValueChanged", function(self, value)
		RecipeRadar_MinimapButtonPositionSliderText:SetText(RRS("Minimap Button Position").." "..value)
		RecipeRadar_Options.MinimapButtonPosition = value
		RecipeRadar_MinimapButton_UpdatePosition()
	end)

	-- Auto-select region checkbox
	CreateFrame("CheckButton", "RecipeRadar_OptionsFrame_AutoSelectRegion", RecipeRadar_OptionsFrame, "InterfaceOptionsCheckButtonTemplate")
	RecipeRadar_OptionsFrame_AutoSelectRegion:SetPoint("TOPLEFT", 16, -123)
	RecipeRadar_OptionsFrame_AutoSelectRegion:SetHitRectInsets(0, -300, 0, 0)
	RecipeRadar_OptionsFrame_AutoSelectRegionText:SetText(RRS("Auto-select Current Region"))
	RecipeRadar_OptionsFrame_AutoSelectRegion:SetChecked(RecipeRadar_Options.AutoSelectRegion)
	RecipeRadar_OptionsFrame_AutoSelectRegion:SetScript("OnClick", function(self)
		if RecipeRadar_Options.AutoSelectRegion then
			PlaySound("igMainMenuOptionCheckBoxOff")
			RecipeRadar_Options.AutoSelectRegion = false
			UIDropDownMenu_EnableDropDown(RecipeRadar_Continent_DropDown)
--			UIDropDownMenu_EnableDropDown(RecipeRadar_Region_DropDown)
		else
			PlaySound("igMainMenuOptionCheckBoxOn")
			-- auto-select the current region
			RecipeRadar_NewRegion(GetRealZoneText())
			UIDropDownMenu_ClearAll(RecipeRadar_Continent_DropDown)
			UIDropDownMenu_Initialize(RecipeRadar_Continent_DropDown, RecipeRadar_Continent_DropDown_Init)
--			UIDropDownMenu_Initialize(RecipeRadar_Region_DropDown, RecipeRadar_Region_DropDown_Init)

			RecipeRadar_Options.AutoSelectRegion = true
			UIDropDownMenu_DisableDropDown(RecipeRadar_Continent_DropDown)
--			UIDropDownMenu_DisableDropDown(RecipeRadar_Region_DropDown)
		end
		self:SetChecked(RecipeRadar_Options.AutoSelectRegion)
	end)

	-- Region selection dropdowns
	CreateFrame("Frame", "RecipeRadar_Continent_DropDown", RecipeRadar_OptionsFrame, "UIDropDownMenuTemplate")
	RecipeRadar_Continent_DropDown:SetPoint("TOPLEFT", 25, -157)
	RecipeRadar_Continent_DropDown:SetHitRectInsets(16, 16, 0, 0)
	UIDropDownMenu_SetWidth(RecipeRadar_Continent_DropDown, 300)
	UIDropDownMenu_EnableDropDown(RecipeRadar_Continent_DropDown)
--	RecipeRadar_Continent_DropDown:SetScript("OnClick", function(self)
--		UIDropDownMenu_SetSelectedValue(RecipeRadar_Continent_DropDown, this.value)
--		UIDropDownMenu_Initialize(RecipeRadar_Region_DropDown, RecipeRadar_Region_DropDown_Init)
--	end)
--	RecipeRadar_Continent_DropDown:CreateFontString("RecipeRadar_Continent_DropDownLabel", "BACKGROUND", "GameFontNormalSmall")
--	RecipeRadar_Continent_DropDownLabel:SetPoint("BOTTOMLEFT", RecipeRadar_Continent_DropDown, "TOPLEFT", 21, 1)
--	RecipeRadar_Continent_DropDownLabel:SetText(TEXT)

	-- Add to Blizzard Interface Options
	RecipeRadar_OptionsFrame.name = "RecipeRadar " .. RECIPERADAR_VERSION
	InterfaceOptions_AddCategory(RecipeRadar_OptionsFrame)

end

function RecipeRadar_Continent_DropDown_Init()
   for index, name in pairs(RecipeRadar_Continents) do
      local item = { }
      if ((not RecipeRadar_Continent_DropDown.selectedValue and
            index == RecipeRadar_Options.CurrentContinent) or
            index == RecipeRadar_Continent_DropDown.selectedValue) then
         item.checked = 1
      end
      item.text = name
      item.value = index
      item.func = RecipeRadar_ContinentDropDown_OnClick
      UIDropDownMenu_AddButton(item)
   end
   
   if (not RecipeRadar_Continent_DropDown.selectedValue) then
      UIDropDownMenu_SetSelectedValue(RecipeRadar_Continent_DropDown,
            RecipeRadar_Options.CurrentContinent)
   end

end

function RecipeRadar_Options_Init()
	RecipeRadar_Continent_DropDown.initialize = RecipeRadar_Continent_DropDown_Init
--	UIDropDownMenu_SetSelectedValue(RecipeRadar_Continent_DropDown, RecipeRadar_Options.CurrentContinent)
end

function RecipeRadar_Options_OnShow()
	RecipeRadar_OptionsFrame_Availability:SetChecked(RecipeRadar_Options.RealmAvailability)
	RecipeRadar_OptionsFrame_MinimapButton:SetChecked(RecipeRadar_Options.ShowMinimapButton)
	RecipeRadar_MinimapButtonPositionSlider:SetValue(RecipeRadar_Options.MinimapButtonPosition)
	RecipeRadar_OptionsFrame_AutoSelectRegion:SetChecked(RecipeRadar_Options.AutoSelectRegion)
--	RecipeRadar_Continent_DropDownText:SetText(RecipeRadar_Options.CurrentContinent)
--	RecipeRadar_Continent_DropDown:SetSelectedValue(RecipeRadar_Options.CurrentContinent)
	if RecipeRadar_Options.AutoSelectRegion then
		UIDropDownMenu_DisableDropDown(RecipeRadar_Continent_DropDown)
	else
		UIDropDownMenu_EnableDropDown(RecipeRadar_Continent_DropDown)
	end
	UIDropDownMenu_SetSelectedValue(RecipeRadar_Continent_DropDown, RecipeRadar_Options.CurrentContinent)
end

RecipeRadar_OptionsFrame:RegisterEvent("VARIABLES_LOADED")
RecipeRadar_OptionsFrame:SetScript("OnEvent", RecipeRadar_Options_Init)
RecipeRadar_OptionsFrame:SetScript("OnShow", RecipeRadar_Options_OnShow)
]]

function RecipeRadar_OnEvent()

   if (event == "VARIABLES_LOADED") then

      RecipeRadar_Globals_Init()
      RecipeRadar_MinimapButton_Init()
      RecipeRadar_Availability_Init()
      RecipeRadar_Maps_Init()
      RecipeRadar_SkillDB_Init()
      RecipeRadar_MainFrame_Init()
      RecipeRadar_DropDowns_Init()
      RecipeRadar_SelectFirstRecipe()

   elseif (event == "ZONE_CHANGED_NEW_AREA") then

      if (RecipeRadar_RegionHasLearnableRecipe(GetRealZoneText())) then
         RecipeRadar_MinimapButton_Flash()
      end
      RecipeRadar_Minimap_OnNewRegion()
      if (RecipeRadar_Options.AutoSelectRegion) then
         RecipeRadar_NewRegion(GetRealZoneText())
      end

   elseif (event == "ADDON_LOADED" and arg1 == "RecipeRadar") then

      --

   elseif (event == "PLAYER_ENTERING_WORLD") then

      if (RecipeRadar_Filters.Teams["Horde"] == nil) then
         -- this should be the first run of the addon, so set team filters
         RecipeRadar_Filters.Teams[UnitFactionGroup("player")] = false
         RecipeRadar_Filters.Teams
               [RecipeRadar_GetOpposingFaction("player")] = true
      end

   elseif (event == "WORLD_MAP_UPDATE") then

      RecipeRadar_Worldmap_UpdateButtons()

   elseif (event == "CRAFT_SHOW") then

      RecipeRadar_SkillDB_Refresh("craft")

   elseif (event == "TRADE_SKILL_SHOW") then

      RecipeRadar_SkillDB_Refresh("trade")

   elseif (event == "CHAT_MSG_SKILL") then

      -- arg1 = eg. "Your skill in Cooking has increased to 221."
      RecipeRadar_ParseChatMessage(arg1)

   elseif (event == "CHAT_MSG_SYSTEM") then

      -- arg1 = eg. "You have learned how to create a new item: Bristle Whisker Catfish."
      RecipeRadar_ParseChatMessage(arg1)

   end

end

function RecipeRadar_OnShow()

   --PlaySound("igCharacterInfoOpen")

end

function RecipeRadar_OnHide()

   PlaySound("igCharacterInfoClose")
   RecipeRadarFrame:StopMovingOrSizing()

end

function RecipeRadar_SlashHandler(msg)

   local _, _, cmd, arg1, arg2, arg3 =
         string.find(msg, "(%w+)%s*(%w*)%s*(%w*)%s*(%w*)")

   if (cmd == "debug") then

      if (arg1 == "vendor") then
         RecipeRadar_Options.PositionDebug = true
      end

      if (arg1 == "skills") then
         RecipeRadar_SkillDB_Print(arg2, arg3)
      end

      if (arg1 == "clear") then
         RecipeRadar_SkillDB_Clear(arg2)
      end

      return

   end

   if (cmd == "radar") then
   
      local region = RecipeRadar_RegionData[GetRealZoneText()]
      if (not region) then return end

      for _, vendor in pairs(region.Vendors) do

         RecipeRadar_Print(RecipeRadar_GetColoredTeamString(
               vendor.Team, vendor.Name) .. " (" ..
               string.format("%d", vendor.Coordinates[1].x * 100) .. ", " ..
               string.format("%d", vendor.Coordinates[1].y * 100) .. "):")

         table.sort(vendor.Recipes, RecipeRadar_CompareSkills)
         for k, v in pairs(vendor.Recipes) do
            RecipeRadar_PrintRecipe(k, v)
         end

      end

      return

   end

   RecipeRadar_Toggle()
   
end

-- Function to be called on selecting a new region to initialize the
-- main frame and any active filters.
function RecipeRadar_NewRegion(region)

   if (not region or region == "") then return end

   RecipeRadar_Options.CurrentRegion = region
   RecipeRadar_Options.CurrentContinent = RecipeRadar_GetCurrentContinent()
   RecipeRadar_InitVendorFilters()
   RecipeRadar_SelectFirstRecipe()
   RecipeRadar_FrameUpdate()

end

-- Returns whether the given region contains at least one recipe that
-- is learnable by the player or (optionally) alts.
function RecipeRadar_RegionHasLearnableRecipe(region_name)

   local region_data = RecipeRadar_RegionData[region_name]
   if (not region_data) then return false end

   for _, vendor in pairs(region_data.Vendors) do
      if (not RecipeRadar_IsVendorFiltered(vendor)) then
         for _, recipe in pairs(vendor.Recipes) do
            if (RecipeRadar_Availability_IsLearnable(recipe)) then
               return not RecipeRadar_IsRecipeFiltered(recipe)
            end
         end
      end
   end

   return false

end

-- Given a table of recipes, returns the number of recipes that are
-- learnable by the player or (optionally) alts.
function RecipeRadar_CountLearnableRecipes(recipes)

   if (not recipes) then return 0 end

   local total = 0
   
   for _, recipe in pairs(recipes) do
      if (RecipeRadar_Availability_IsLearnable(recipe)) then
         total = total + 1
      end
   end

   return total

end

-- Returns the index of the current region's continent, or 0 if the
-- player is not in a known map zone (eg. she's in an instance).
function RecipeRadar_GetCurrentContinent()

   local region = RecipeRadar_RegionData[RecipeRadar_Options.CurrentRegion]

   -- this mess derives the current continent from the current region
   if (region) then
      return region.Continent
   else
      -- region is not in our database, so we search for it
      for cont_id, _ in pairs( { GetMapContinents() } ) do
         for _, zone in pairs( { GetMapZones(cont_id) } ) do
            if (RecipeRadar_Options.CurrentRegion == zone) then
               return cont_id
            end
         end
      end
      return 0
   end

end

-- Converts a format string to one that LUA can understand.  I've
-- shamelessly stolen this from Innerfire in order to (I hope) prevent
-- the combat chatlog parsing errors.
function RecipeRadar_ConvertFormat(fmt)

   -- remove non-English format params like "%1$s"
   local find = string.gsub(fmt, "%%%d%$", "%%")

   -- remove any special characters with escaped versions
   find = string.gsub(find, "([%^%$%(%)%.%[%]%*%+%-%?])", "%%%1")

   -- finally replace standard "%s" and "%d"
   find = string.gsub(find, "%%s", "(.+)")
   find = string.gsub(find, "%%d", "(%%d+)")
   return find

end

-- Parses the input for a couple of known message formats to gain information
-- about changes in the character's skills.
function RecipeRadar_ParseChatMessage(msg)
   
   -- messages of the form 'Your skill in %s has increased to %d.'
   local start, _, prof, rank =
         string.find(msg, RecipeRadar_ConvertFormat(ERR_SKILL_UP_SI))
   if (start) then
      RecipeRadar_SkillDB_SetRank(prof, rank)
   end

   -- messages of the form 'You have unlearned %s.'
   start, _, prof =
         string.find(msg, RecipeRadar_ConvertFormat(ERR_SPELL_UNLEARNED_S))
   if (prof) then
      RecipeRadar_SkillDB_GetPlayerProfessions()[prof] = nil
   end
   
end

-- Foreach function that prints a recipe link and its cost.
function RecipeRadar_PrintRecipe(index, recipe)

   RecipeRadar_Print("   " .. RecipeRadar_GetHyperlinkByID(recipe.ID) ..
         ", " .. RecipeRadar_GetGSCString(recipe.Cost))

end

-- Returns a string describing an item link from an item ID.
function RecipeRadar_GetHyperlinkByID(id)

   local name, link, color = RecipeRadar_GetSafeItemInfo(id)
   return RecipeRadar_GetHyperlinkByParts(name, link, color)

end

-- Returns a string describing an item link from link components.
function RecipeRadar_GetHyperlinkByParts(name, link, color)

   return string.format("%s|H%s|h[%s]|h|r", color, link, name)

end

-- If an item is not in your local cache, GetItemInfo() fails.  To work
-- around this, we just return 'uncached' along with the details we do know.
-- If an item is recognized by the client, we cache the name so that future
-- requests don't end up fully 'uncached'.  The final return value is a
-- boolean indicating whether it was a cached ID.
function RecipeRadar_GetSafeItemInfo(id)

   local name, link, rarity, _, _, _, _, _, _, texture = GetItemInfo(id)
   
   -- initialize our name cache if necessary
   if (not RecipeRadar_NameCache) then RecipeRadar_NameCache = { } end

   if (name == nil) then
   
      -- set returned name according to whether we've already seen the id
      if (RecipeRadar_NameCache[id]) then
         name = RecipeRadar_NameCache[id]
      else
         name = RRS("Uncached Recipe")
      end

      -- create our own tooltip for uncached items
      return name, "item:" .. id .. ":0:0:0:0:0:0:0",
            RecipeRadar_ColorToCode(RecipeRadar_Colors.UncachedRecipe),
            RecipeRadar_Colors.UncachedRecipe.r,
            RecipeRadar_Colors.UncachedRecipe.g,
            RecipeRadar_Colors.UncachedRecipe.b,
            GetItemIcon(id),
            false  -- boolean to indicate whether we got a real recipe

   else
      RecipeRadar_NameCache[id] = name  -- cache names
      local r, g, b, colorcode = GetItemQualityColor(rarity)
      local _, _, link = string.find(link, "^|%x+|H(.+)|h%[.+%]")
      return name, link, colorcode, r, g, b, texture, true
   end

end

-- From a region name, retrieve its index in the GetMapZones() table.
function RecipeRadar_RegionNameToID(region_name, continent)

   local index = 0
   
   for _, zone in pairs( { GetMapZones(continent) } ) do
      index = index + 1
      if (region_name == zone) then
         return index
      end   
   end

   RecipeRadar_Print("ASSERT FAILED: region '" .. region_name ..
         "' not found by GetMapZones()!  Bad translation?")
   return nil

end

-- Get gold, silver, copper from total copper.
function RecipeRadar_GetGSC(copper)

   if (copper == nil) then
      copper = 0
   end

   local g = math.floor(copper / 10000)
   local s = math.floor((copper - (g * 10000)) / 100)
   local c = math.floor(copper - (g * 10000) - (s * 100))

   return g, s, c

end

-- Format text by color for gold, silver and copper (from EnhTooltip).
function RecipeRadar_GetGSCString(copper)

   local TEXT_NONE = "0"
   local GSC_GOLD = "ffd100"
   local GSC_SILVER = "e6e6e6"
   local GSC_COPPER = "c8602c"
   local GSC_FMT = "|cff%s%d|r"
   local GSC_PART = ".|cff%s%02d|r"
   local GSC_NONE = "|cffa0a0a0" .. TEXT_NONE .. "|r"

   local g, s, c = RecipeRadar_GetGSC(copper)
   local gsc = ""

   if (g > 0) then
      gsc = string.format(GSC_FMT, GSC_GOLD, g)
      if (s > 0) then
         gsc = gsc .. string.format(GSC_PART, GSC_SILVER, s)
      end
      if exact then
         gsc = gsc .. string.format(GSC_PART,GSC_COPPER, c)
      end
   elseif (s > 0) then
      gsc = string.format(GSC_FMT, GSC_SILVER, s)
      if (c > 0) then
         gsc = gsc .. string.format(GSC_PART, GSC_COPPER, c)
      end
   elseif (c > 0) then
      gsc = gsc .. string.format(GSC_FMT, GSC_COPPER, c)
   else
      gsc = GSC_NONE
   end

   return gsc

end

-- Returns the name of the player's opposing faction.
-- These strings shouldn't need to be localized.
function RecipeRadar_GetOpposingFaction()

   if (UnitFactionGroup("player") == "Alliance") then
      return "Horde"
   elseif (UnitFactionGroup("player") == "Horde") then
      return "Alliance"
   end
   return "Unknown"

end

-- Formats a string in the color specified by the key 'team'.
function RecipeRadar_GetColoredTeamString(team, s)

   if (not RecipeRadar_Teams[team]) then return end
   return RecipeRadar_ColorToCode(RecipeRadar_Teams[team].Color) ..
         s .. FONT_COLOR_CODE_CLOSE

end

-- Returns whether the skill is an applicable profession.
function RecipeRadar_IsProfession(skill_name)

   for name, _ in pairs(RecipeRadar_Professions) do
      if (name == skill_name) then
         return true
      end
   end
   
   return false

end

-- Returns the profession under which a trade skill specialty belongs,
-- or false if skill_name is not a known specialty.
function RecipeRadar_IsProfessionSpecialty(skill_name)

   for spec, prof in pairs(RecipeRadar_Specialties) do
      if (spec == skill_name) then
         return prof
      end
   end

   return false

end

-- Returns whether the supplied faction is one that Recipe Radar cares about.
function RecipeRadar_IsKnownFaction(faction)

   for _, name in pairs(RecipeRadar_Factions) do
      if (name == faction) then
         return true
      end
   end

   return false

end

-- Returns whether the given vendor has at least one unfiltered recipe.
function RecipeRadar_VendorHasRecipes(vendor)

   for _, recipe in pairs(vendor.Recipes) do
      if (not RecipeRadar_IsRecipeFiltered(recipe)) then
         return true
      end
   end

   return false

end

-- Returns whether the given recipe has at least one unfiltered location.
function RecipeRadar_RecipeHasLocations(recipe)

   for _, location in pairs(recipe.Locations) do
      if (not location.IsFiltered) then
         return true
      end
   end

   return false

end

-- Returns a string describing the requirements to buy the given recipe
-- according to the recipe's Notes/Faction member, or nil.
function RecipeRadar_GetRecipeReqsString(recipe)

   if (recipe.Faction and recipe.Faction ~= "") then
      return recipe.Faction .. ", " ..
            getglobal("FACTION_STANDING_LABEL" .. recipe.Level)
   end
   if (recipe.Specialty and recipe.Specialty ~= "") then
      return recipe.Specialty
   end
   if (recipe.Notes and recipe.Notes ~= "") then
      return RecipeRadar_FormatRecipeNote(recipe.Notes)
   end
   return nil

end

-- If the supplied recipe has an item cost, this function returns the
-- number of items and the item's ID, respectively.
function RecipeRadar_GetSpecialRecipeCost(recipe)

   local note_type, note_value = RecipeRadar_ParseNote(recipe.Notes)
   
   if (note_type == "CostItem") then
      local cost = recipe.Cost
      if (string.len(cost) == 1) then
         cost = " " .. cost  -- right-justify single digits
      end
      return cost, note_value + 0
   end

end

-- Parses a recipe note of the form 'Type: Value' and returns both
-- the type and value, respectively.
function RecipeRadar_ParseNote(note)

   if (not note) then return nil end
   return string.match(note, "(%w+): (%w+)")

end

-- Formats the subtext that applies to a few vendors.  include_parens is
-- a boolean that specifies whether the string should be in parens.
function RecipeRadar_FormatRecipeNote(note)

   local note_type, note_value = RecipeRadar_ParseNote(note)
   
   if (note_type == "Class") then
      return format(RRS("%s Only"), RRS(note_value))
   end

end

-- Formats the subtext that applies to a few vendors.  include_parens is
-- a boolean that specifies whether the string should be in parens.
function RecipeRadar_FormatVendorNote(note, include_parens)

   local note_type = RecipeRadar_ParseNote(note)
   if (note_type) then note = note_type end
   
   if (include_parens) then
      return format(TEXT(PARENS_TEMPLATE), format(RRS("%s Vendor"), note))
   end
   return format(RRS("%s Vendor"), note)

end

-- Given a recipe name of the form 'Label: Name', returns 'Name'.  It
-- does this by checking for a ':' and looking ahead to the name.
-- Thanks to an SF.Net poster for verifying this with Unicode locales.
function RecipeRadar_TrimRecipeLabel(recipe_name)

   local label_end = string.find(recipe_name, ": ")
   if (label_end) then
      return string.sub(recipe_name, label_end + 2)
   else
      label_end = string.find(recipe_name, ":")
      if (label_end) then
         return string.sub(recipe_name, label_end + 1)
      end
   end
   return recipe_name

end

-- Sorts the vendors in a given region.
function RecipeRadar_SortVendors(region_data)

   if (region_data.IsVendorTableSorted) then
      return
   end

   table.sort(region_data.Vendors, RecipeRadar_CompareNames)
   region_data.IsVendorTableSorted = true

end

-- Sorts the recipes on a given vendor.
function RecipeRadar_SortRecipes(container)

   if (container.IsRecipeTableSorted) then
      return
   end

   table.sort(container.Recipes, RecipeRadar_CompareSkills)
   container.IsRecipeTableSorted = true

end

-- Sorts the locations on a given recipe.
function RecipeRadar_SortLocations(recipe_data)

   if (recipe_data.IsLocationsTableSorted) then
      return
   end

   table.sort(recipe_data.Locations, RecipeRadar_CompareRegions)
   recipe_data.IsRecipeTableSorted = true

end

-- Sort comparison function on required skill first, then the recipe's
-- ID (to insure a deterministic order).
function RecipeRadar_CompareSkills(recipe1, recipe2)

   if (recipe1.Skill == recipe2.Skill) then
      return recipe1.ID < recipe2.ID
   end
   return recipe1.Skill < recipe2.Skill
   
end

-- Sort comparison function on vendor names.
function RecipeRadar_CompareNames(vendor1, vendor2)

   return vendor1.Name < vendor2.Name

end

-- Sort comparison function on location names.
function RecipeRadar_CompareRegions(location1, location2)

   if (location1.Region == location2.Region) then
      return location1.Vendor < location2.Vendor
   end
   return location1.Region < location2.Region

end

-- Returns the result of the "<" operator on both major and minor portions.
function RecipeRadar_IsVersionLessThan(version1, version2)

   local major1, minor1 = string.match(version1, "(%d+)\.(%d+)")
   local major2, minor2 = string.match(version2, "(%d+)\.(%d+)")

   if (major1 ~= major2) then
      return major1+0 < major2+0
   end
   return minor1+0 < minor2+0
   
end

-- Returns a color code string converted from RGB decimals.
function RecipeRadar_ColorToCode(color)

   return string.format("|cff%02x%02x%02x",
         255 * color.r, 255 * color.g, 255 * color.b)

end

-- Returns the given string surrounded by the highlighted version of
-- base_color.
function RecipeRadar_HighlightString(s, base_color)

   local highlight = {
         r = math.min(1.0, base_color.r + RecipeRadar_Colors.Highlight),
         g = math.min(1.0, base_color.g + RecipeRadar_Colors.Highlight),
         b = math.min(1.0, base_color.b + RecipeRadar_Colors.Highlight), }
   return RecipeRadar_ColorToCode(highlight) .. s .. FONT_COLOR_CODE_CLOSE

end

-- Basic chat frame printing function.
function RecipeRadar_Print(msg, r, g, b)

   DEFAULT_CHAT_FRAME:AddMessage(msg,
         (r or RecipeRadar_Colors.MainFont.r),
         (g or RecipeRadar_Colors.MainFont.g),
         (b or RecipeRadar_Colors.MainFont.b))

end
