--
-- This file is part of TALER
-- Copyright (C) 2025 Taler Systems SA
--
-- TALER is free software; you can redistribute it and/or modify it under the
-- terms of the GNU General Public License as published by the Free Software
-- Foundation; either version 3, or (at your option) any later version.
--
-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-- A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License along with
-- TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
--


DROP FUNCTION IF EXISTS merchant_statistic_interval_amount_get;
CREATE FUNCTION merchant_statistic_interval_amount_get (
  IN in_slug TEXT
)
RETURNS SETOF merchant.merchant_statistic_interval_amount_get_return_value
LANGUAGE plpgsql
AS $$
DECLARE
  my_time INT8 DEFAULT ROUND(EXTRACT(epoch FROM CURRENT_TIMESTAMP(0)::TIMESTAMP) * 1000000)::INT8 / 1000 / 1000;
  my_ranges INT8[];
  my_range INT8;
  my_delta_value INT8;
  my_delta_frac INT8;
  my_meta INT8;
  my_next_max_serial INT8;
  my_currency TEXT;
  my_rec RECORD;
  my_irec RECORD;
  my_jrec RECORD;
  my_i INT;
  my_min_serial INT8 DEFAULT NULL;
  my_rval merchant.merchant_statistic_interval_amount_get_return_value;
BEGIN

  SELECT imeta_serial_id
        ,ranges
        ,precisions
    INTO my_rec
    FROM merchant_statistic_interval_meta
   WHERE slug=in_slug;
  IF NOT FOUND
  THEN
    RETURN;
  END IF;

  my_meta = my_rec.imeta_serial_id;
  my_ranges = my_rec.ranges;

  FOR my_currency IN
    SELECT DISTINCT delta_curr
      FROM merchant_statistic_amount_event
     WHERE imeta_serial_id = my_meta
  LOOP

  my_rval.rvalue.val = 0;
  my_rval.rvalue.frac = 0;
  my_rval.rvalue.curr = my_currency;

  FOR my_i IN 1..COALESCE(array_length(my_ranges,1),0)
  LOOP
    my_range = my_ranges[my_i];
    SELECT event_delimiter
          ,cumulative_value
          ,cumulative_frac
      INTO my_irec
      FROM merchant_statistic_interval_amount
     WHERE imeta_serial_id = my_meta
       AND curr = my_currency
       AND range = my_range;

    IF FOUND
    THEN
      my_min_serial = my_irec.event_delimiter;
      my_rval.rvalue.val = (my_rval.rvalue).val + my_irec.cumulative_value + my_irec.cumulative_frac / 100000000;
      my_rval.rvalue.frac = (my_rval.rvalue).frac + my_irec.cumulative_frac % 100000000;
      IF (my_rval.rvalue).frac > 100000000
      THEN
        my_rval.rvalue.frac = (my_rval.rvalue).frac - 100000000;
        my_rval.rvalue.val = (my_rval.rvalue).val + 1;
      END IF;

      -- Check if we have events that left the applicable range
      SELECT SUM(delta_value) AS value_sum
            ,SUM(delta_frac) AS frac_sum
        INTO my_jrec
        FROM merchant_statistic_amount_event
       WHERE imeta_serial_id = my_meta
         AND delta_curr = my_currency
         AND slot < my_time - my_range
         AND aevent_serial_id >= my_min_serial;

      IF FOUND AND my_jrec.value_sum IS NOT NULL
      THEN
        -- Normalize sum
        my_delta_value = my_jrec.value_sum + my_jrec.frac_sum / 100000000;
        my_delta_frac = my_jrec.frac_sum % 100000000;
        my_rval.rvalue.val = (my_rval.rvalue).val - my_delta_value;
        IF ((my_rval.rvalue).frac >= my_delta_frac)
        THEN
          my_rval.rvalue.frac = (my_rval.rvalue).frac - my_delta_frac;
        ELSE
          my_rval.rvalue.frac = 100000000 + (my_rval.rvalue).frac - my_delta_frac;
          my_rval.rvalue.val = (my_rval.rvalue).val - 1;
        END IF;

        -- First find out the next event delimiter value
        SELECT aevent_serial_id
          INTO my_next_max_serial
          FROM merchant_statistic_amount_event
         WHERE imeta_serial_id = my_meta
           AND delta_curr = my_currency
           AND slot >= my_time - my_range
           AND aevent_serial_id >= my_min_serial
         ORDER BY slot ASC
         LIMIT 1;
        IF FOUND
        THEN
          -- remove expired events from the sum of the current slot
          UPDATE merchant_statistic_interval_amount SET
             cumulative_value = cumulative_value - my_delta_value
              - CASE
                  WHEN cumulative_frac < my_delta_frac
                  THEN 1
                  ELSE 0
                END,
             cumulative_frac = cumulative_frac - my_delta_frac
             + CASE
                 WHEN cumulative_frac < my_delta_frac
                 THEN 100000000
                 ELSE 0
               END,
             event_delimiter = my_next_max_serial
           WHERE imeta_serial_id = my_meta
             AND curr = my_currency
             AND range = my_range;
        ELSE
          -- actually, slot is now empty, remove it entirely
          DELETE FROM merchant_statistic_interval_amount
           WHERE imeta_serial_id = my_meta
             AND curr = my_currency
             AND range = my_range;
        END IF;
        IF (my_i < COALESCE(array_length(my_ranges,1),0))
        THEN
          -- carry over all events into the next (larger) slot
          UPDATE merchant_statistic_interval_amount AS msia SET
            cumulative_value = cumulative_value + my_delta_value
              + CASE
                 WHEN cumulative_frac + my_delta_frac > 100000000
                 THEN 1
                 ELSE 0
               END,
            cumulative_frac = cumulative_frac + my_delta_frac
              - CASE
                 WHEN cumulative_frac + my_delta_frac > 100000000
                 THEN 100000000
                 ELSE 0
               END,
            event_delimiter = LEAST (msia.event_delimiter,my_min_serial)
           WHERE imeta_serial_id = my_meta
             AND range=my_ranges[my_i+1];
          IF NOT FOUND
          THEN
            INSERT INTO merchant_statistic_interval_amount
              (imeta_serial_id
              ,event_delimiter
              ,range
              ,curr
              ,cumulative_value
              ,cumulative_frac
              ) VALUES (
               my_meta
              ,my_min_serial
              ,my_ranges[my_i+1]
              ,my_currency
              ,my_delta_value
              ,my_delta_frac);
          END IF;
        ELSE
          -- events are obsolete, delete them
          DELETE FROM merchant_statistic_amount_event
                WHERE imeta_serial_id = my_meta
                  AND slot < my_time - my_range;
        END IF;
      END IF;

      my_rval.range = my_range;
      RETURN NEXT my_rval;
    END IF;
  END LOOP; -- over my_ranges
  END LOOP; -- over my_currency
END $$;

COMMENT ON FUNCTION merchant_statistic_interval_amount_get
  IS 'Returns deposit statistic tracking deposited amounts over certain time intervals; we first trim the stored data to only track what is still in-range, and then return the remaining value; multiple values are returned, one per currency and range';
