-- Public, versioned and read-only agronomy boundary owned by Realistic Crop Rotation.
-- The facade projects only detached scalar data. Consumers must never retain owner objects.

RCRAgronomyReadFacade = RCRAgronomyReadFacade or {}

local CONTRACT_VERSION = 1
local SCHEMA_VERSION = 1
local SOURCE_ID = "rcrAgronomy"
local REVISION_POLL_INTERVAL_MS = 2000
local state = {
    manager = nil,
    missionLoaded = false,
    ownerSynchronized = false,
    cacheByFarm = {}
}

local CAPABILITIES = {
    fieldIdentity = true,
    currentCrop = true,
    cropHistory = true,
    rotationPlan = true,
    coverCropPlan = true,
    growthAndHarvest = true,
    nitrogen = true,
    soilPh = true,
    lime = true,
    treatment = true,
    weeds = true,
    disease = true,
    ownerAdvice = true,
    rotationScore = false,
    moisture = false,
    salesQuality = false,
    priceFactor = false,
    nextCropForecast = false
}

local function positiveInteger(value)
    value = tonumber(value)
    if value == nil or value <= 0 or value ~= math.floor(value) then return nil end
    if FarmManager ~= nil and FarmManager.SPECTATOR_FARM_ID ~= nil
        and value == FarmManager.SPECTATOR_FARM_ID then
        return nil
    end
    return value
end

local function numberOrNil(value)
    value = tonumber(value)
    return value ~= nil and value == value and value or nil
end

local function stringOrNil(value)
    if type(value) ~= "string" then return nil end
    local trimmed = value:match("^%s*(.-)%s*$")
    return trimmed ~= "" and trimmed or nil
end

local function copyStringArray(source)
    local result = {}
    for _, value in ipairs(type(source) == "table" and source or {}) do
        value = stringOrNil(value)
        if value ~= nil then result[#result + 1] = value end
    end
    return result
end

local function copyHistory(source)
    local result = {}
    for index, entry in ipairs(type(source) == "table" and source or {}) do
        local cropName = stringOrNil(type(entry) == "table"
            and (entry.cropName or entry.crop) or nil)
        if cropName ~= nil then
            result[#result + 1] = {
                position=tonumber(entry.position) or index,
                cropName=cropName,
                displayName=stringOrNil(entry.displayName)
            }
        end
    end
    return result
end

local function copyCapabilities(source)
    source = type(source) == "table" and source or {}
    local result = {}
    for key in pairs(CAPABILITIES) do result[key] = source[key] == true end
    return result
end

local function copyField(source)
    local result = {
        schemaVersion=SCHEMA_VERSION,
        sourceId=SOURCE_ID,
        available=source.available == true,
        availabilityState=source.availabilityState,
        farmId=source.farmId,
        ownerFarmId=source.ownerFarmId,
        farmlandId=source.farmlandId,
        fieldId=source.fieldId,
        stableFieldId=source.stableFieldId,
        identityResolution=source.identityResolution,
        displayName=source.displayName,
        areaHa=source.areaHa,
        fruitTypeIndex=source.fruitTypeIndex,
        fruitTypeName=source.fruitTypeName,
        cropDisplayName=source.cropDisplayName,
        cropFamily=source.cropFamily,
        growthState=source.growthState,
        growthStateLabel=source.growthStateLabel,
        harvestState=source.harvestState,
        harvested=source.harvested == true,
        withered=source.withered == true,
        harvestReady=source.harvestReady == true,
        latestActiveCrop=source.latestActiveCrop,
        rotationStatus=source.rotationStatus,
        rotationScore=source.rotationScore,
        rotationFactor=source.rotationFactor,
        rotationAdviceAvailable=source.rotationAdviceAvailable == true,
        rotationActionRequired=source.rotationActionRequired == true,
        rotationAdviceText=source.rotationAdviceText,
        nitrogenLevel=source.nitrogenLevel,
        nitrogenTarget=source.nitrogenTarget,
        nitrogenNeed=source.nitrogenNeed,
        ph=source.ph,
        phTarget=source.phTarget,
        phMin=source.phMin,
        phMax=source.phMax,
        limeStatus=source.limeStatus,
        fieldStatus=source.fieldStatus,
        fieldStatusKind=source.fieldStatusKind,
        fieldStatusIndex=source.fieldStatusIndex,
        treatmentState=source.treatmentState,
        treatmentLabel=source.treatmentLabel,
        treatmentReason=source.treatmentReason,
        treatmentPeriodsRemaining=source.treatmentPeriodsRemaining,
        weedState=source.weedState,
        weedLabel=source.weedLabel,
        diseaseState=source.diseaseState,
        diseaseGroup=source.diseaseGroup,
        diseaseLabel=source.diseaseLabel,
        diseaseSeverity=source.diseaseSeverity,
        soilCondition=source.soilCondition,
        cropCondition=source.cropCondition,
        sourceRevision=source.sourceRevision,
        cropHistory=copyHistory(source.cropHistory),
        rotationPlan=copyStringArray(source.rotationPlan),
        rotationPlanDisplayNames=copyStringArray(source.rotationPlanDisplayNames),
        coverCropPlan=copyStringArray(source.coverCropPlan),
        coverCropPlanDisplayNames=copyStringArray(source.coverCropPlanDisplayNames),
        ownerReasonCodes=copyStringArray(source.ownerReasonCodes),
        limitations=copyStringArray(source.limitations),
        capabilities=copyCapabilities(source.capabilities or CAPABILITIES)
    }
    return result
end

local function getManager()
    local manager = state.manager
    if manager == nil and g_currentMission ~= nil then
        manager = g_currentMission.realisticCropRotationManager
    end
    return manager
end

local function ownedFarmlandIds(farmId)
    local result, seen = {}, {}
    if g_farmlandManager == nil then return result end
    local source = nil
    if type(g_farmlandManager.getOwnedFarmlandIdsByFarmId) == "function" then
        source = g_farmlandManager:getOwnedFarmlandIdsByFarmId(farmId)
    end
    if type(source) == "table" then
        for key, value in pairs(source) do
            local id = positiveInteger(type(value) == "number" and value or key)
            if id ~= nil and not seen[id] then seen[id] = true; result[#result + 1] = id end
        end
    elseif type(g_farmlandManager.getFarmlands) == "function" then
        for key, farmland in pairs(g_farmlandManager:getFarmlands() or {}) do
            local owner = farmland ~= nil and positiveInteger(farmland.farmId or farmland.ownerFarmId) or nil
            if owner == farmId then
                local id = positiveInteger(farmland.id or key)
                if id ~= nil and not seen[id] then seen[id] = true; result[#result + 1] = id end
            end
        end
    end
    table.sort(result)
    return result
end

local function farmlandById(farmlandId)
    if g_farmlandManager == nil then return nil end
    if type(g_farmlandManager.getFarmlandById) == "function" then
        return g_farmlandManager:getFarmlandById(farmlandId)
    end
    local all = type(g_farmlandManager.getFarmlands) == "function"
        and g_farmlandManager:getFarmlands() or g_farmlandManager.farmlands
    return type(all) == "table" and (all[farmlandId] or all[tostring(farmlandId)]) or nil
end

local function fieldIdentity(manager, farmId, farmlandId)
    local field = type(manager.getFieldByFarmlandId) == "function"
        and manager:getFieldByFarmlandId(farmlandId) or nil
    if field == nil then
        return nil, string.format("farm:%d|farmland:%d|field:none",
            farmId, farmlandId), "farmlandId", nil
    end
    local fieldId = positiveInteger(field.fieldId or field.id)
    if fieldId == nil and type(field.getId) == "function" then
        local ok, value = pcall(field.getId, field)
        if ok then fieldId = positiveInteger(value) end
    end
    return fieldId, string.format("farm:%d|farmland:%d|field:%s",
        farmId, farmlandId, tostring(fieldId or "none")),
        fieldId ~= nil and "fieldId" or "farmlandId", field
end

local function areaHa(farmland, field)
    local value = farmland ~= nil and numberOrNil(
        farmland.totalFieldArea or farmland.fieldArea or farmland.areaHa) or nil
    if value == nil and field ~= nil then
        value = numberOrNil(field.fieldArea or field.areaHa or field.area)
    end
    if value ~= nil and value > 1000 then value = value / 10000 end
    return value
end

local function cropDisplayData(cropName, fruitTypeIndex)
    local fruit = nil
    if g_fruitTypeManager ~= nil then
        if positiveInteger(fruitTypeIndex) ~= nil
            and type(g_fruitTypeManager.getFruitTypeByIndex) == "function" then
            fruit = g_fruitTypeManager:getFruitTypeByIndex(fruitTypeIndex)
        elseif cropName ~= nil and type(g_fruitTypeManager.getFruitTypeByName) == "function" then
            fruit = g_fruitTypeManager:getFruitTypeByName(cropName)
        end
    end
    -- Use the exact owner presentation contract used by the Agronomy frame.
    -- fruit.title is an internal name for several GIANTS/mod crops and must
    -- never win over the localized fill-type title.
    local displayName = RealisticCropRotationPresentationService ~= nil
        and type(RealisticCropRotationPresentationService.getCropDisplayName) == "function"
        and stringOrNil(RealisticCropRotationPresentationService.getCropDisplayName(
            cropName, g_i18n)) or nil
    return fruit, displayName or cropName
end

local function harvestFacts(fruit, growthState)
    growthState = numberOrNil(growthState)
    if fruit == nil or growthState == nil then
        return "unknown", false, false, false, "unknown"
    end
    local withered = numberOrNil(fruit.witheredState) == growthState
    local harvested = numberOrNil(fruit.cutState) == growthState
    local minimum = numberOrNil(fruit.minHarvestingGrowthState)
    local maximum = numberOrNil(fruit.maxHarvestingGrowthState)
    local ready = not withered and not harvested and minimum ~= nil and maximum ~= nil
        and growthState >= minimum and growthState <= maximum
    local state = withered and "withered" or (harvested and "harvested"
        or (ready and "ready" or "notReady"))
    return state, harvested, withered, ready, state
end

local function localizedCropNames(source)
    local result = {}
    for _, cropName in ipairs(type(source) == "table" and source or {}) do
        local _, displayName = cropDisplayData(cropName, nil)
        result[#result + 1] = displayName
    end
    return result
end

local function fingerprint(fields)
    local hash = 5381
    local precisionByKey = {
        areaHa=3,
        rotationScore=2,
        rotationFactor=3,
        nitrogenLevel=0,
        nitrogenTarget=0,
        nitrogenNeed=0,
        ph=1,
        phTarget=1,
        phMin=1,
        phMax=1,
        diseaseSeverity=2
    }
    local function add(value)
        local text = tostring(value == nil and "<nil>" or value)
        for index = 1, #text do
            hash = (hash * 33 + string.byte(text, index)) % 2147483647
        end
        hash = (hash * 33 + 124) % 2147483647
    end
    for _, field in ipairs(fields) do
        for _, key in ipairs({"farmId","farmlandId","fieldId","stableFieldId","displayName","areaHa",
            "fruitTypeIndex","fruitTypeName","cropDisplayName","cropFamily","growthState",
            "growthStateLabel","harvestState","harvested","withered","harvestReady",
            "latestActiveCrop","rotationStatus","rotationScore","rotationFactor",
            "rotationAdviceAvailable","rotationActionRequired","rotationAdviceText",
            "nitrogenLevel","nitrogenTarget","nitrogenNeed","ph","phTarget","phMin","phMax",
            "limeStatus","fieldStatus","fieldStatusKind","fieldStatusIndex","treatmentState",
            "treatmentLabel","treatmentReason","treatmentPeriodsRemaining","weedState","weedLabel","diseaseState",
            "diseaseGroup","diseaseLabel","diseaseSeverity","soilCondition","cropCondition"}) do
            local value = field[key]
            if type(value) == "number" then
                value = string.format("%." .. tostring(precisionByKey[key] or 0) .. "f", value)
            end
            add(key); add(value)
        end
        for _, entry in ipairs(field.cropHistory or {}) do add(entry.position); add(entry.cropName) end
        for _, value in ipairs(field.rotationPlan or {}) do add("plan"); add(value) end
        for _, value in ipairs(field.coverCropPlan or {}) do add("cover"); add(value) end
        for _, value in ipairs(field.ownerReasonCodes or {}) do add("reason"); add(value) end
        for _, value in ipairs(field.limitations or {}) do add("limitation"); add(value) end
    end
    return string.format("%08x", hash)
end

local function buildField(manager, farmId, farmlandId)
    local farmland = farmlandById(farmlandId)
    local fieldId, stableFieldId, identityResolution, field =
        fieldIdentity(manager, farmId, farmlandId)
    -- The repository is the synchronized owner-state used by the Agronomy frame
    -- during fast/client display. A live density scan is only a fallback when
    -- that authoritative snapshot has not yet recorded this farmland.
    local repository = manager.repository
    local cropName = repository ~= nil
        and type(repository.getLastKnownActiveCrop) == "function"
        and repository:getLastKnownActiveCrop(farmlandId) or nil
    local growthState = repository ~= nil
        and type(repository.getLastKnownGrowthState) == "function"
        and repository:getLastKnownGrowthState(farmlandId) or nil
    local fruitTypeIndex = nil
    if cropName ~= nil and cropName ~= "" and g_fruitTypeManager ~= nil
        and type(g_fruitTypeManager.getFruitTypeByName) == "function" then
        local knownFruit = g_fruitTypeManager:getFruitTypeByName(cropName)
        fruitTypeIndex = knownFruit ~= nil and knownFruit.index or nil
    end
    if cropName == nil and type(manager.getActiveCropInfo) == "function" then
        cropName, fruitTypeIndex, growthState = manager:getActiveCropInfo(farmlandId)
    end
    cropName = stringOrNil(cropName)
    fruitTypeIndex = positiveInteger(fruitTypeIndex)
    local fruit, displayName = cropDisplayData(cropName, fruitTypeIndex)
    local harvestState, harvested, withered, harvestReady, cropCondition =
        harvestFacts(fruit, growthState)
    if cropName == nil then
        harvestState, harvested, withered, harvestReady, cropCondition =
            "none", false, false, false, "none"
    end
    local cropFamily = nil
    if cropName ~= nil and RealisticCropRotation ~= nil
        and type(RealisticCropRotation.cropConfig) == "table"
        and type(RealisticCropRotation.cropConfig.families) == "table" then
        cropFamily = stringOrNil(RealisticCropRotation.cropConfig.families[cropName])
    end
    local cropInfo = type(manager.getFieldCropInfo) == "function"
        and manager:getFieldCropInfo(farmlandId) or nil
    local statusLabel, statusKind, statusIndex = nil, nil, nil
    if type(manager.getCurrentFieldStatus) == "function" then
        statusLabel, statusKind, statusIndex = manager:getCurrentFieldStatus(farmlandId)
    end
    local soil = type(manager.scanFieldSoil) == "function"
        and manager:scanFieldSoil(farmlandId, fruitTypeIndex, true) or nil
    if soil == false then soil = nil end
    local disease = RealisticCropRotation ~= nil and RealisticCropRotation.disease or nil
    local diseaseGroup, diseaseSeverity = nil, nil
    if disease ~= nil and type(disease.getWorstGroup) == "function" then
        diseaseGroup, diseaseSeverity = disease:getWorstGroup(farmlandId)
    end
    local diseaseLabel = diseaseGroup ~= nil and type(disease.getDisplayName) == "function"
        and stringOrNil(disease:getDisplayName(diseaseGroup)) or nil
    local treatmentLabel = diseaseGroup ~= nil and type(disease.getTreatmentName) == "function"
        and stringOrNil(disease:getTreatmentName(diseaseGroup)) or nil
    local treatmentPeriods = nil
    if RealisticCropRotationTreatmentLifecycle ~= nil
        and type(RealisticCropRotationTreatmentLifecycle.getNematicidePeriodsRemaining) == "function" then
        treatmentPeriods = numberOrNil(
            RealisticCropRotationTreatmentLifecycle.getNematicidePeriodsRemaining(farmlandId))
    end
    local limitations = {}
    if field == nil then limitations[#limitations + 1] = "noFieldDefinition" end
    if soil == nil then limitations[#limitations + 1] = "soilAnalysisUnavailable" end
    local history = type(manager.getHistory) == "function" and manager:getHistory(farmlandId) or {}
    local historyProjection = copyHistory(history)
    for _, entry in ipairs(historyProjection) do
        local _, historyDisplayName = cropDisplayData(entry.cropName, nil)
        entry.displayName = stringOrNil(historyDisplayName)
    end
    local plan = type(manager.getRotationPlan) == "function" and manager:getRotationPlan(farmlandId) or {}
    local coverPlan = type(manager.getRotationCoverPlan) == "function"
        and manager:getRotationCoverPlan(farmlandId) or {}
    local latestActiveCrop = cropName
    if latestActiveCrop == nil and type(manager.getPendingHistoryCrop) == "function" then
        latestActiveCrop = manager:getPendingHistoryCrop(farmlandId)
    end
    local advice = RealisticCropRotationPresentationService ~= nil
        and type(RealisticCropRotationPresentationService.buildAdvice) == "function"
        and RealisticCropRotationPresentationService.buildAdvice(
            manager, farmlandId, cropName, g_i18n) or nil
    local nitrogenLevel = type(soil) == "table" and numberOrNil(soil.nActual) or nil
    local nitrogenTarget = type(soil) == "table" and numberOrNil(soil.nTarget) or nil
    local ph = type(soil) == "table" and numberOrNil(soil.phActual) or nil
    local phTarget = type(soil) == "table" and numberOrNil(soil.phTarget) or nil
    local weedLabel = type(cropInfo) == "table"
        and stringOrNil(cropInfo.weedActionText or cropInfo.weedHeader) or nil
    return {
        schemaVersion=SCHEMA_VERSION, sourceId=SOURCE_ID, available=true,
        availabilityState="available", farmId=farmId, ownerFarmId=farmId,
        farmlandId=farmlandId, fieldId=fieldId, stableFieldId=stableFieldId,
        identityResolution=identityResolution,
        displayName=type(manager.getFarmlandDisplayName) == "function"
            and manager:getFarmlandDisplayName(farmlandId) or tostring(farmlandId),
        areaHa=areaHa(farmland, field),
        fruitTypeIndex=fruitTypeIndex, fruitTypeName=cropName,
        cropDisplayName=displayName, cropFamily=cropFamily,
        growthState=numberOrNil(growthState),
        growthStateLabel=type(cropInfo) == "table" and stringOrNil(cropInfo.growthStageText) or nil,
        harvestState=harvestState, harvested=harvested, withered=withered,
        harvestReady=harvestReady, cropCondition=cropCondition,
        cropHistory=historyProjection, rotationPlan=copyStringArray(plan),
        rotationPlanDisplayNames=localizedCropNames(plan),
        coverCropPlan=copyStringArray(coverPlan),
        coverCropPlanDisplayNames=localizedCropNames(coverPlan),
        latestActiveCrop=stringOrNil(latestActiveCrop),
        rotationStatus=advice ~= nil and advice.status or nil,
        rotationScore=nil, rotationFactor=nil,
        rotationAdviceAvailable=advice ~= nil and advice.text ~= nil,
        rotationActionRequired=advice ~= nil and advice.actionRequired == true,
        rotationAdviceText=advice ~= nil and stringOrNil(advice.text) or nil,
        ownerReasonCodes=advice ~= nil and copyStringArray(advice.reasonCodes) or {},
        nitrogenLevel=nitrogenLevel, nitrogenTarget=nitrogenTarget,
        nitrogenNeed=nitrogenLevel ~= nil and nitrogenTarget ~= nil
            and nitrogenTarget - nitrogenLevel or nil,
        ph=ph, phTarget=phTarget,
        phMin=type(soil) == "table" and numberOrNil(soil.phMin) or nil,
        phMax=type(soil) == "table" and numberOrNil(soil.phMax) or nil,
        limeStatus=ph ~= nil and phTarget ~= nil and (ph < phTarget and "needed" or "ok") or nil,
        fieldStatus=stringOrNil(statusLabel), fieldStatusKind=stringOrNil(statusKind),
        fieldStatusIndex=numberOrNil(statusIndex),
        treatmentState=treatmentPeriods ~= nil and treatmentPeriods > 0 and "active"
            or (treatmentLabel ~= nil and "recommended" or "none"),
        treatmentLabel=treatmentLabel, treatmentReason=diseaseLabel,
        treatmentPeriodsRemaining=treatmentPeriods,
        weedState=weedLabel ~= nil and "documented" or "unknown", weedLabel=weedLabel,
        diseaseState=diseaseGroup ~= nil and "active" or "none",
        diseaseGroup=stringOrNil(diseaseGroup), diseaseLabel=diseaseLabel,
        diseaseSeverity=numberOrNil(diseaseSeverity),
        soilCondition=stringOrNil(statusKind) == "soil" and stringOrNil(statusLabel) or nil,
        limitations=limitations, capabilities=CAPABILITIES
    }
end

local function buildFarmSnapshot(farmId)
    local manager = getManager()
    local fields = {}
    for _, farmlandId in ipairs(ownedFarmlandIds(farmId)) do
        fields[#fields + 1] = buildField(manager, farmId, farmlandId)
    end
    table.sort(fields, function(a, b) return a.farmlandId < b.farmlandId end)
    local revision = string.format("rcr-agronomy-v1:%d:%s",
        farmId, fingerprint(fields))
    for _, field in ipairs(fields) do field.sourceRevision = revision end
    state.cacheByFarm[farmId] = {
        revision=revision,
        fields=fields,
        builtAtMs=tonumber(g_time) or 0
    }
    return fields, revision
end

function RCRAgronomyReadFacade.getContractVersion()
    return CONTRACT_VERSION
end

function RCRAgronomyReadFacade.getSupportedCapabilities()
    return copyCapabilities(CAPABILITIES)
end

function RCRAgronomyReadFacade.isAvailable(farmId)
    return positiveInteger(farmId) ~= nil and state.missionLoaded
        and state.ownerSynchronized and getManager() ~= nil
end

function RCRAgronomyReadFacade.getRevision(farmId)
    farmId = positiveInteger(farmId)
    if not RCRAgronomyReadFacade.isAvailable(farmId) then
        return "rcr-agronomy-v1:" .. tostring(farmId or "none") .. ":unavailable"
    end
    local cached = state.cacheByFarm[farmId]
    local nowMs = tonumber(g_time) or 0
    if cached ~= nil and (nowMs <= 0
        or nowMs - (cached.builtAtMs or 0) < REVISION_POLL_INTERVAL_MS) then
        return cached.revision
    end
    local _, revision = buildFarmSnapshot(farmId)
    return revision
end

function RCRAgronomyReadFacade.getAllFieldAgronomy(farmId)
    farmId = positiveInteger(farmId)
    local result = {
        schemaVersion=SCHEMA_VERSION,
        contractVersion=CONTRACT_VERSION,
        sourceId=SOURCE_ID,
        farmId=farmId,
        available=false,
        availabilityState="loading",
        sourceRevision="rcr-agronomy-v1:" .. tostring(farmId or "none") .. ":unavailable",
        capabilities=copyCapabilities(CAPABILITIES),
        fields={}
    }
    if not RCRAgronomyReadFacade.isAvailable(farmId) then return result end
    RCRAgronomyReadFacade.getRevision(farmId)
    local cached = state.cacheByFarm[farmId]
    local fields = cached ~= nil and cached.fields or buildFarmSnapshot(farmId)
    result.available = true
    result.availabilityState = "available"
    result.sourceRevision = cached ~= nil and cached.revision
        or "rcr-agronomy-v1:" .. tostring(farmId) .. ":unavailable"
    for _, field in ipairs(fields) do result.fields[#result.fields + 1] = copyField(field) end
    return result
end

function RCRAgronomyReadFacade.getFieldAgronomy(farmId, farmlandId)
    farmId, farmlandId = positiveInteger(farmId), positiveInteger(farmlandId)
    if farmId == nil or farmlandId == nil then return nil end
    local snapshot = RCRAgronomyReadFacade.getAllFieldAgronomy(farmId)
    for _, field in ipairs(snapshot.fields or {}) do
        if field.farmlandId == farmlandId then return field end
    end
    return nil
end

function RCRAgronomyReadFacade.onMissionLoad(manager, ownerSynchronized)
    state.manager = manager
    state.missionLoaded = manager ~= nil
    state.ownerSynchronized = ownerSynchronized == true
    state.cacheByFarm = {}
end

function RCRAgronomyReadFacade.onOwnerSyncApplied()
    state.ownerSynchronized = true
    state.cacheByFarm = {}
end

function RCRAgronomyReadFacade.markChanged()
    state.cacheByFarm = {}
end

function RCRAgronomyReadFacade.onMissionDelete()
    state.manager = nil
    state.missionLoaded = false
    state.ownerSynchronized = false
    state.cacheByFarm = {}
end

g_globalMods = g_globalMods or {}
g_globalMods.RCRAgronomyReadFacade = RCRAgronomyReadFacade
