library property;

import 'dart:async';
import 'dart:collection';
import 'dart:convert';
import 'dart:math';

import 'package:guru_utils/log/log.dart';
import 'package:guru_utils/property/property_delegate.dart';
import 'package:guru_utils/property/storage/property_storage.dart';
import 'package:guru_utils/quiver/cache/map_cache.dart';

/// Created by @Haoyi on 2020/12/4
///

part "property_bundle.dart";

part "property_key.dart";

extension PropertyExtension on Map<PropertyKey, String> {
  List<MapEntry<String, String>> toNamedMapEntries() {
    final List<MapEntry<String, String>> result = [];
    for (var entry in entries) {
      result.add(MapEntry(entry.key.key, entry.value.toString()));
    }
    return result;
  }
}

class AppProperty implements PropertyDelegate {
  static late AppProperty _instance;

  static AppProperty getInstance() => _mock ?? _instance;

  static AppProperty? _mock;

  final PropertyStorage _storage;

  final MapCache<PropertyKey, String> _cache;

  AppProperty._(this._storage, {int cacheSize = 256})
      : _cache = MapCache.lru(maximumSize: cacheSize);

  static void setMock(AppProperty mock) {
    _mock = mock;
  }

  static void initialize(PropertyStorage storage, {int cacheSize = 256}) {
    _instance = AppProperty._(storage, cacheSize: cacheSize);
  }

  static void reload({int cacheSize = 256}) {
    _instance._cache.clear((_) {});
    _instance = AppProperty._(_instance._storage, cacheSize: cacheSize);
  }

  @override
  Future<bool> setInt(PropertyKey key, int value) {
    return _setValue(key, value.toString());
  }

  Future<int> getAndIncrease(PropertyKey key, {int defValue = 0, String? tag}) async {
    final count = await getInt(key, defValue: defValue);
    await setInt(key, count + 1);
    return count;
  }

  Future<int> increaseAndGet(PropertyKey key, {int defValue = 0}) async {
    final count = await getInt(key, defValue: defValue);
    await setInt(key, count + 1);
    return count + 1;
  }

  Future<int> decreaseAndGet(PropertyKey key, {int defValue = 0}) async {
    final count = await getInt(key, defValue: defValue);
    await setInt(key, count - 1);
    return count - 1;
  }

  Future<int> getAndDecrease(PropertyKey key, {int defValue = 0, String? tag}) async {
    final count = await getInt(key, defValue: defValue);
    await setInt(key, count - 1);
    return count;
  }

  @override
  Future<bool> setDouble(PropertyKey key, double value) {
    return _setValue(key, value.toString());
  }

  @override
  Future<bool> setString(PropertyKey key, String value) {
    return _setValue(key, value);
  }

  @override
  Future<bool> setBool(PropertyKey key, bool value) async {
    return await _setValue(key, value ? "1" : "0");
  }

  Future<bool> _setValue(PropertyKey key, String value) {
    _cache.set(key, value);
    return _storage.setProperty(key, value);
  }

  Future<bool> setProperties(PropertyBundle bundle) async {
    _cache.addAll(bundle.data);
    return await _storage.setProperties(bundle.data);
  }

  Future<Map<PropertyKey, String>> loadAllValues() {
    return _storage.loadAllProperties();
  }

  Future<String?> _loadValue(PropertyKey key) {
    return _storage.getProperty(key);
  }

  Future<String?> _loadOrCreateValue(PropertyKey key, {String? Function()? ifAbsent}) {
    return _storage.getOrCreateProperty(key, ifAbsent: ifAbsent);
  }

  Future<Map<PropertyKey, String>> _loadValues(List<PropertyKey> keys) {
    return _storage.getProperties(keys);
  }

  FutureOr<String?> _getValue(PropertyKey key) async {
    return await _cache.get(key, ifAbsent: _loadValue);
  }

  FutureOr<String?> _getValueIfExists(PropertyKey key) async {
    return await _cache.get(key);
  }

  Future<Map<PropertyKey, String>> getValues(List<PropertyKey> keys) async {
    final result = <PropertyKey, String>{};
    final loadKeys = <PropertyKey>[];
    for (var key in keys) {
      final value = await _cache.get(key);
      if (value != null) {
        result[key] = value;
      } else {
        loadKeys.add(key);
      }
    }

    final map = await _loadValues(loadKeys);
    for (var entry in map.entries) {
      _cache.set(entry.key, entry.value);
    }
    result.addAll(map);
    return result;
  }

  @override
  Future<int> getInt(PropertyKey key, {required int defValue}) async {
    try {
      final value = await _getValue(key);
      if (value != null) {
        return int.parse(value);
      }
    } catch (error, stacktrace) {
      Log.d("getInt error!", error: error, stackTrace: stacktrace);
    }
    return defValue;
  }

  @override
  Future<double> getDouble(PropertyKey key, {required double defValue}) async {
    try {
      final value = await _getValue(key);
      if (value != null) {
        return double.parse(value);
      }
    } catch (error, stacktrace) {
      Log.w("getDouble error!", error: error, stackTrace: stacktrace);
    }
    return defValue;
  }

  @override
  Future<String> getString(PropertyKey key, {required String defValue}) async {
    try {
      final value = await _getValue(key);
      if (value != null) {
        return value;
      }
    } catch (error, stacktrace) {
      Log.w("getString error!", error: error, stackTrace: stacktrace);
    }
    return defValue;
  }

  @override
  Future<bool> getBool(PropertyKey key, {required bool defValue}) async {
    try {
      final value = await _getValue(key);
      if (value != null) {
        return value == "1";
      }
    } catch (error, stacktrace) {
      Log.d("getString error!", error: error, stackTrace: stacktrace);
    }
    return defValue;
  }

  Future<String?> _getOrCreateValue(PropertyKey key, String Function() ifAbsent) async {
    return await _cache.get(key, ifAbsent: (key) => _loadOrCreateValue(key, ifAbsent: ifAbsent));
  }

  Future<int> getOrCreateInt(PropertyKey key, int ifAbsent) async {
    try {
      final value = await _getOrCreateValue(key, () => ifAbsent.toString());
      if (value != null) {
        return int.parse(value);
      }
    } catch (error, stacktrace) {
      Log.w("getOrCreateInt error!", error: error, stackTrace: stacktrace);
    }
    return ifAbsent;
  }

  Future<double> getOrCreateDouble(PropertyKey key, double ifAbsent) async {
    try {
      final value = await _getOrCreateValue(key, () => ifAbsent.toString());
      if (value != null) {
        return double.parse(value);
      }
    } catch (error, stacktrace) {
      Log.w("getOrCreateDouble error!", error: error, stackTrace: stacktrace);
    }
    return ifAbsent;
  }

  Future<String> getOrCreateString(PropertyKey key, String ifAbsent) async {
    try {
      final value = await _getOrCreateValue(key, () => ifAbsent);
      if (value != null) {
        return value;
      }
    } catch (error, stacktrace) {
      Log.w("getOrCreateString error!", error: error, stackTrace: stacktrace);
    }
    return ifAbsent;
  }

  Future<bool> getOrCreateBool(PropertyKey key, bool ifAbsent) async {
    try {
      final value = await _getOrCreateValue(key, () => ifAbsent ? "1" : "0");
      if (value != null) {
        return value == "1";
      }
    } catch (error, stacktrace) {
      Log.d("getOrCreateBool error!", error: error, stackTrace: stacktrace);
    }
    return ifAbsent;
  }

  @override
  Future<PropertyBundle> loadValuesByTag(String tag) async {
    try {
      final map = await _storage.getPropertiesByTag(tag);
      for (var entry in map.entries) {
        _cache.set(entry.key, entry.value);
      }
      return PropertyBundle(map: map);
    } catch (error, stacktrace) {
      return PropertyBundle.empty();
    }
  }

  @override
  Future<PropertyBundle> loadValuesByUsage(int usage) async {
    try {
      final map = await _storage.getPropertiesByUsage(usage);
      for (var entry in map.entries) {
        _cache.set(entry.key, entry.value);
      }
      return PropertyBundle(map: map);
    } catch (error, stacktrace) {
      return PropertyBundle.empty();
    }
  }

  @override
  Future<bool> remove(PropertyKey key) async {
    try {
      _cache.invalidate(key);
      return await _storage.removeProperty(key);
    } catch (error, stacktrace) {
      Log.w("remove error! $error", error: error, stackTrace: stacktrace);
    }
    return false;
  }

  @override
  Future<bool> removeAllWithTag(String tag) async {
    try {
      final keys = await _storage.removeAllWithTag(tag);
      for (var key in keys) {
        if (key is String) {
          _cache.invalidate(key);
        }
      }
      return true;
    } catch (error, stacktrace) {
      Log.w("removeAllWithTag error! $error", error: error, stackTrace: stacktrace);
    }
    return false;
  }

  @override
  Future<bool?> getBoolOrNull(PropertyKey key, {bool? defValue}) async {
    try {
      final value = await _getValue(key);
      if (value != null) {
        return value == "1";
      }
    } catch (error, stacktrace) {
      Log.w("getString error!", error: error, stackTrace: stacktrace);
    }
    return defValue;
  }

  @override
  Future<double?> getDoubleOrNull(PropertyKey key, {double? defValue}) async {
    try {
      final value = await _getValue(key);
      if (value != null) {
        return double.parse(value);
      }
    } catch (error, stacktrace) {
      Log.w("getDouble error!", error: error, stackTrace: stacktrace);
    }
    return defValue;
  }

  @override
  Future<int?> getIntOrNull(PropertyKey key, {int? defValue}) async {
    try {
      final value = await _getValue(key);
      if (value != null) {
        return int.parse(value);
      }
    } catch (error, stacktrace) {
      Log.w("getInt error!", error: error, stackTrace: stacktrace);
    }
    return defValue;
  }

  @override
  Future<String?> getStringOrNull(PropertyKey key, {String? defValue}) async {
    try {
      final value = await _getValue(key);
      if (value != null) {
        return value;
      }
    } catch (error, stacktrace) {
      Log.w("getString error!", error: error, stackTrace: stacktrace);
    }
    return defValue;
  }
}
