-- ---------------------------------------------------------------------------
--  Freemode ped appearance model.
--
--  The client builds a ped from this table and the server stores it verbatim,
--  so it is sanitised here - once - and both sides use the same code. Anything
--  the client sends that is not described below is dropped.
-- ---------------------------------------------------------------------------

Appearance = {}

Appearance.MODELS = {
  m = 'mp_m_freemode_01',
  f = 'mp_f_freemode_01',
}

-- SetPedHeadBlendData: 46 heritage faces per parent.
Appearance.PARENT_MAX = 45

-- SetPedFaceFeature indices, in the order the creator shows them.
Appearance.FEATURES = {
  { id = 0,  label = 'Nose width' },
  { id = 1,  label = 'Nose height' },
  { id = 2,  label = 'Nose length' },
  { id = 3,  label = 'Nose bridge' },
  { id = 4,  label = 'Nose tip' },
  { id = 5,  label = 'Nose shift' },
  { id = 6,  label = 'Brow height' },
  { id = 7,  label = 'Brow depth' },
  { id = 8,  label = 'Cheekbone height' },
  { id = 9,  label = 'Cheekbone width' },
  { id = 10, label = 'Cheek width' },
  { id = 11, label = 'Eye opening' },
  { id = 12, label = 'Lip thickness' },
  { id = 13, label = 'Jaw width' },
  { id = 14, label = 'Jaw length' },
  { id = 15, label = 'Chin height' },
  { id = 16, label = 'Chin length' },
  { id = 17, label = 'Chin width' },
  { id = 18, label = 'Chin dimple' },
  { id = 19, label = 'Neck thickness' },
}

-- SetPedHeadOverlay slots that the creator exposes.
Appearance.OVERLAYS = {
  { id = 2,  key = 'eyebrows',  label = 'Eyebrows',    max = 33, tint = 'hair' },
  { id = 1,  key = 'beard',     label = 'Facial hair', max = 28, tint = 'hair' },
  { id = 0,  key = 'blemishes', label = 'Blemishes',   max = 23, tint = false },
  { id = 3,  key = 'ageing',    label = 'Ageing',      max = 14, tint = false },
  { id = 6,  key = 'complexion',label = 'Complexion',  max = 11, tint = false },
  { id = 7,  key = 'sundamage', label = 'Sun damage',  max = 10, tint = false },
  { id = 9,  key = 'freckles',  label = 'Freckles',    max = 17, tint = false },
  { id = 5,  key = 'blush',     label = 'Blush',       max = 6,  tint = 'makeup' },
  { id = 8,  key = 'lipstick',  label = 'Lipstick',    max = 9,  tint = 'makeup' },
  { id = 4,  key = 'makeup',    label = 'Make-up',     max = 74, tint = 'makeup' },
}

-- Clothing components the creator exposes (0-11 exist; these are the useful ones).
Appearance.COMPONENTS = {
  { id = 11, key = 'jacket',    label = 'Top' },
  { id = 8,  key = 'undershirt',label = 'Undershirt' },
  { id = 4,  key = 'legs',      label = 'Legs' },
  { id = 6,  key = 'shoes',     label = 'Shoes' },
  { id = 3,  key = 'torso',     label = 'Arms' },
  { id = 1,  key = 'mask',      label = 'Mask' },
  { id = 9,  key = 'vest',      label = 'Vest' },
  { id = 7,  key = 'accessory', label = 'Accessory' },
  { id = 10, key = 'decal',     label = 'Decal' },
  { id = 5,  key = 'bag',       label = 'Bag' },
}

Appearance.PROPS = {
  { id = 0, key = 'hat',     label = 'Hat' },
  { id = 1, key = 'glasses', label = 'Glasses' },
  { id = 2, key = 'ear',     label = 'Earrings' },
}

local function num(v, lo, hi, fallback)
  v = tonumber(v)
  if not v or v ~= v then return fallback end     -- also rejects NaN
  if v < lo then return lo end
  if v > hi then return hi end
  return v
end

local function int(v, lo, hi, fallback)
  return math.floor(num(v, lo, hi, fallback) + 0.0)
end

function Appearance.Default(gender)
  local a = {
    model    = (gender == 'f') and 'f' or 'm',
    parents  = { father = 0, mother = 21, shapeMix = 0.5, skinMix = 0.5 },
    features = {},
    overlays = {},
    hair     = { style = 0, colour = 0, highlight = 0 },
    eyeColour = 0,
    components = {},
    props      = {},
  }
  for _, f in ipairs(Appearance.FEATURES) do a.features[tostring(f.id)] = 0.0 end
  for _, o in ipairs(Appearance.OVERLAYS) do
    a.overlays[o.key] = { index = -1, opacity = 1.0, colour = 0 }
  end
  for _, c in ipairs(Appearance.COMPONENTS) do
    a.components[c.key] = { drawable = 0, texture = 0 }
  end
  for _, p in ipairs(Appearance.PROPS) do
    a.props[p.key] = { drawable = -1, texture = 0 }
  end
  -- a plain default outfit rather than the naked base ped
  a.components.jacket     = { drawable = 15, texture = 0 }
  a.components.undershirt = { drawable = 15, texture = 0 }
  a.components.torso      = { drawable = 15, texture = 0 }
  a.components.legs       = { drawable = 21, texture = 0 }
  a.components.shoes      = { drawable = 34, texture = 0 }
  return a
end

--- Returns a clean appearance table built only from known keys.
function Appearance.Sanitise(input)
  local gender = (type(input) == 'table' and input.model == 'f') and 'f' or 'm'
  local out = Appearance.Default(gender)
  if type(input) ~= 'table' then return out end

  if type(input.parents) == 'table' then
    out.parents.father   = int(input.parents.father, 0, Appearance.PARENT_MAX, 0)
    out.parents.mother   = int(input.parents.mother, 0, Appearance.PARENT_MAX, 21)
    out.parents.shapeMix = num(input.parents.shapeMix, 0.0, 1.0, 0.5)
    out.parents.skinMix  = num(input.parents.skinMix, 0.0, 1.0, 0.5)
  end

  if type(input.features) == 'table' then
    for _, f in ipairs(Appearance.FEATURES) do
      local k = tostring(f.id)
      out.features[k] = num(input.features[k], -1.0, 1.0, 0.0)
    end
  end

  if type(input.overlays) == 'table' then
    for _, o in ipairs(Appearance.OVERLAYS) do
      local given = input.overlays[o.key]
      if type(given) == 'table' then
        out.overlays[o.key] = {
          index   = int(given.index, -1, o.max, -1),
          opacity = num(given.opacity, 0.0, 1.0, 1.0),
          colour  = int(given.colour, 0, 63, 0),
        }
      end
    end
  end

  if type(input.hair) == 'table' then
    out.hair.style     = int(input.hair.style, 0, 80, 0)
    out.hair.colour    = int(input.hair.colour, 0, 63, 0)
    out.hair.highlight = int(input.hair.highlight, 0, 63, 0)
  end

  out.eyeColour = int(input.eyeColour, 0, 31, 0)

  if type(input.components) == 'table' then
    for _, c in ipairs(Appearance.COMPONENTS) do
      local given = input.components[c.key]
      if type(given) == 'table' then
        out.components[c.key] = {
          drawable = int(given.drawable, 0, 511, 0),
          texture  = int(given.texture, 0, 63, 0),
        }
      end
    end
  end

  if type(input.props) == 'table' then
    for _, p in ipairs(Appearance.PROPS) do
      local given = input.props[p.key]
      if type(given) == 'table' then
        out.props[p.key] = {
          drawable = int(given.drawable, -1, 255, -1),
          texture  = int(given.texture, 0, 63, 0),
        }
      end
    end
  end

  return out
end
