CREATE OR REPLACE FUNCTION int4range_list_accum(rangelist_in int4range[], newvalue int)
  RETURNS int4range[]
  LANGUAGE plpgsql
  STRICT
  IMMUTABLE
AS $$
DECLARE
  range  int4range;
  index  int;
  x      boolean;
BEGIN
  index = 0;
  FOREACH range IN ARRAY rangelist_in LOOP
    index := index + 1;
    IF newvalue <@ range THEN
      RETURN rangelist_in;
    END IF;
    IF newvalue = (lower(range) - 1) THEN
      -- extend range lower
      RETURN rangelist_in[1:index - 1]
          || int4range(newvalue, upper(range))
          || rangelist_in[index + 1:array_upper(rangelist_in, 1)];
    END IF;
    IF newvalue < (lower(range)) THEN
      -- add range before
      RETURN rangelist_in[1:index - 1]
          || int4range(newvalue, newvalue + 1)
          || rangelist_in[index:array_upper(rangelist_in, 1)];
    END IF;
    IF newvalue = (upper(range)) THEN
      x := CASE
             WHEN array_upper(rangelist_in, 1) <= index THEN FALSE
             WHEN newvalue < lower(rangelist_in[index + 1]) - 1 THEN FALSE
             ELSE TRUE
           END;
      IF x THEN
        -- combine adjacent ranges
        RETURN rangelist_in[1:index - 1]
            || int4range(lower(range), upper(rangelist_in[index + 1]))
            || rangelist_in[index + 2:array_upper(rangelist_in, 1)];
      ELSE
        -- extend range higher
        RETURN rangelist_in[1:index - 1]
            || int4range(lower(range), newvalue + 1)
            || rangelist_in[index + 1:array_upper(rangelist_in, 1)];
      END IF;
    END IF;
  END LOOP;
  -- add range at end
  RETURN rangelist_in || int4range(newvalue, newvalue + 1);
END;
$$;

CREATE OR REPLACE FUNCTION int4range_list_final(rangelist int4range[])
  RETURNS text
  LANGUAGE plpgsql
  IMMUTABLE
AS $$
DECLARE
  range     int4range;
  result    text = '';
  separator text = '';
BEGIN
  IF coalesce(array_upper(rangelist, 1), 0) = 0 THEN
    RETURN NULL;
  END IF;
  FOREACH range IN ARRAY rangelist LOOP
    result = result
        || separator
        || lower(range)::text
        || CASE
             WHEN upper(range) = lower(range) + 1 THEN ''::text
             ELSE '-'::text || (upper(range) - 1)::text
           END;
    separator := ', ';
  END LOOP;
  RETURN result;
END;
$$;

DROP AGGREGATE IF EXISTS int4range_list(int);

CREATE AGGREGATE int4range_list(int)
  (
    SFUNC = int4range_list_accum,
    STYPE = int4range[],
    FINALFUNC = int4range_list_final,
    INITCOND = '{}'
  );
