// ============================================================================
// Pattern Fill
// ============================================================================

///|
/// Pattern definition for fills
pub(all) struct Pattern {
  id : String
  x : Double
  y : Double
  width : Double // Pattern tile width
  height : Double // Pattern tile height
  content : Array[SVGNode] // Pattern content
  pattern_units : PatternUnits
  pattern_content_units : PatternUnits
  transform : Transform
  view_box : ViewBox?
  preserve_aspect_ratio : PreserveAspectRatio
}

///|
pub(all) enum PatternUnits {
  UserSpaceOnUse // Coordinates relative to current user space
  ObjectBoundingBox // Coordinates relative to bounding box (0-1)
}

///|
pub fn Pattern::new(
  id : String,
  width : Double,
  height : Double,
  content : Array[SVGNode],
) -> Pattern {
  {
    id,
    x: 0.0,
    y: 0.0,
    width,
    height,
    content,
    pattern_units: ObjectBoundingBox,
    pattern_content_units: UserSpaceOnUse,
    transform: Transform::identity(),
    view_box: None,
    preserve_aspect_ratio: PreserveAspectRatio::default(),
  }
}

///|
/// Pattern registry
priv struct PatternRegistry {
  patterns : Map[String, Pattern]
  references : Map[String, String]
  specified : Map[String, Int]
}

///|
fn PatternRegistry::new() -> PatternRegistry {
  { patterns: Map([]), references: Map([]), specified: Map([]) }
}

///|
fn PatternRegistry::add(self : PatternRegistry, pattern : Pattern) -> Unit {
  self.patterns.set(pattern.id, pattern)
  self.specified.set(pattern.id, 0x7FFFFFFF)
  let _ = self.references.remove(pattern.id)
}

///|
fn PatternRegistry::add_definition(
  self : PatternRegistry,
  pattern : Pattern,
  reference : String?,
  specified : Int,
) -> Unit {
  self.patterns.set(pattern.id, pattern)
  self.specified.set(pattern.id, specified)
  match reference {
    Some(id) => self.references.set(pattern.id, id)
    None => {
      let _ = self.references.remove(pattern.id)
    }
  }
}

///|
fn string_array_contains(values : Array[String], value : String) -> Bool {
  for item in values {
    if item == value {
      return true
    }
  }
  false
}

///|
fn PatternRegistry::resolve_inner(
  self : PatternRegistry,
  id : String,
  visiting : Array[String],
) -> Pattern? {
  if string_array_contains(visiting, id) {
    return None
  }
  let current = match self.patterns.get(id) {
    Some(pattern) => pattern
    None => return None
  }
  let parent_id = match self.references.get(id) {
    Some(value) => value
    None => return Some(current)
  }
  let next_visiting = visiting.copy()
  next_visiting.push(id)
  let parent = match self.resolve_inner(parent_id, next_visiting) {
    Some(pattern) => pattern
    None => return None
  }
  let specified = self.specified.get(id).unwrap_or(0)
  Some({
    id: current.id,
    x: if (specified & 1) != 0 {
      current.x
    } else {
      parent.x
    },
    y: if (specified & 2) != 0 {
      current.y
    } else {
      parent.y
    },
    width: if (specified & 4) != 0 {
      current.width
    } else {
      parent.width
    },
    height: if (specified & 8) != 0 {
      current.height
    } else {
      parent.height
    },
    content: if (specified & 512) != 0 {
      current.content
    } else {
      parent.content
    },
    pattern_units: if (specified & 16) != 0 {
      current.pattern_units
    } else {
      parent.pattern_units
    },
    pattern_content_units: if (specified & 32) != 0 {
      current.pattern_content_units
    } else {
      parent.pattern_content_units
    },
    transform: if (specified & 64) != 0 {
      current.transform
    } else {
      parent.transform
    },
    view_box: if (specified & 128) != 0 {
      current.view_box
    } else {
      parent.view_box
    },
    preserve_aspect_ratio: if (specified & 256) != 0 {
      current.preserve_aspect_ratio
    } else {
      parent.preserve_aspect_ratio
    },
  })
}

///|
fn PatternRegistry::resolve(self : PatternRegistry, id : String) -> Pattern? {
  self.resolve_inner(id, [])
}

///|
/// Gradient definitions
pub(all) enum Gradient {
  Linear(LinearGradient)
  Radial(RadialGradient)
}

///|
/// Gradient registry
priv struct GradientRegistry {
  gradients : Map[String, Gradient]
  references : Map[String, String]
  specified : Map[String, Int]
}

///|
fn GradientRegistry::new() -> GradientRegistry {
  { gradients: Map([]), references: Map([]), specified: Map([]) }
}

///|
fn GradientRegistry::add(
  self : GradientRegistry,
  id : String,
  gradient : Gradient,
) -> Unit {
  self.gradients.set(id, gradient)
  self.specified.set(id, 0x7FFFFFFF)
  let _ = self.references.remove(id)
}

///|
fn GradientRegistry::add_definition(
  self : GradientRegistry,
  id : String,
  gradient : Gradient,
  reference : String?,
  specified : Int,
) -> Unit {
  self.gradients.set(id, gradient)
  self.specified.set(id, specified)
  match reference {
    Some(parent) => self.references.set(id, parent)
    None => {
      let _ = self.references.remove(id)
    }
  }
}

///|
fn GradientRegistry::resolve_inner(
  self : GradientRegistry,
  id : String,
  visiting : Array[String],
) -> Gradient? {
  if string_array_contains(visiting, id) {
    return None
  }
  let current = match self.gradients.get(id) {
    Some(gradient) => gradient
    None => return None
  }
  let parent_id = match self.references.get(id) {
    Some(value) => value
    None => return Some(current)
  }
  let next_visiting = visiting.copy()
  next_visiting.push(id)
  let parent = match self.resolve_inner(parent_id, next_visiting) {
    Some(gradient) => gradient
    None => return None
  }
  let specified = self.specified.get(id).unwrap_or(0)
  match (current, parent) {
    (Linear(child), Linear(base)) =>
      Some(
        Linear({
          x1: if (specified & 1) != 0 {
            child.x1
          } else {
            base.x1
          },
          y1: if (specified & 2) != 0 {
            child.y1
          } else {
            base.y1
          },
          x2: if (specified & 4) != 0 {
            child.x2
          } else {
            base.x2
          },
          y2: if (specified & 8) != 0 {
            child.y2
          } else {
            base.y2
          },
          stops: if (specified & 128) != 0 {
            child.stops
          } else {
            base.stops
          },
          spread_method: if (specified & 16) != 0 {
            child.spread_method
          } else {
            base.spread_method
          },
          units: if (specified & 32) != 0 {
            child.units
          } else {
            base.units
          },
          transform: if (specified & 64) != 0 {
            child.transform
          } else {
            base.transform
          },
        }),
      )
    (Radial(child), Radial(base)) =>
      Some(
        Radial({
          cx: if (specified & 1) != 0 {
            child.cx
          } else {
            base.cx
          },
          cy: if (specified & 2) != 0 {
            child.cy
          } else {
            base.cy
          },
          fx: if (specified & 8) != 0 {
            child.fx
          } else {
            base.fx
          },
          fy: if (specified & 16) != 0 {
            child.fy
          } else {
            base.fy
          },
          r: if (specified & 4) != 0 {
            child.r
          } else {
            base.r
          },
          stops: if (specified & 256) != 0 {
            child.stops
          } else {
            base.stops
          },
          spread_method: if (specified & 32) != 0 {
            child.spread_method
          } else {
            base.spread_method
          },
          units: if (specified & 64) != 0 {
            child.units
          } else {
            base.units
          },
          transform: if (specified & 128) != 0 {
            child.transform
          } else {
            base.transform
          },
        }),
      )
    _ => Some(current)
  }
}

///|
fn GradientRegistry::resolve(self : GradientRegistry, id : String) -> Gradient? {
  self.resolve_inner(id, [])
}