#!/usr/bin/env python3

import datetime
import json
import requests

SOFTWARES = ['diaspora', 'friendica', 'hubzilla', 'mastodon', 'gnusocial',
             'pleroma', 'misskey', 'peertube', 'pixelfed', 'funkwhale',
             'writefreely', 'plume', 'mobilizon', 'lemmy', 'gotosocial',
             'owncast', 'bookwyrm', 'microblogpub', 'akkoma', 'firefish',
             'hometown']

def number_to_string(number):
    """
    Format number as string, using non-breaking space as a thousands separator.
    """
    return f'{number:,}'.replace(',', '\u00a0')

class SoftwareNumbers:
    """
    Contains numbers for a given software, or for Fediverse overall.
    """
    def __init__(self, softwarename):
        self.softwarename = softwarename
        self.population = 0
        self.active_users = 0
        self.pods_num = 0

    def __add__(self, other):
        result = SoftwareNumbers(self.softwarename)
        result.population = self.population + other.population
        result.active_users = self.active_users + other.active_users
        result.pods_num = self.pods_num + other.pods_num
        return result

    def as_object(self):
        result = {}
        result[self.softwarename] = {}
        result[self.softwarename]['population'] = number_to_string(self.population)
        result[self.softwarename]['activeUsers'] = number_to_string(self.active_users)
        result[self.softwarename]['podsNum'] = number_to_string(self.pods_num)
        return result

class StatsFetcher:
    """
    Fetches Fediverse numbers and produces a report.
    """
    def __init__(self):
        self.session = requests.Session()

    def __get_node_stats_for_software(self, softwarename):
        query = '{ nodes(status: "UP", softwarename: "' \
                + softwarename \
                + '") { total_users, active_users_halfyear } }'
        response = self.session.post('https://api.fediverse.observer/', data={'query': query})
        return response.json().get('data', {}).get('nodes', [])

    def __calculate_stats_for_software(self, softwarename):
        node_stats = self.__get_node_stats_for_software(softwarename)

        result = SoftwareNumbers(softwarename)
        result.pods_num = len(node_stats)
        for node in node_stats:
            result.population += node.get('total_users', 0)
            result.active_users += node.get('active_users_halfyear', 0)

        return result

    def get_numbers(self):
        """
        Obtain an object with numbers for each of the supported Fediverse
        platforms, and also a "fediverse" entry with totals.
        """
        numbers = {}
        numbers['lastUpdate'] = datetime.date.today().strftime('%d %b %Y')

        fediverse = SoftwareNumbers('fediverse')
        for i, software in enumerate(SOFTWARES):
            print(f'({i+1:2}/{len(SOFTWARES):2}) Fetching stats for {software}...')
            stats = self.__calculate_stats_for_software(software)
            fediverse += stats
            numbers |= stats.as_object()

        numbers |= fediverse.as_object()
        return { 'list': numbers }

if __name__ == '__main__':
    fetcher = StatsFetcher()
    numbers = fetcher.get_numbers()
    FILEPATH = 'source/_data/numbers.json'
    with open(FILEPATH, 'w') as f:
        f.write(json.dumps(numbers, indent=2))
    print(f'{FILEPATH} updated.')
