///|
struct RawFrame {
  filename : String?
  frame : Rect
  rotated : Bool
  trimmed : Bool
  spriteSourceSize : Rect
  sourceSize : Size
  duration : Int
} derive(Debug, Eq, ToJson, FromJson)

///|
struct RawFrameTag {
  name : String
  from : Int
  to : Int
  direction : String
} derive(Debug, Eq, ToJson, FromJson)

///|
struct RawSliceKey {
  frame : Int
  bounds : Rect
  center : Rect?
  pivot : Point?
} derive(Debug, Eq, ToJson, FromJson)

///|
struct RawSlice {
  name : String
  color : String?
  keys : Array[RawSliceKey]
} derive(Debug, Eq, ToJson, FromJson)

///|
struct RawMeta {
  image : String
  size : Size
  frameTags : Array[RawFrameTag]?
  slices : Array[RawSlice]?
} derive(Debug, Eq, ToJson, FromJson)

///|
struct RawAsepriteSheet {
  frames : Array[RawFrame]
  meta : RawMeta
} derive(Debug, Eq, ToJson, FromJson)

///|
struct RawObjectAsepriteSheet {
  frames : Map[String, RawFrame]
  meta : RawMeta
} derive(Debug, Eq, ToJson, FromJson)

///|
pub fn Direction::from_string(text : String) -> Direction {
  match text {
    "reverse" => Reverse
    "pingpong" => PingPong
    _ => Forward
  }
}

///|
fn normalize_tag(tag : RawFrameTag) -> FrameTag {
  {
    name: tag.name,
    from: tag.from,
    to: tag.to,
    direction: Direction::from_string(tag.direction),
  }
}

///|
fn normalize_slice(slice : RawSlice) -> Slice {
  let keys : Array[SliceKey] = slice.keys.map(fn(key) {
    {
      frame: key.frame,
      bounds: key.bounds,
      center: key.center,
      pivot: key.pivot,
    }
  })
  let first : SliceKey = match keys.get(0) {
    Some(key) => key
    None =>
      {
        frame: 0,
        bounds: { x: 0, y: 0, w: 0, h: 0 },
        center: None,
        pivot: None,
      }
  }
  {
    name: slice.name,
    color: slice.color,
    bounds: first.bounds,
    center: first.center,
    pivot: first.pivot,
    keys,
  }
}

///|
fn normalize_frame(frame : RawFrame, fallback_name : String) -> SourceFrame {
  {
    filename: match frame.filename {
      Some(name) => name
      None => fallback_name
    },
    frame: frame.frame,
    rotated: frame.rotated,
    trimmed: frame.trimmed,
    sprite_source_size: frame.spriteSourceSize,
    source_size: frame.sourceSize,
    duration: frame.duration,
    boxes: [],
  }
}

///|
fn RawAsepriteSheet::normalize(self : RawAsepriteSheet) -> SpriteSheet {
  {
    image: self.meta.image,
    size: self.meta.size,
    frames: self.frames.map(fn(frame) {
      let name = match frame.filename {
        Some(name) => name
        None => ""
      }
      normalize_frame(frame, name)
    }),
    tags: self.meta.frameTags.unwrap_or([]).map(normalize_tag),
    slices: self.meta.slices.unwrap_or([]).map(normalize_slice),
  }
}

///|
fn RawObjectAsepriteSheet::normalize(
  self : RawObjectAsepriteSheet,
) -> SpriteSheet {
  let entries = self.frames.to_array()
  entries.sort_by(fn(a, b) { a.0.compare(b.0) })
  {
    image: self.meta.image,
    size: self.meta.size,
    frames: entries.map(fn(entry) { normalize_frame(entry.1, entry.0) }),
    tags: self.meta.frameTags.unwrap_or([]).map(normalize_tag),
    slices: self.meta.slices.unwrap_or([]).map(normalize_slice),
  }
}

///|
pub fn parse_aseprite_json(text : String) -> SpriteSheet raise {
  let value = @json.parse(text)
  try {
    let raw : RawAsepriteSheet = @json.from_json(value)
    raw.normalize()
  } catch {
    _ => {
      let raw : RawObjectAsepriteSheet = @json.from_json(value)
      raw.normalize()
    }
  }
}