/* 
 * This file is part of Stack Wallet.
 * 
 * Copyright (c) 2023 Cypher Stack
 * All Rights Reserved.
 * The code is distributed under GPLv3 license, see LICENSE file for details.
 * Generated by Cypher Stack on 2023-05-26
 *
 */

import 'dart:core' as core;
import 'dart:core';
import 'dart:io';

import 'package:flutter/foundation.dart';
import 'package:isar/isar.dart';

import '../models/isar/models/log.dart';
import 'constants.dart';
import 'enums/log_level_enum.dart';

export 'enums/log_level_enum.dart';

class Logging {
  static const isArmLinux = bool.fromEnvironment("IS_ARM");
  static final isTestEnv = Platform.environment["FLUTTER_TEST"] == "true";
  Logging._();
  static final Logging _instance = Logging._();
  static Logging get instance => _instance;

  static const core.int defaultPrintLength = 1020;

  late final Isar? isar;
  late final _AsyncLogWriterQueue _queue;

  Future<void> init(Isar isar) async {
    _queue = _AsyncLogWriterQueue();
    this.isar = isar;
  }

  void log(
    core.Object? object, {
    required LogLevel level,
    core.bool printToConsole = true,
    core.bool printFullLength = false,
  }) {
    try {
      if (isTestEnv || isArmLinux) {
        Logger.print(object, normalLength: !printFullLength);
        return;
      }
      String message = object.toString();

      // random value to check max size of message before storing in db
      if (message.length > 20000) {
        message = "${message.substring(0, 20000)}...";
      }

      final now = core.DateTime.now().toUtc();
      final log = Log()
        ..message = message
        ..logLevel = level
        ..timestampInMillisUTC = now.millisecondsSinceEpoch;
      if (level == LogLevel.Error || level == LogLevel.Fatal) {
        printFullLength = true;
      }

      _queue.add(
        () async => isar!.writeTxn(
          () async => await isar!.logs.put(log),
        ),
      );

      if (printToConsole) {
        final core.String logStr = "Log: ${log.toString()}";
        final core.int logLength = logStr.length;

        if (!printFullLength || logLength <= defaultPrintLength) {
          debugPrint(logStr);
        } else {
          core.int start = 0;
          core.int endIndex = defaultPrintLength;
          core.int tmpLogLength = logLength;
          while (endIndex < logLength) {
            debugPrint(logStr.substring(start, endIndex));
            endIndex += defaultPrintLength;
            start += defaultPrintLength;
            tmpLogLength -= defaultPrintLength;
          }
          if (tmpLogLength > 0) {
            debugPrint(logStr.substring(start, logLength));
          }
        }
      }
    } catch (e, s) {
      print("problem trying to log");
      print("$e $s");
      Logger.print(object);
    }
  }
}

abstract class Logger {
  static final isTestEnv = Platform.environment["FLUTTER_TEST"] == "true";

  static void print(
    core.Object? object, {
    core.bool withTimeStamp = true,
    core.bool normalLength = true,
  }) async {
    if (Constants.disableLogger && !isTestEnv) {
      return;
    }
    final utcTime = withTimeStamp ? "${core.DateTime.now().toUtc()}: " : "";
    final core.int defaultPrintLength = 1020 - utcTime.length;
    if (normalLength) {
      debugPrint("$utcTime$object");
    } else if (object == null ||
        object.toString().length <= defaultPrintLength) {
      debugPrint("$utcTime$object");
    } else {
      final core.String log = object.toString();
      core.int start = 0;
      core.int endIndex = defaultPrintLength;
      final core.int logLength = log.length;
      core.int tmpLogLength = log.length;
      while (endIndex < logLength) {
        debugPrint(utcTime + log.substring(start, endIndex));
        endIndex += defaultPrintLength;
        start += defaultPrintLength;
        tmpLogLength -= defaultPrintLength;
      }
      if (tmpLogLength > 0) {
        debugPrint(utcTime + log.substring(start, logLength));
      }
    }
  }
}

/// basic async queue for writing logs in the [Logging] to isar
class _AsyncLogWriterQueue {
  final List<Future<void> Function()> _queue = [];
  bool _runningLock = false;

  void add(Future<void> Function() futureFunction) {
    _queue.add(futureFunction);
    _run();
  }

  void _run() async {
    if (_runningLock) {
      return;
    }
    _runningLock = true;
    try {
      while (_queue.isNotEmpty) {
        final futureFunction = _queue.removeAt(0);
        try {
          await futureFunction.call();
        } catch (e, s) {
          debugPrint("$e\n$s");
        }
      }
    } finally {
      _runningLock = false;
    }
  }
}