//! Structures used by Marathon's Map format.

use crate::{durandal::{bin::*, err::*, fixed::*, text::*},
            marathon::xfer::TransferMode};
use bitflags::bitflags;

/// Reads a `LightFunc` object.
pub fn read_lightfunc(b: &[u8]) -> ResultS<LightFunc>
{
   read_data! {
      14, BE in b =>
      ftype   = u16[0];
      prd_nrm = u16[2];
      prd_dta = u16[4];
      val_nrm = Fixed[6];
      val_dta = Fixed[10];
   }

   let ftype = LightFuncType::from_repr(ftype)?;

   Ok(LightFunc{ftype, prd_nrm, prd_dta, val_nrm, val_dta})
}

/// Reads a `SideTex` object.
pub fn read_sidetex(b: &[u8]) -> ResultS<SideTex>
{
   read_data! {
      6, BE in b =>
      offs   = read_point[0..4];
      tex_id = OptU16[4];
   }

   Ok(SideTex{offs, tex_id})
}

/// Reads a `Point` object.
pub fn read_point(b: &[u8]) -> ResultS<Point>
{
   read_data! {
      4, BE in b =>
      x = Unit[0];
      y = Unit[2];
   }

   Ok(Point{x, y})
}

/// Reads a `Minf` chunk.
pub fn read_minf(b: &[u8]) -> ResultS<Minf>
{
   read_data! {
      88, BE in b =>
      texture_id = u16[0];
      physics_id = u16[2];
      skypict_id = u16[4];
      miss_flags = u16[6];
      envi_flags = u16[8];
      level_name = mac_roman_cstr[18..84] no_try;
      entr_flags = u32[84];
   }

   let miss_flags = flag_ok!(MsnFlags, miss_flags)?;
   let envi_flags = flag_ok!(EnvFlags, envi_flags)?;
   let entr_flags = flag_ok!(EntFlags, entr_flags)?;

   Ok(Minf{texture_id, physics_id, skypict_id, miss_flags, envi_flags,
           entr_flags, level_name})
}

/// Reads an old `Minf` chunk.
pub fn read_old_minf(b: &[u8]) -> ResultS<Minf>
{
   let minf = read_minf(b)?;

   let mut entr_flags = if minf.entr_flags.is_empty() {
      EntFlags::SOLO
   } else {
      minf.entr_flags
   };

   if entr_flags.intersects(EntFlags::SOLO | EntFlags::CARNAGE) {
      entr_flags.insert(EntFlags::CO_OP)
   }

   Ok(Minf{entr_flags, ..minf})
}

/// Reads an `iidx` chunk.
pub fn read_iidx(b: &[u8]) -> ResultS<(u16, usize)>
{
   check_data!(2, b);

   Ok((u16b(b), 2))
}

/// Reads an `EPNT` chunk.
pub fn read_epnt(b: &[u8]) -> ResultS<(Point, usize)>
{
   read_data! {
      16, BE in b =>
      pnt = read_point[6..10];
   }

   Ok((pnt, 16))
}

/// Reads a `PNTS` chunk.
pub fn read_pnts(b: &[u8]) -> ResultS<(Point, usize)>
{
   Ok((read_point(b)?, 4))
}

/// Reads a `LINS` chunk.
pub fn read_lins(b: &[u8]) -> ResultS<(Line, usize)>
{
   read_data! {
      32, BE in b =>
      pnt_beg = u16[0];
      pnt_end = u16[2];
      flags   = u16[4];
      side_fr = OptU16[12];
      side_bk = OptU16[14];
      poly_fr = OptU16[16];
      poly_bk = OptU16[18];
   }

   let flags = flag_ok!(LineFlags, flags)?;

   Ok((Line{flags, pnt_beg, pnt_end, side_fr, side_bk, poly_fr, poly_bk}, 32))
}

/// Reads a `SIDS` chunk.
pub fn read_sids(b: &[u8]) -> ResultS<(Side, usize)>
{
   read_data! {
      64, BE in b =>
      stype    = u16[0];
      flags    = u16[2];
      tex_pri  = read_sidetex[4..10];
      tex_sec  = read_sidetex[10..16];
      tex_tra  = read_sidetex[16..22];
      paneltyp = u16[38];
      paneldat = i16[40];
      xfer_pri = u16[42];
      xfer_sec = u16[44];
      xfer_tra = u16[46];
      shade    = Fixed[48];
   }

   let flags    = flag_ok!(SideFlags, flags)?;
   let xfer_pri = TransferMode::from_repr(xfer_pri)?;
   let xfer_sec = TransferMode::from_repr(xfer_sec)?;
   let xfer_tra = TransferMode::from_repr(xfer_tra)?;
   let stype    = SideType::from_repr(stype)?;

   Ok((Side{stype, flags, tex_pri, tex_sec, tex_tra, paneltyp, paneldat,
            xfer_pri, xfer_sec, xfer_tra, shade}, 64))
}

/// Reads an old `SIDS` chunk.
pub fn read_old_sids(b: &[u8]) -> ResultS<(Side, usize)>
{
   let (side, siz) = read_sids(b)?;

   Ok((Side{tex_tra: SideTex{tex_id: OptU16::none(), ..side.tex_tra},
            shade:   0.into(),
            flags:   side.flags | SideFlags::ITEM_OPT,
            ..side}, siz))
}

/// Reads a polygon for either M1 or M2.
fn read_poly_inter(b: &[u8]) -> ResultS<Polygon>
{
   read_data! {
      128, BE in b =>
      tex_flr = OptU16[40];
      tex_cei = OptU16[42];
      hei_flr = Unit[44];
      hei_cei = Unit[46];
      lit_flr = u16[48];
      lit_cei = u16[50];
      xfr_flr = u16[64];
      xfr_cei = u16[66];
   }

   let xfr_flr = TransferMode::from_repr(xfr_flr)?;
   let xfr_cei = TransferMode::from_repr(xfr_cei)?;

   Ok(Polygon{tex_flr, tex_cei, hei_flr, hei_cei, lit_flr, lit_cei, xfr_flr,
              xfr_cei, ..Default::default()})
}

/// Reads a `POLY` chunk.
pub fn read_poly(b: &[u8]) -> ResultS<(Polygon, usize)>
{
   read_data! {
      128, BE in b =>
      ptype   = u16[0];
      pdata   = u16[4];
      ori_flr = read_point[108..112];
      ori_cei = read_point[112..116];
      med_ind = OptU16[116];
      med_ctl = u16[118];
      snd_amb = OptU16[122];
      snd_ind = u16[120];
      snd_rnd = OptU16[124];
   }

   let poly  = read_poly_inter(b)?;
   let ptype = PolyType::new(ptype, pdata)?;

   Ok((Polygon{ptype, ori_flr, ori_cei, med_ind, med_ctl, snd_ind, snd_amb,
               snd_rnd, ..poly}, 128))
}

/// Reads an old `POLY` chunk.
pub fn read_old_poly(b: &[u8]) -> ResultS<(Polygon, usize)>
{
   read_data! {
      128, BE in b =>
      ptype = u16[0];
      pdata = u16[4];
   }

   let poly  = read_poly_inter(b)?;
   let ptype = PolyType::new_old(ptype, pdata)?;

   Ok((Polygon{ptype, ..poly}, 128))
}

/// Reads a `LITE` chunk.
pub fn read_lite(b: &[u8]) -> ResultS<(Light, usize)>
{
   read_data! {
      100, BE in b =>
      ltype   = u16[0];
      flags   = u16[2];
      phase   = i16[4];
      act_pri = read_lightfunc[6..20];
      act_sec = read_lightfunc[20..34];
      act_mid = read_lightfunc[34..48];
      ina_pri = read_lightfunc[48..62];
      ina_sec = read_lightfunc[62..76];
      ina_mid = read_lightfunc[76..90];
      tag     = u16[90];
   }

   let flags = flag_ok!(LightFlags, flags)?;
   let ltype = LightType::from_repr(ltype)?;

   Ok((Light{ltype, flags, phase, act_pri, act_sec, act_mid, ina_pri, ina_sec,
             ina_mid, tag}, 100))
}

/// Reads an old `LITE` chunk.
pub fn read_old_lite(b: &[u8]) -> ResultS<(Light, usize)>
{
   read_data! {
      32, BE in b =>
      ltype = u16[2] usize;
      mode  = u16[4];
      phase = i16[6];
      min   = Fixed[8];
      max   = Fixed[12];
      prd   = u16[16];
   }

   if OLD_LIGHT_DEFINITIONS.len() < ltype {
      bail!("bad old light type");
   }

   let lite   = &OLD_LIGHT_DEFINITIONS[ltype];
   let on     = mode == 0 || mode == 1;
   let strobe = ltype == 3;
   let flags  = if on {lite.flags | LightFlags::INIT_ACTIVE} else {lite.flags};

   // modify each old light function accordingly
   let old_lfun = move |func: &LightFunc| -> LightFunc {
      LightFunc{ftype:   func.ftype,
                prd_nrm: if strobe {prd / 4 + 1} else {func.prd_nrm},
                prd_dta: func.prd_dta,
                val_nrm: if func.val_nrm > 0.into() {max} else {min},
                val_dta: func.val_dta}
   };

   Ok((Light{flags,
             phase,
             act_pri: old_lfun(&lite.act_pri),
             act_sec: old_lfun(&lite.act_sec),
             act_mid: old_lfun(&lite.act_mid),
             ina_pri: old_lfun(&lite.ina_pri),
             ina_sec: old_lfun(&lite.ina_sec),
             ina_mid: old_lfun(&lite.ina_mid),
             tag: 0,
             ..*lite}, 32))
}

/// Reads an `OBJS` chunk.
pub fn read_objs(b: &[u8]) -> ResultS<(Object, usize)>
{
   read_data! {
      16, BE in b =>
      group = u16[0];
      index = u16[2];
      angle = Angle[4];
      poly  = u16[6];
      pos_x = Unit[8];
      pos_y = Unit[10];
      pos_z = Unit[12];
      flags = u16[14];
   }

   let bias  = flags & 0xF0_00;
   let flags = flags & 0x0F_FF;
   let bias  = bias >> 12;
   let flags = flag_ok!(ObjectFlags, flags)?;

   Ok((Object{group, index, angle, poly, pos_x, pos_y, pos_z, flags, bias}, 16))
}

/// Reads a `plac` chunk.
pub fn read_plac(b: &[u8]) -> ResultS<(ObjectFreq, usize)>
{
   read_data! {
      12, BE in b =>
      flags   = u16[0];
      cnt_ini = u16[2];
      cnt_min = u16[4];
      cnt_max = u16[6];
      cnt_rnd = u16[8];
      chance  = u16[10];
   }

   let rnd_loc = flags != 0;

   Ok((ObjectFreq{rnd_loc, cnt_ini, cnt_min, cnt_max, cnt_rnd, chance}, 12))
}

/// Reads an `ambi` chunk.
pub fn read_ambi(b: &[u8]) -> ResultS<(SoundAmbi, usize)>
{
   read_data! {
      16, BE in b =>
      index  = u16[2];
      volume = u16[4];
   }

   Ok((SoundAmbi{index, volume}, 16))
}

/// Reads a `bonk` chunk.
pub fn read_bonk(b: &[u8]) -> ResultS<(SoundRand, usize)>
{
   read_data! {
      32, BE in b =>
      flags   = u16[0];
      index   = u16[2];
      vol_nrm = u16[4];
      vol_dta = u16[6];
      prd_nrm = u16[8];
      prd_dta = u16[10];
      yaw_nrm = Angle[12];
      yaw_dta = Angle[14];
      pit_nrm = Fixed[16];
      pit_dta = Fixed[20];
   }

   let no_dir = flags != 0;

   Ok((SoundRand{no_dir, index, vol_nrm, vol_dta, prd_nrm, prd_dta, yaw_nrm,
                 yaw_dta, pit_nrm, pit_dta}, 32))
}

/// Reads a `medi` chunk.
pub fn read_medi(b: &[u8]) -> ResultS<(Media, usize)>
{
   read_data! {
      32, BE in b =>
      mtype   = u16[0];
      flags   = u16[2];
      control = u16[4];
      dir     = Angle[6];
      mag     = Unit[8];
      hei_lo  = Unit[10];
      hei_hi  = Unit[12];
      orig    = read_point[14..18];
      hei_nrm = Unit[18];
      min_lt  = Fixed[20];
      texture = OptU16[24];
      xfer    = u16[26];
   }

   let mtype   = MediaType::from_repr(mtype)?;
   let xfer    = TransferMode::from_repr(xfer)?;
   let flr_obs = flags != 0;

   Ok((Media{mtype, flr_obs, control, dir, mag, hei_lo, hei_hi, orig, hei_nrm,
             min_lt, texture, xfer}, 32))
}

/// Reads a `plat` chunk.
pub fn read_plat(b: &[u8]) -> ResultS<(Platform, usize)>
{
   read_data! {
      32, BE in b =>
      ptype   = u16[0];
      speed   = u16[2];
      delay   = u16[4];
      hei_max = Unit[6];
      hei_min = Unit[8];
      flags   = u32[10];
      index   = u16[14];
      tag     = u16[16];
   }

   let flags = flag_ok!(PlatformFlags, flags)?;

   Ok((Platform{ptype, speed, delay, hei_min, hei_max, flags, index, tag}, 32))
}

/// Reads a `NOTE` chunk.
pub fn read_note(b: &[u8]) -> ResultS<(Note, usize)>
{
   read_data! {
      72, BE in b =>
      pos  = read_point[2..6];
      poly = u16[6];
      text = mac_roman_cstr[8..72] no_try;
   }

   Ok((Note{pos, poly, text}, 72))
}

impl PolyType
{
   /// Creates a `PolyType` from a `n`/`pdata` pair.
   pub fn new(n: u16, pdata: u16) -> Result<Self, ReprError>
   {
      match n {
         0  => Ok(PolyType::Normal),
         1  => Ok(PolyType::ImpassItem),
         2  => Ok(PolyType::ImpassMons),
         3  => Ok(PolyType::Hill),
         4  => Ok(PolyType::Base),
         5  => Ok(PolyType::Platform(pdata)),
         6  => Ok(PolyType::TrigLightOn(pdata)),
         7  => Ok(PolyType::TrigPlatOn(pdata)),
         8  => Ok(PolyType::TrigLightOff(pdata)),
         9  => Ok(PolyType::TrigPlatOff(pdata)),
         10 => Ok(PolyType::Teleporter(pdata)),
         11 => Ok(PolyType::ZoneBorder),
         12 => Ok(PolyType::Goal),
         13 => Ok(PolyType::TrigMonsVis),
         14 => Ok(PolyType::TrigMonsInv),
         15 => Ok(PolyType::TrigMonsDual),
         16 => Ok(PolyType::TrigItems),
         17 => Ok(PolyType::MustExplore),
         18 => Ok(PolyType::AutoExit),
         19 => Ok(PolyType::OuchMinor),
         20 => Ok(PolyType::OuchMajor),
         21 => Ok(PolyType::Glue),
         22 => Ok(PolyType::GlueTrigger(pdata)),
         23 => Ok(PolyType::GlueSuper),
         n  => Err(ReprError::new(n)),
      }
   }

   /// Creates a `PolyType` from a Marathon 1 compatible `n`/`pdata` pair.
   fn new_old(n: u16, pdata: u16) -> Result<Self, ReprError>
   {
      match n {
         0  => Ok(PolyType::Normal),
         1  => Ok(PolyType::ImpassItem),
         2  => Ok(PolyType::ImpassMons),
         3  => Ok(PolyType::OuchMinor),
         4  => Ok(PolyType::OuchMajor),
         5  => Ok(PolyType::Platform(pdata)),
         6  => Ok(PolyType::TrigLightOn(pdata)),
         7  => Ok(PolyType::TrigPlatOn(pdata)),
         8  => Ok(PolyType::TrigLightOff(pdata)),
         9  => Ok(PolyType::TrigPlatOff(pdata)),
         10 => Ok(PolyType::Teleporter(pdata)),
         11 => Ok(PolyType::Glue),
         12 => Ok(PolyType::GlueTrigger(pdata)),
         13 => Ok(PolyType::GlueSuper),
         14 => Ok(PolyType::MustExplore),
         15 => Ok(PolyType::AutoExit),
         n  => Err(ReprError::new(n)),
      }
   }
}

impl Default for PolyType
{
   fn default() -> Self {PolyType::Normal}
}

impl Default for Minf
{
   fn default() -> Self
   {
      Self{texture_id: 0,
           physics_id: 1,
           skypict_id: 0,
           miss_flags: MsnFlags::empty(),
           envi_flags: EnvFlags::empty(),
           entr_flags: EntFlags::SOLO,
           level_name: "Map".to_string()}
   }
}

/// A point in world-space.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub struct Point
{
   pub x: Unit,
   pub y: Unit,
}

/// A line segment.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Debug, Eq, PartialEq)]
pub struct Line
{
   pub flags:   LineFlags,
   pub pnt_beg: u16,
   pub pnt_end: u16,
   pub side_fr: OptU16,
   pub side_bk: OptU16,
   pub poly_fr: OptU16,
   pub poly_bk: OptU16,
}

/// The texture of a side segment.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Debug, Eq, PartialEq)]
pub struct SideTex
{
   pub offs:   Point,
   pub tex_id: OptU16,
}

/// One side of a line segment.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Debug, Eq, PartialEq)]
pub struct Side
{
   pub stype:    SideType,
   pub flags:    SideFlags,
   pub tex_pri:  SideTex,
   pub tex_sec:  SideTex,
   pub tex_tra:  SideTex,
   pub paneltyp: u16,
   pub paneldat: i16,
   pub xfer_pri: TransferMode,
   pub xfer_sec: TransferMode,
   pub xfer_tra: TransferMode,
   pub shade:    Fixed,
}

/// A polygon segment.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Debug, Default, Eq, PartialEq)]
pub struct Polygon
{
   pub ptype:   PolyType,
   pub tex_flr: OptU16,
   pub tex_cei: OptU16,
   pub hei_flr: Unit,
   pub hei_cei: Unit,
   pub lit_flr: u16,
   pub lit_cei: u16,
   pub xfr_flr: TransferMode,
   pub xfr_cei: TransferMode,
   pub ori_flr: Point,
   pub ori_cei: Point,
   pub med_ind: OptU16,
   pub med_ctl: u16,
   pub snd_ind: u16,
   pub snd_amb: OptU16,
   pub snd_rnd: OptU16,
}

/// A light function.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Debug, Eq, PartialEq)]
pub struct LightFunc
{
   pub ftype:   LightFuncType,
   pub prd_nrm: u16,
   pub prd_dta: u16,
   pub val_nrm: Fixed,
   pub val_dta: Fixed,
}

/// A dynamic polygon light.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Debug, Eq, PartialEq)]
pub struct Light
{
   pub ltype:   LightType,
   pub flags:   LightFlags,
   pub phase:   i16,
   pub act_pri: LightFunc,
   pub act_sec: LightFunc,
   pub act_mid: LightFunc,
   pub ina_pri: LightFunc,
   pub ina_sec: LightFunc,
   pub ina_mid: LightFunc,
   pub tag:     u16,
}

/// An object in the world.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Debug, Eq, PartialEq)]
pub struct Object
{
   pub group: u16,
   pub index: u16,
   pub angle: Angle,
   pub poly:  u16,
   pub pos_x: Unit,
   pub pos_y: Unit,
   pub pos_z: Unit,
   pub flags: ObjectFlags,
   pub bias:  u16,
}

/// The difficulty definition for various object types.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Debug, Eq, PartialEq)]
pub struct ObjectFreq
{
   pub rnd_loc: bool,
   pub cnt_ini: u16,
   pub cnt_min: u16,
   pub cnt_max: u16,
   pub cnt_rnd: u16,
   pub chance:  u16,
}

/// An ambient sound definition.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Debug, Eq, PartialEq)]
pub struct SoundAmbi
{
   pub index:  u16,
   pub volume: u16,
}

/// A randomly played sound definition.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Debug, Eq, PartialEq)]
pub struct SoundRand
{
   pub no_dir:  bool,
   pub index:   u16,
   pub vol_nrm: u16,
   pub vol_dta: u16,
   pub prd_nrm: u16,
   pub prd_dta: u16,
   pub yaw_nrm: Angle,
   pub yaw_dta: Angle,
   pub pit_nrm: Fixed,
   pub pit_dta: Fixed,
}

/// A media, as in a part of a polygon which goes up the middle of the wall.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Debug, Eq, PartialEq)]
pub struct Media
{
   pub mtype:   MediaType,
   pub flr_obs: bool,
   pub control: u16,
   pub dir:     Angle,
   pub mag:     Unit,
   pub hei_lo:  Unit,
   pub hei_hi:  Unit,
   pub orig:    Point,
   pub hei_nrm: Unit,
   pub min_lt:  Fixed,
   pub texture: OptU16,
   pub xfer:    TransferMode,
}

/// Extra information for polygons with platforms.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Debug, Eq, PartialEq)]
pub struct Platform
{
   pub ptype:   u16,
   pub speed:   u16,
   pub delay:   u16,
   pub hei_min: Unit,
   pub hei_max: Unit,
   pub flags:   PlatformFlags,
   pub index:   u16,
   pub tag:     u16,
}

/// Overhead map annotations.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Debug, Eq, PartialEq)]
pub struct Note
{
   pub pos:  Point,
   pub poly: u16,
   pub text: String,
}

/// Static map information.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Debug, Eq, PartialEq)]
pub struct Minf
{
   pub texture_id: u16,
   pub physics_id: u16,
   pub skypict_id: u16,
   pub miss_flags: MsnFlags,
   pub envi_flags: EnvFlags,
   pub entr_flags: EntFlags,
   pub level_name: String,
}

/// The action type of a `Polygon`.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Debug, Eq, PartialEq)]
pub enum PolyType
{
   Normal,
   ImpassItem,
   ImpassMons,
   Hill,
   Base,
   Platform(u16),
   TrigLightOn(u16),
   TrigPlatOn(u16),
   TrigLightOff(u16),
   TrigPlatOff(u16),
   Teleporter(u16),
   ZoneBorder,
   Goal,
   TrigMonsVis,
   TrigMonsInv,
   TrigMonsDual,
   TrigItems,
   MustExplore,
   AutoExit,
   OuchMinor,
   OuchMajor,
   Glue,
   GlueTrigger(u16),
   GlueSuper,
}

bitflags! {
   /// Flags for `Line`.
   #[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
   pub struct LineFlags: u16
   {
      const TRANS_SIDE  = 1 << 9;
      const ELEV_VAR    = 1 << 10;
      const ELEVATION   = 1 << 11;
      const LANDSCAPE   = 1 << 12;
      const TRANSPARENT = 1 << 13;
      const SOLID       = 1 << 14;
   }
}

bitflags! {
   /// Flags for `Side`.
   #[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
   pub struct SideFlags: u16
   {
      const STATUS      = 1;
      const PANEL       = 1 << 1;
      const REPAIR      = 1 << 2;
      const ITEM_USE    = 1 << 3;
      const LIGHTED     = 1 << 4;
      const CAN_DESTROY = 1 << 5;
      const HIT_ONLY    = 1 << 6;
      const ITEM_OPT    = 1 << 7;
   }
}

bitflags! {
   /// Static environment flags.
   #[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
   pub struct EnvFlags: u16
   {
      const VACUUM     = 1;
      const MAGNETIC   = 1 << 1;
      const REBELLION  = 1 << 2;
      const LOW_GRAV   = 1 << 3;
      const M1_GLUE    = 1 << 4;
      const LAVA_FLOOR = 1 << 5;
      const REBELLION2 = 1 << 6;
      const MUSIC      = 1 << 7;
      const TERM_PAUSE = 1 << 8;
      const M1_MONSTER = 1 << 9;
      const M1_WEPS    = 1 << 10;
   }
}

bitflags! {
   /// Static entry point flags.
   #[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
   pub struct EntFlags: u32
   {
      const SOLO    = 1;
      const CO_OP   = 1 << 1;
      const CARNAGE = 1 << 2;
      const KTMWTB  = 1 << 3;
      const KOTH    = 1 << 4;
      const DEFENSE = 1 << 5;
      const RUGBY   = 1 << 6;
      const CTF     = 1 << 7;
   }
}

bitflags! {
   /// Static mission flags.
   #[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
   pub struct MsnFlags: u16
   {
      const EXTERMINATION = 1;
      const EXPLORATION   = 1 << 1;
      const RETRIEVAL     = 1 << 2;
      const REPAIR        = 1 << 3;
      const RESCUE        = 1 << 4;
   }
}

bitflags! {
   /// Flags for `Polygon`.
   #[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
   pub struct PolyFlags: u16
   {
      const DETACHED = 1 << 14;
   }
}

bitflags! {
   /// Flags for `Light`.
   #[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
   pub struct LightFlags: u16
   {
      const INIT_ACTIVE = 1;
      const SLAVE_VALUE = 1 << 1;
      const STATELESS   = 1 << 2;
   }
}

bitflags! {
   /// Flags for `Object`.
   #[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
   pub struct ObjectFlags: u16
   {
      const INVISIBLE = 1;
      const CEILING   = 1 << 1;
      const BLIND     = 1 << 2;
      const DEAF      = 1 << 3;
      const FLOATING  = 1 << 4;
      const NET_ONLY  = 1 << 5;
   }
}

bitflags! {
   /// Flags for `Platform`.
   #[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
   pub struct PlatformFlags: u32
   {
      const INIT_ACTIVE           = 1;
      const INIT_EXTENDED         = 1 << 1;
      const STOP_AT_EACH_LEVEL    = 1 << 2;
      const STOP_AT_INIT_LEVEL    = 1 << 3;
      const START_ADJ_ON_STOP     = 1 << 4;
      const EXTENDS_FLOOR_TO_CEIL = 1 << 5;
      const COMES_FROM_FLOOR      = 1 << 6;
      const COMES_FROM_CEIL       = 1 << 7;
      const CAUSES_DAMAGE         = 1 << 8;
      const NO_ACTIVATE_PARENT    = 1 << 9;
      const ACTIVATES_ONCE        = 1 << 10;
      const ACTIVATES_LIGHT       = 1 << 11;
      const DEACTIVATES_LIGHT     = 1 << 12;
      const PLAYER_CONTROLS       = 1 << 13;
      const MONSTER_CONTROLS      = 1 << 14;
      const REVERSE_ON_OBSTRUCT   = 1 << 15;
      const NO_EXT_DEACTIVATION   = 1 << 16;
      const USE_POLYGON_HEIGHTS   = 1 << 17;
      const DELAYED_ACTIVATION    = 1 << 18;
      const START_ADJ_ON_START    = 1 << 19;
      const STOP_ADJ_ON_START     = 1 << 20;
      const STOP_ADJ_ON_STOP      = 1 << 21;
      const SLOW                  = 1 << 22;
      const START_AT_EACH_LEVEL   = 1 << 23;
      const LOCKED                = 1 << 24;
      const SECRET                = 1 << 25;
      const DOOR                  = 1 << 26;
   }
}

c_enum! {
   /// The texture type of a `Side`.
   #[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
   #[derive(Debug)]
   pub enum SideType: u16
   {
      0 => Full,
      1 => High,
      2 => Low,
      3 => Composite,
      4 => Split,
   }
}

c_enum! {
   /// The type of function for a `LightFunc`.
   #[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
   #[derive(Debug)]
   pub enum LightFuncType: u16
   {
      0 => Constant,
      1 => Linear,
      2 => Smooth,
      3 => Flicker,
      4 => Random,
      5 => Fluorescent,
   }
}

c_enum! {
   /// The type of a `Light`.
   #[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
   #[derive(Debug)]
   pub enum LightType: u16
   {
      0 => Normal,
      1 => Strobe,
      2 => Media,
   }
}

c_enum! {
   /// The liquid type of a `Media`.
   #[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
   #[derive(Debug)]
   pub enum MediaType: u16
   {
      0 => Water,
      1 => Lava,
      2 => Goo,
      3 => Sewage,
   }
}

/// The number of game ticks per second.
pub const TICKS_PER_SECOND: u16 = 30;

const OLD_LIGHT_DEFINITIONS: [Light; 8] = [
   // Normal
   Light{ltype: LightType::Normal,
         flags: LightFlags::SLAVE_VALUE,
         phase: 0,
         act_pri: LightFunc{ftype:   LightFuncType::Constant,
                            prd_nrm: TICKS_PER_SECOND,
                            prd_dta: 0,
                            val_nrm: Fixed::from_int(1),
                            val_dta: Fixed::from_int(0)},
         act_sec: LightFunc{ftype:   LightFuncType::Constant,
                            prd_nrm: TICKS_PER_SECOND,
                            prd_dta: 0,
                            val_nrm: Fixed::from_int(1),
                            val_dta: Fixed::from_int(0)},
         act_mid: LightFunc{ftype:   LightFuncType::Constant,
                            prd_nrm: 1,
                            prd_dta: 0,
                            val_nrm: Fixed::from_int(1),
                            val_dta: Fixed::from_int(0)},
         ina_pri: LightFunc{ftype:   LightFuncType::Constant,
                            prd_nrm: TICKS_PER_SECOND,
                            prd_dta: 0,
                            val_nrm: Fixed::from_int(0),
                            val_dta: Fixed::from_int(0)},
         ina_sec: LightFunc{ftype:   LightFuncType::Constant,
                            prd_nrm: TICKS_PER_SECOND,
                            prd_dta: 0,
                            val_nrm: Fixed::from_int(0),
                            val_dta: Fixed::from_int(0)},
         ina_mid: LightFunc{ftype:   LightFuncType::Constant,
                            prd_nrm: 1,
                            prd_dta: 0,
                            val_nrm: Fixed::from_int(1),
                            val_dta: Fixed::from_int(0)},
         tag: 0},

   // Rheostat
   Light{ltype: LightType::Normal,
         flags: LightFlags::SLAVE_VALUE,
         phase: 0,
         act_pri: LightFunc{ftype:   LightFuncType::Constant,
                            prd_nrm: TICKS_PER_SECOND,
                            prd_dta: 0,
                            val_nrm: Fixed::from_int(1),
                            val_dta: Fixed::from_int(0)},
         act_sec: LightFunc{ftype:   LightFuncType::Constant,
                            prd_nrm: TICKS_PER_SECOND,
                            prd_dta: 0,
                            val_nrm: Fixed::from_int(1),
                            val_dta: Fixed::from_int(0)},
         act_mid: LightFunc{ftype:   LightFuncType::Smooth,
                            prd_nrm: TICKS_PER_SECOND * 3,
                            prd_dta: 0,
                            val_nrm: Fixed::from_int(1),
                            val_dta: Fixed::from_int(0)},
         ina_pri: LightFunc{ftype:   LightFuncType::Constant,
                            prd_nrm: TICKS_PER_SECOND,
                            prd_dta: 0,
                            val_nrm: Fixed::from_int(0),
                            val_dta: Fixed::from_int(0)},
         ina_sec: LightFunc{ftype:   LightFuncType::Constant,
                            prd_nrm: TICKS_PER_SECOND,
                            prd_dta: 0,
                            val_nrm: Fixed::from_int(0),
                            val_dta: Fixed::from_int(0)},
         ina_mid: LightFunc{ftype:   LightFuncType::Smooth,
                            prd_nrm: TICKS_PER_SECOND * 3,
                            prd_dta: 0,
                            val_nrm: Fixed::from_int(0),
                            val_dta: Fixed::from_int(0)},
         tag: 0},

   // Flourescent
   Light{ltype: LightType::Normal,
         flags: LightFlags::SLAVE_VALUE,
         phase: 0,
         act_pri: LightFunc{ftype:   LightFuncType::Constant,
                            prd_nrm: TICKS_PER_SECOND,
                            prd_dta: 0,
                            val_nrm: Fixed::from_int(1),
                            val_dta: Fixed::from_int(0)},
         act_sec: LightFunc{ftype:   LightFuncType::Constant,
                            prd_nrm: TICKS_PER_SECOND,
                            prd_dta: 0,
                            val_nrm: Fixed::from_int(1),
                            val_dta: Fixed::from_int(0)},
         act_mid: LightFunc{ftype:   LightFuncType::Fluorescent,
                            prd_nrm: TICKS_PER_SECOND * 3,
                            prd_dta: 0,
                            val_nrm: Fixed::from_int(1),
                            val_dta: Fixed::from_int(0)},
         ina_pri: LightFunc{ftype:   LightFuncType::Constant,
                            prd_nrm: TICKS_PER_SECOND,
                            prd_dta: 0,
                            val_nrm: Fixed::from_int(0),
                            val_dta: Fixed::from_int(0)},
         ina_sec: LightFunc{ftype:   LightFuncType::Constant,
                            prd_nrm: TICKS_PER_SECOND,
                            prd_dta: 0,
                            val_nrm: Fixed::from_int(0),
                            val_dta: Fixed::from_int(0)},
         ina_mid: LightFunc{ftype:   LightFuncType::Constant,
                            prd_nrm: 1,
                            prd_dta: 0,
                            val_nrm: Fixed::from_int(0),
                            val_dta: Fixed::from_int(0)},
         tag: 0},

   // Strobe
   Light{ltype: LightType::Normal,
         flags: LightFlags::SLAVE_VALUE,
         phase: 0,
         act_pri: LightFunc{ftype:   LightFuncType::Constant,
                            prd_nrm: TICKS_PER_SECOND,
                            prd_dta: 0,
                            val_nrm: Fixed::from_int(1),
                            val_dta: Fixed::from_int(0)},
         act_sec: LightFunc{ftype:   LightFuncType::Constant,
                            prd_nrm: TICKS_PER_SECOND,
                            prd_dta: 0,
                            val_nrm: Fixed::from_int(0),
                            val_dta: Fixed::from_int(0)},
         act_mid: LightFunc{ftype:   LightFuncType::Constant,
                            prd_nrm: 1,
                            prd_dta: 0,
                            val_nrm: Fixed::from_int(1),
                            val_dta: Fixed::from_int(0)},
         ina_pri: LightFunc{ftype:   LightFuncType::Constant,
                            prd_nrm: TICKS_PER_SECOND,
                            prd_dta: 0,
                            val_nrm: Fixed::from_int(0),
                            val_dta: Fixed::from_int(0)},
         ina_sec: LightFunc{ftype:   LightFuncType::Constant,
                            prd_nrm: TICKS_PER_SECOND,
                            prd_dta: 0,
                            val_nrm: Fixed::from_int(1),
                            val_dta: Fixed::from_int(0)},
         ina_mid: LightFunc{ftype:   LightFuncType::Constant,
                            prd_nrm: 1,
                            prd_dta: 0,
                            val_nrm: Fixed::from_int(0),
                            val_dta: Fixed::from_int(0)},
         tag: 0},

   // Flicker
   Light{ltype: LightType::Normal,
         flags: LightFlags::SLAVE_VALUE,
         phase: 0,
         act_pri: LightFunc{ftype:   LightFuncType::Constant,
                            prd_nrm: TICKS_PER_SECOND,
                            prd_dta: 0,
                            val_nrm: Fixed::from_int(1),
                            val_dta: Fixed::from_int(0)},
         act_sec: LightFunc{ftype:   LightFuncType::Constant,
                            prd_nrm: TICKS_PER_SECOND,
                            prd_dta: 0,
                            val_nrm: Fixed::from_int(1),
                            val_dta: Fixed::from_int(0)},
         act_mid: LightFunc{ftype:   LightFuncType::Flicker,
                            prd_nrm: TICKS_PER_SECOND * 3,
                            prd_dta: 0,
                            val_nrm: Fixed::from_int(1),
                            val_dta: Fixed::from_int(0)},
         ina_pri: LightFunc{ftype:   LightFuncType::Constant,
                            prd_nrm: TICKS_PER_SECOND,
                            prd_dta: 0,
                            val_nrm: Fixed::from_int(0),
                            val_dta: Fixed::from_int(0)},
         ina_sec: LightFunc{ftype:   LightFuncType::Constant,
                            prd_nrm: TICKS_PER_SECOND,
                            prd_dta: 0,
                            val_nrm: Fixed::from_int(0),
                            val_dta: Fixed::from_int(0)},
         ina_mid: LightFunc{ftype:   LightFuncType::Constant,
                            prd_nrm: 1,
                            prd_dta: 0,
                            val_nrm: Fixed::from_int(0),
                            val_dta: Fixed::from_int(0)},
         tag: 0},

   // Pulsate
   Light{ltype: LightType::Normal,
         flags: LightFlags::SLAVE_VALUE,
         phase: 0,
         act_pri: LightFunc{ftype:   LightFuncType::Smooth,
                            prd_nrm: TICKS_PER_SECOND * 2,
                            prd_dta: 0,
                            val_nrm: Fixed::from_int(1),
                            val_dta: Fixed::from_int(0)},
         act_sec: LightFunc{ftype:   LightFuncType::Smooth,
                            prd_nrm: TICKS_PER_SECOND * 2 - 1,
                            prd_dta: 0,
                            val_nrm: Fixed::from_int(0),
                            val_dta: Fixed::from_int(0)},
         act_mid: LightFunc{ftype:   LightFuncType::Smooth,
                            prd_nrm: TICKS_PER_SECOND * 2 - 1,
                            prd_dta: 0,
                            val_nrm: Fixed::from_int(1),
                            val_dta: Fixed::from_int(0)},
         ina_pri: LightFunc{ftype:   LightFuncType::Smooth,
                            prd_nrm: TICKS_PER_SECOND * 2,
                            prd_dta: 0,
                            val_nrm: Fixed::from_int(0),
                            val_dta: Fixed::from_int(0)},
         ina_sec: LightFunc{ftype:   LightFuncType::Smooth,
                            prd_nrm: TICKS_PER_SECOND * 2 - 1,
                            prd_dta: 0,
                            val_nrm: Fixed::from_int(1),
                            val_dta: Fixed::from_int(0)},
         ina_mid: LightFunc{ftype:   LightFuncType::Smooth,
                            prd_nrm: TICKS_PER_SECOND * 2,
                            prd_dta: 0,
                            val_nrm: Fixed::from_int(0),
                            val_dta: Fixed::from_int(0)},
         tag: 0},

   // Annoying
   Light{ltype: LightType::Normal,
         flags: LightFlags::SLAVE_VALUE,
         phase: 0,
         act_pri: LightFunc{ftype:   LightFuncType::Random,
                            prd_nrm: 2,
                            prd_dta: 1,
                            val_nrm: Fixed::from_int(1),
                            val_dta: Fixed::from_int(0)},
         act_sec: LightFunc{ftype:   LightFuncType::Constant,
                            prd_nrm: 2,
                            prd_dta: 0,
                            val_nrm: Fixed::from_int(0),
                            val_dta: Fixed::from_int(0)},
         act_mid: LightFunc{ftype:   LightFuncType::Random,
                            prd_nrm: 1,
                            prd_dta: 0,
                            val_nrm: Fixed::from_int(1),
                            val_dta: Fixed::from_int(0)},
         ina_pri: LightFunc{ftype:   LightFuncType::Constant,
                            prd_nrm: TICKS_PER_SECOND,
                            prd_dta: 0,
                            val_nrm: Fixed::from_int(0),
                            val_dta: Fixed::from_int(0)},
         ina_sec: LightFunc{ftype:   LightFuncType::Constant,
                            prd_nrm: TICKS_PER_SECOND,
                            prd_dta: 0,
                            val_nrm: Fixed::from_int(0),
                            val_dta: Fixed::from_int(0)},
         ina_mid: LightFunc{ftype:   LightFuncType::Constant,
                            prd_nrm: TICKS_PER_SECOND,
                            prd_dta: 0,
                            val_nrm: Fixed::from_int(0),
                            val_dta: Fixed::from_int(0)},
         tag: 0},

   // Energy Efficient
   Light{ltype: LightType::Normal,
         flags: LightFlags::SLAVE_VALUE,
         phase: 0,
         act_pri: LightFunc{ftype:   LightFuncType::Constant,
                            prd_nrm: TICKS_PER_SECOND,
                            prd_dta: 0,
                            val_nrm: Fixed::from_int(1),
                            val_dta: Fixed::from_int(0)},
         act_sec: LightFunc{ftype:   LightFuncType::Constant,
                            prd_nrm: TICKS_PER_SECOND,
                            prd_dta: 0,
                            val_nrm: Fixed::from_int(0),
                            val_dta: Fixed::from_int(0)},
         act_mid: LightFunc{ftype:   LightFuncType::Linear,
                            prd_nrm: TICKS_PER_SECOND * 2,
                            prd_dta: 0,
                            val_nrm: Fixed::from_int(1),
                            val_dta: Fixed::from_int(0)},
         ina_pri: LightFunc{ftype:   LightFuncType::Constant,
                            prd_nrm: TICKS_PER_SECOND,
                            prd_dta: 0,
                            val_nrm: Fixed::from_int(0),
                            val_dta: Fixed::from_int(0)},
         ina_sec: LightFunc{ftype:   LightFuncType::Constant,
                            prd_nrm: TICKS_PER_SECOND,
                            prd_dta: 0,
                            val_nrm: Fixed::from_int(0),
                            val_dta: Fixed::from_int(0)},
         ina_mid: LightFunc{ftype:   LightFuncType::Linear,
                            prd_nrm: TICKS_PER_SECOND * 2,
                            prd_dta: 0,
                            val_nrm: Fixed::from_int(0),
                            val_dta: Fixed::from_int(0)},
         tag: 0},
];

// EOF
