(Ord: OrderedType) = struct
  
  type data = Ord.t
  type heap = {
    
    mutable hp_heap: data array;
    
    mutable hp_length: int;
    
    hp_null_element: data;
  }
  type t = heap
  let compare_min =
    Ord.compare
  let compare_max x y =
    Ord.compare y x
  
  let create (null_element: data) : heap = {
    hp_heap = [| |];
    hp_length = 0;
    hp_null_element = null_element;
  }
  
  let to_string (heap: heap) : string =
    let rec to_string' i =
      if i >= heap.hp_length then
        ""
      else
        string_of_int i ^ ": " ^ Ord.to_string heap.hp_heap.(i) ^ "\n" ^ to_string' (i + 1)
    in
      to_string' 0
  
  
  let is_min_level (index: int) : bool =
    let level =
      log (float_of_int (index + 1)) /. log (float_of_int 2)
    in
      
      ((int_of_float level) mod 2) = 0
  
  let left_child_index (index: int) : int =
    index * 2 + 1
  
  let right_child_index (index: int) : int =
    index * 2 + 2
  
  let parent_index (index: int) : int =
    (index - 1) / 2
  
  let within_bound (heap: heap) (index: int) : bool =
    index < heap.hp_length
  
  let min_entry (compare: data -> data -> int) (heap: heap) (first_index: int) (second_index: int) : int =
    if compare heap.hp_heap.(first_index) heap.hp_heap.(second_index) <= 0 then
      first_index
    else
      second_index
  
  let min_with_descendants (compare: data -> data -> int) (heap: heap) (index: int) : int =
    let left_child =
      left_child_index index
    in
      
      if within_bound heap left_child then begin
        let right_child =
          right_child_index index
        in
          
          if within_bound heap right_child then
            min_entry compare heap index (min_entry compare heap left_child right_child)
          
          else
            min_entry compare heap index left_child
      end
        
      
      else
        index
        
      
  
  
  let increase_heap_size (heap: heap) (data: data) : unit =
    if heap.hp_length >= Array.length heap.hp_heap then begin
      
      if heap.hp_length = Sys.max_array_length then begin
        raise OVERFLOW;
      end;
      let new_size =
        let desired_size = 
          Tools.max_int 256 (2 * (Array.length heap.hp_heap))
        in
          
          Pervasives.min Sys.max_array_length desired_size
      in
        let new_heap =
          Array.make new_size data
        in
          Array.blit heap.hp_heap 0 new_heap 0 (Array.length heap.hp_heap);
          heap.hp_heap <- new_heap;
    end
  
  let rec sift_up' (compare: data -> data -> int) (heap: heap) (data: data) (index: int) : unit =
    
    if index > 2 then begin
      let grand_parent : int =
        parent_index (parent_index index)
      in
        if compare data heap.hp_heap.(grand_parent) < 0 then begin
          
          heap.hp_heap.(index) <- heap.hp_heap.(grand_parent);
          sift_up' compare heap data grand_parent
        end
        else
          
          heap.hp_heap.(index) <- data;
    end
    else begin
      
      heap.hp_heap.(index) <- data;
    end
  
  let rec sift_up (heap: heap) (data: data) (index: int) : unit =
    if is_min_level index then begin
      let parent =
        parent_index index
      in
        if Ord.compare data heap.hp_heap.(parent) > 0 then begin
          
          heap.hp_heap.(index) <- heap.hp_heap.(parent);
          sift_up' compare_max heap data parent
        end
        else begin
          
          sift_up' compare_min heap data index
        end
    end
    else begin
      let parent =
        parent_index index
      in
        if Ord.compare data heap.hp_heap.(parent) < 0 then begin
          
          heap.hp_heap.(index) <- heap.hp_heap.(parent);
          sift_up' compare_min heap data parent
        end
        else begin
          
          sift_up' compare_max heap data index
        end
    end
  let add' (heap: heap) (data: data) : unit =
    increase_heap_size heap data;
    if heap.hp_length = 0 then
      heap.hp_heap.(0) <- data
    else
      sift_up heap data heap.hp_length;
    heap.hp_length <- heap.hp_length + 1
  
  
  let rec sift_down (compare: data -> data -> int) (heap: heap) (data: data) (index: int) : unit =
    let left_child =
      left_child_index index
    in
      
      if within_bound heap left_child then begin
        
        let min_left_index =
          min_with_descendants compare heap left_child
        in
        let right_child =
          right_child_index index
        in
          
        
        let min_children_index =
          
          if within_bound heap right_child then begin
            min_entry compare heap min_left_index (min_with_descendants compare heap right_child)
          end
            
          
          else
            min_left_index
        in
          
          if compare data heap.hp_heap.(min_children_index) <= 0 then begin
            heap.hp_heap.(index) <- data;
          end
          
          else if
            min_children_index = left_child
            ||
            min_children_index = right_child
          then begin
            
            heap.hp_heap.(index) <- heap.hp_heap.(min_children_index);
            heap.hp_heap.(min_children_index) <- data;
          end
          
          else begin
            
            heap.hp_heap.(index) <- heap.hp_heap.(min_children_index);
            
            
            let parent_index =
              parent_index min_children_index
            in
              if compare data heap.hp_heap.(parent_index) > 0 then begin
                let new_data =
                  heap.hp_heap.(parent_index)
                in
                  heap.hp_heap.(parent_index) <- data;
                  sift_down compare heap new_data min_children_index
              end
                
              else begin
                
                sift_down compare heap data min_children_index
              end
          end
    end
    
    else begin
      heap.hp_heap.(index) <- data;
    end
  
  let min (heap: heap) : data = 
    if heap.hp_length <= 0 then begin
      raise Not_found
    end;
    heap.hp_heap.(0)
  let remove_min' (heap: heap) : data = 
    let data =
      min heap
    in
      heap.hp_length <- heap.hp_length - 1;
      
      if heap.hp_length > 0 then begin
        sift_down compare_min heap heap.hp_heap.(heap.hp_length) 0
      end;
      heap.hp_heap.(heap.hp_length) <- heap.hp_null_element;
      data
    
  let max_index (heap: heap) : int = 
    if heap.hp_length <= 0 then
      raise Not_found
    else if heap.hp_length = 1 then
      0
    else if heap.hp_length = 2 then
      1
    else if Ord.compare heap.hp_heap.(1) heap.hp_heap.(2) > 0 then
      1
    else
      2
  let max (heap: heap) : data = 
    heap.hp_heap.(max_index heap)
  let remove_max' (heap: heap) : data =
    let index =
      max_index heap
    in
    let data =
      heap.hp_heap.(index)
    in
      heap.hp_length <- heap.hp_length - 1;
      
      
      
      if heap.hp_length > 1 then begin
        sift_down compare_max heap heap.hp_heap.(heap.hp_length) index
      end;
      heap.hp_heap.(heap.hp_length) <- heap.hp_null_element;
      data
  
  
  let check (heap : heap) : unit =
    if Const.debug then begin
      
      for i = 0 to heap.hp_length - 1 do
        if heap.hp_heap.(i) == heap.hp_null_element then
          failwith "Min_max_heap.check: null_element"
      done;
      
      
      for i = 0 to heap.hp_length - 1 do
        let check_at index =
          if is_min_level i then begin
            if
              within_bound heap index
              &&
              Ord.compare heap.hp_heap.(i) heap.hp_heap.(index) > 0
            then
              failwith "Min_max_heap.check: min_level"
          end
          else begin
            if
              within_bound heap index
              &&
              Ord.compare heap.hp_heap.(i) heap.hp_heap.(index) < 0
            then
              failwith ("Min_max_heap.check: max_level " ^ string_of_int i ^ " <-> " ^ string_of_int index);
          end
        in
          check_at (left_child_index i);
          check_at (left_child_index (left_child_index i));
          check_at (right_child_index (left_child_index i));
          check_at (right_child_index i);
          check_at (left_child_index (right_child_index i));
          check_at (right_child_index (right_child_index i));
      done;
      
      begin
        let heap' = { heap with
                        hp_heap = Array.copy heap.hp_heap
                    }
        in
        let last_min =
          ref None
        in
          while heap'.hp_length > 0 do
            let min =
              remove_min' heap'
            in
              begin
                match !last_min with
                  | None ->
                      ()
                        
                  | Some last ->
                      if Ord.compare last min >= 0 then begin
                        print_endline ("PREV: " ^ Ord.to_string last);
                        failwith "Heap.check' min";
                      end
              end;
              last_min := Some min
          done
      end;
      
      begin
        
        let heap' = { heap with
                        hp_heap = Array.copy heap.hp_heap
                    }
        in
        let last_max =
          ref None
        in
          while heap'.hp_length > 0 do
            let max =
              remove_max' heap'
            in
              
              begin
                match !last_max with
                  | None ->
                      ()
                        
                  | Some last ->
                      if Ord.compare last max <= 0 then begin
                        print_endline ("PREV: " ^ Ord.to_string last);
                        failwith "Heap.check' max";
                      end
              end;
              last_max := Some max
          done
      end;
    end
  
  let add (heap: heap) (data: data) : unit =
    add' heap data;
    check heap
  let remove_min (heap: heap) : data =
    let min =
      remove_min' heap
    in
      check heap;
      min
  let remove_max (heap: heap) : data =
    let max =
      remove_max' heap
    in
      check heap;
      max
  
  let iter (func: data -> unit) (heap: heap) : unit =
    for i = 0 to heap.hp_length - 1 do
      func heap.hp_heap.(i)
    done
  
  let is_empty (heap: heap) : bool =
    heap.hp_length = 0
    
  let size (heap: heap) : int =
    heap.hp_length
end