import 'package:comunic/enums/user_page_visibility.dart';
import 'package:comunic/helpers/database/users_database_helper.dart';
import 'package:comunic/lists/custom_emojies_list.dart';
import 'package:comunic/lists/users_list.dart';
import 'package:comunic/models/advanced_user_info.dart';
import 'package:comunic/models/api_request.dart';
import 'package:comunic/models/custom_emoji.dart';
import 'package:comunic/models/user.dart';

/// User helper
///
/// Helper used to get information about the users of Comunic
///
/// @author Pierre HUBERT

/// Handle advanced information error
enum GetUserAdvancedInformationErrorCause {
  NOT_FOUND,
  NETWORK_ERROR,
  NOT_AUTHORIZED
}

class GetUserAdvancedUserError extends Error {
  final GetUserAdvancedInformationErrorCause cause;

  GetUserAdvancedUserError(this.cause) : assert(cause != null);
}

class UsersHelper {
  final UsersDatabaseHelper _usersDatabaseHelper = UsersDatabaseHelper();

  /// Download information about some given users ID
  ///
  /// Return the list of users information in case of success, null in case of
  /// failure
  Future<UsersList> _downloadInfo(List<int> users) async {
    // Execute the request
    final response = await APIRequest(
        uri: "user/getInfoMultiple",
        needLogin: true,
        args: {"usersID": users.join(",")}).exec();

    // Check if the request did not execute correctly
    if (response.code != 200) return null;

    final list = UsersList();
    response.getObject().forEach(
          (k, v) => list.add(
            User(
              id: v["userID"],
              firstName: v["firstName"],
              lastName: v["lastName"],
              pageVisibility: v["publicPage"] == "false"
                  ? UserPageVisibility.PRIVATE
                  : (v["openPage"] == "false"
                      ? UserPageVisibility.PRIVATE
                      : UserPageVisibility.OPEN),
              virtualDirectory:
                  v["virtualDirectory"] == "" ? null : v["virtualDirectory"],
              accountImageURL: v["accountImage"],
              customEmojies: _parseCustomEmojies(v["customEmojis"]),
            ),
          ),
        );

    // Save the list
    _usersDatabaseHelper.insertOrUpdateAll(list);

    return list;
  }

  /// Get users information from a given [Set]. Throws an exception in case
  /// of failure
  Future<UsersList> getListWithThrow(Set<int> users,
      {bool forceDownload = false}) async {
    final list =
        await getUsersInfo(users.toList(), forceDownload: forceDownload);

    if (list == null)
      throw Exception(
          "Could not get information about the given list of users!");

    return list;
  }

  /// Get information about a single user. Throws in case of failure
  Future<User> getSingleWithThrow(int user,
      {bool forceDownload = false}) async {
    return (await getListWithThrow(Set<int>()..add(user),
        forceDownload: forceDownload))[0];
  }

  /// Get users information from a given [Set]
  Future<UsersList> getList(Set<int> users,
      {bool forceDownload = false}) async {
    return await getUsersInfo(users.toList());
  }

  /// Get users information
  ///
  /// If [forceDownload] is set to true, the data will always be retrieved from
  /// the server, otherwise cached data will be used if available
  Future<UsersList> getUsersInfo(List<int> users,
      {bool forceDownload = false}) async {
    List<int> toDownload = List();
    UsersList list = UsersList();

    // Check cache
    for (int userID in users) {
      if (!forceDownload && await _usersDatabaseHelper.has(userID))
        list.add(await _usersDatabaseHelper.get(userID));
      else
        toDownload.add(userID);
    }

    // Process download if required
    if (toDownload.length > 0) {
      final downloadedList = await _downloadInfo(toDownload);

      if (downloadedList == null) return null;

      list.addAll(downloadedList);
    }

    return list;
  }

  /// Try to fetch advanced information about a user
  Future<AdvancedUserInfo> getAdvancedInfo(int id) async {
    final response = await APIRequest(
        uri: "user/getAdvancedUserInfo",
        needLogin: true,
        args: {"userID": id.toString()}).exec();

    // Handle exceptions
    if (response.code != 200) {
      var cause = GetUserAdvancedInformationErrorCause.NETWORK_ERROR;

      if (response.code == 404)
        cause = GetUserAdvancedInformationErrorCause.NOT_FOUND;

      if (response.code == 401)
        cause = GetUserAdvancedInformationErrorCause.NOT_AUTHORIZED;

      throw new GetUserAdvancedUserError(cause);
    }

    final data = response.getObject();

    return AdvancedUserInfo(
      id: data["userID"],
      firstName: data["firstName"],
      lastName: data["lastName"],
      pageVisibility: data["publicPage"] == "false"
          ? UserPageVisibility.PRIVATE
          : (data["openPage"] == "false"
              ? UserPageVisibility.PRIVATE
              : UserPageVisibility.OPEN),
      virtualDirectory:
          data["virtualDirectory"] == "" ? null : data["virtualDirectory"],
      accountImageURL: data["accountImage"],
      customEmojies: _parseCustomEmojies(data["customEmojis"]),
      publicNote: data["publicNote"],
      canPostTexts: data["can_post_texts"],
    );
  }

  /// Parse the list of custom emojies
  CustomEmojiesList _parseCustomEmojies(List<dynamic> list) {
    final l = list.cast<Map<String, dynamic>>();

    return CustomEmojiesList()
      ..addAll(l
          .map((f) => CustomEmoji(
                id: f["id"],
                userID: f["userID"],
                shortcut: f["shortcut"],
                url: f["url"],
              ))
          .toList());
  }
}