import 'dart:convert';
import 'dart:io';
import 'dart:math';

import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'memory_service.g.dart';

// riverpod stream builder docs:
// https://riverpod.dev/docs/providers/stream_provider

@riverpod
class MemoryService extends _$MemoryService {
  @override
  Future<double> build() async {
    if (Platform.isLinux) {
      final processMemory = await Process.start(
        'grep',
        [
          'MemTotal',
          '/proc/meminfo',
        ],
      );
      final awkMemInKB = await Process.start('awk', ['{print \$2}']);
      processMemory.stdout.pipe(awkMemInKB.stdin);

      var result = await awkMemInKB.stdout.transform(utf8.decoder).join();

      return double.parse(result) / 1024;
    } else {
      return 8192;
    }
  }

  Stream<double> freeMemory() async* {
    while (true) {
      if (Platform.isLinux) {
        await Future.delayed(const Duration(milliseconds: 100));
        final meminfo = await Process.start(
          'cat',
          [
            '/proc/meminfo',
          ],
        );
        String output = await meminfo.stdout.transform(utf8.decoder).join();

        Iterable<String> lines = LineSplitter.split(output);

        int memTotal = _getKeyValue(list: lines, key: 'MemTotal');
        int shMem = _getKeyValue(list: lines, key: 'Shmem');
        int memFree = _getKeyValue(list: lines, key: 'MemFree');
        int buffers = _getKeyValue(list: lines, key: 'Buffers');
        int cached = _getKeyValue(list: lines, key: 'Cached');
        int sReclaimable = _getKeyValue(list: lines, key: 'SReclaimable');

        //  Used memory is calculated using the following "formula":
        // MemUsed = MemTotal + Shmem - MemFree - Buffers - Cached - SReclaimable
        // Source: https://github.com/dylanaraps/pfetch/blob/master/pfetch
        // Source: https://github.com/KittyKatt/screenFetch/issues/386

        final double memoryUsed =
            (memTotal + shMem - memFree - buffers - cached - sReclaimable) /
                1024;

        yield memoryUsed;
      } else {
        await Future.delayed(const Duration(milliseconds: 100));
        yield Random().nextDouble() * 8192;
      }
    }
  }
}

/// get the key value from a /proc/meminfo line
int _getKeyValue({required Iterable<String> list, required String key}) {
  final String keyLine = list.firstWhere((element) => element.startsWith(key));
  return int.parse(keyLine.replaceAll(RegExp(r"\s+"), " ").split(' ')[1]);
}
