text: # (C) Copyright 2020 ECMWF.
  #
  # This software is licensed under the terms of the Apache Licence Version 2.0
  # which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
  # In applying this licence, ECMWF does not waive the privileges and immunities
  # granted to it by virtue of its status as an intergovernmental organisation
  # nor does it submit to any jurisdiction.
  #
  
  from climetlab.wrappers import get_wrapper
  
  
  def _normalize(lon, minimum):
      while lon < minimum:
          lon += 360
  
      while lon >= minimum + 360:
          lon -= 360
  
      return lon
  
  
  class BoundingBox:
      def __init__(self, *, north, west, south, east):
  
          # Convert to float as these values may come from Numpy
          self.north = min(float(north), 90.0)
          self.south = max(float(south), -90.0)
  
          self.is_periodic_west_east = (east - west) == 360
          self.west = _normalize(float(west), -180)  # Or 0?
  
          if self.is_periodic_west_east:
              self.east = self.west + 360
          else:
              self.east = _normalize(float(east), self.west)
  
          if self.north < self.south:
              raise ValueError(
                  f"Invalid bounding box, north={self.north} < south={self.south}"
              )
  
          if self.west > self.east:
              raise ValueError(
                  f"Invalid bounding box, west={self.west} > east={self.east}"
              )
  
          if self.east > self.west + 360:
              raise ValueError(
                  f"Invalid bounding box, east={self.east} > west={self.west}+360"
              )
  
      def __repr__(self):
          return "BoundingBox(north=%g,west=%g,south=%g,east=%g)" % (
              self.north,
              self.west,
              self.south,
              self.east,
          )
  
      def __eq__(self, other):
          if self.__class__ is not other.__class__:
              return False
  
          return self.as_tuple() == other.as_tuple()
  
      @property
      def width(self):
          return self.east - self.west
  
      @property
      def height(self):
          return self.north - self.south
  
      @classmethod
      def multi_merge(cls, bboxes):
  
          north = max(z.north for z in bboxes)
          south = min(z.south for z in bboxes)
  
          first = bboxes[0]
  
          origin = first.east % 360
          full = BoundingBox(
              north=north,
              west=first.west,
              south=south,
              east=first.west + 360,
          )
  
          # Build the list of provided [west, east] intervals
  
          boundaries = list()
          stacked_intervals = set()  # To keep track of overlapping intervals
          for i, b in enumerate(bboxes):
  
              if b.east - b.west == 360:
                  return full
  
              west = (b.west - origin) % 360
              east = (b.east - origin) % 360
  
              if west > east:
                  stacked_intervals.add(i)
  
              boundaries.append((west, True, i))
              boundaries.append((east, False, i))
  
          boundaries = sorted(boundaries)
  
          start = 0
          best = -1
          west = 0
          east = 0
  
          # Find the longest interval *outside* the provided [west, east] intervals
          # It's complement will be the result
  
          for cursor, entering, interval in boundaries:
  
              if entering:
                  if not stacked_intervals:
                      distance = cursor - start
                      if distance > best:
                          best = distance
                          west = cursor
                          east = start
  
                      start = None
  
                  stacked_intervals.add(interval)
  
              else:  # exiting
                  stacked_intervals.remove(interval)
                  if not stacked_intervals:
                      start = cursor
  
          if best <= 0:
              return full
  
          return BoundingBox(
              north=north,
              west=origin + west,
              south=south,
              east=origin + east,
          )
  
      def merge(self, other):
          return self.multi_merge([self, other])
  
      def add_margins(self, margins):
  
          if isinstance(margins, str) and margins[-1] == "%":
              margins = int(margins[:-1]) / 100.0
              margins = max(
                  (self.north - self.south) * margins, (self.east - self.west) * margins
              )
  
          # TODO:check east/west
          margins_lat = margins
          margins_lon = margins
  
          if self.east - self.west > 360 - 2 * margins:
              margins = (360 - (self.east - self.west)) / 2.0
  
          return BoundingBox(
              north=self.north + margins_lat,
              west=self.west - margins_lon,
              south=self.south - margins_lat,
              east=self.east + margins_lon,
          )
  
      def as_list(self):
          return [self.north, self.west, self.south, self.east]
  
      def as_tuple(self):
          return (self.north, self.west, self.south, self.east)
  
      def as_dict(self):
          return dict(north=self.north, west=self.west, south=self.south, east=self.east)
  
  
  def to_bounding_box(obj):
  
      if isinstance(obj, BoundingBox):
          return obj
  
      if isinstance(obj, (list, tuple)):
          return BoundingBox(north=obj[0], west=obj[1], south=obj[2], east=obj[3])
  
      print(obj)
      obj = get_wrapper(obj)
  
      return to_bounding_box(obj.to_bounding_box())
  