import 'dart:math';

import 'package:comunic/helpers/events_helper.dart';
import 'package:comunic/helpers/groups_helper.dart';
import 'package:comunic/helpers/posts_helper.dart';
import 'package:comunic/helpers/users_helper.dart';
import 'package:comunic/lists/groups_list.dart';
import 'package:comunic/lists/posts_list.dart';
import 'package:comunic/lists/users_list.dart';
import 'package:comunic/models/comment.dart';
import 'package:comunic/models/post.dart';
import 'package:comunic/ui/screens/conversation_screen.dart';
import 'package:comunic/ui/tiles/post_tile.dart';
import 'package:comunic/ui/widgets/safe_state.dart';
import 'package:comunic/ui/widgets/scroll_watcher.dart';
import 'package:comunic/utils/intl_utils.dart';
import 'package:comunic/utils/ui_utils.dart';
import 'package:flutter/material.dart';

/// Posts list widget
///
/// Displays a list of posts
///
/// @author Pierre HUBERT

class PostsListWidget extends StatefulWidget {
  final List<Widget> topWidgets;
  final Future<PostsList> Function() getPostsList;
  final Future<PostsList> Function(int from) getOlder;
  final bool showPostsTarget;
  final bool buildListView;
  final bool userNamesClickable;
  final bool disablePullToRefresh;

  const PostsListWidget({
    Key key,
    @required this.getPostsList,
    @required this.showPostsTarget,
    this.userNamesClickable = true,
    this.buildListView = true,
    this.getOlder,
    this.topWidgets,
    this.disablePullToRefresh = false,
  })  : assert(getPostsList != null),
        assert(showPostsTarget != null),
        assert(buildListView != null),
        assert(userNamesClickable != null),
        assert(disablePullToRefresh != null),
        super(key: key);

  @override
  State<StatefulWidget> createState() => PostsListWidgetState();
}

class PostsListWidgetState extends SafeState<PostsListWidget> {
  // Helpers
  final UsersHelper _usersHelper = UsersHelper();
  final GroupsHelper _groupsHelper = GroupsHelper();

  // Class members
  PostsList _list;
  UsersList _users;
  GroupsList _groups;
  ScrollWatcher _scrollController;
  ErrorLevel _error = ErrorLevel.NONE;
  bool _loading = false;

  final _registeredPosts = Set<int>();

  set error(ErrorLevel err) => setState(() => _error = err);

  int get _numberTopWidgets =>
      widget.topWidgets == null ? 0 : widget.topWidgets.length;

  @override
  void initState() {
    super.initState();

    _scrollController = ScrollWatcher(onReachBottom: reachedPostsBottom);

    // Register to events
    this.listen<NewCommentEvent>((ev) => this._addComment(ev.comment));
    this.listenChangeState<UpdatedCommentEvent>(
        (ev) => this._updateComment(ev.comment));
    this.listenChangeState<DeletedCommentEvent>(
        (ev) => this._removeComment(ev.commentID));
  }

  @override
  void dispose() {
    super.dispose();

    _unregisterAllPosts();
  }

  @override
  void setState(fn) {
    if (!mounted) return;
    super.setState(fn);

    // Register for posts update
    _registerRequiredPosts();
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    loadPostsList();
  }

  void _loadError() {
    error = _list == null ? ErrorLevel.MAJOR : ErrorLevel.MINOR;
    _loading = false;
  }

  /// Load the list of posts
  Future<void> loadPostsList({bool getOlder = false}) async {
    if (_loading) return;

    _loading = true;

    try {
      final list = !getOlder
          ? await widget.getPostsList()
          : await widget.getOlder(_list.oldestID);

      if (list == null) return _loadError();

      final users = await _usersHelper.getList(list.usersID);

      final groups = await _groupsHelper.getList(list.groupsID);

      if (groups == null) return _loadError();

      if (!mounted) return;

      setState(() {
        if (!getOlder) {
          _list = list;
          _users = users;
          _groups = groups;
        } else {
          _list.addAll(list);
          _users.addAll(users);
          _groups.addAll(groups);
        }
      });
    } catch (e, s) {
      print("Failed to load post information ! $e => $s");
      _loadError();
    }

    _loading = false;
  }

  /// Register for potential new posts
  void _registerRequiredPosts() async {
    if (_list == null) return;

    final missing = _list
        .where((f) => !_registeredPosts.contains(f.id))
        .map((f) => f.id)
        .toSet();

    _registeredPosts.addAll(missing);

    for (final postID in missing) {
      await PostsHelper().registerPostEvents(postID);
    }
  }

  /// Unregister from all posts
  ///
  /// This method should be called only once
  void _unregisterAllPosts() async {
    for (final postID in _registeredPosts) {
      // We put the try - catch here because we must absolutely unregister ALL
      // POSTS event if one post fails to remove
      try {
        await PostsHelper().unregisterPostEvents(postID);
      } catch (e, stack) {
        print("Could not unregister post! $e $stack");
      }
    }
  }

  Widget _buildErrorCard() {
    return buildErrorCard(tr("Could not get the list of posts !"));
  }

  Widget _buildNoPostNotice() {
    return Center(
      child: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Text(tr("There is no post to display here yet.")),
      ),
    );
  }

  Widget _buildListViewWithRefreshIndicator() => RefreshIndicator(
        child: _buildListView(),
        onRefresh: () => loadPostsList(),
      );

  Widget _buildListView() {
    return ListView.builder(
      // We use max function here to display to post notice in case there are not posts to display but there are custom widgets...
      itemCount: max(_list.length, 1) + _numberTopWidgets,

      itemBuilder: _buildItem,
      controller: _scrollController,
    );
  }

  Widget _buildColumn() {
    return Column(
      children: List.generate(
        _list.length,
        (i) => _buildItem(null, i),
      ),
    );
  }

  Widget _buildItem(BuildContext context, int index) {
    if (index < _numberTopWidgets) return widget.topWidgets[index];

    // Show no posts notice if required
    if (_list.length == 0) return _buildNoPostNotice();

    return PostTile(
      post: _list[index - _numberTopWidgets],
      usersInfo: _users,
      groupsInfo: _groups,
      onDeletedPost: _removePost,
      showPostTarget: widget.showPostsTarget,
      userNamesClickable: widget.userNamesClickable,
    );
  }

  @override
  Widget build(BuildContext context) {
    if (_error == ErrorLevel.MAJOR) return _buildErrorCard();
    if (_list == null) return buildCenteredProgressBar();
    if (_list.length == 0 && _numberTopWidgets == 0)
      return _buildNoPostNotice();
    if (!widget.buildListView) return _buildColumn();

    if (widget.disablePullToRefresh) return _buildListView();

    return _buildListViewWithRefreshIndicator();
  }

  void _removePost(Post post) => setState(() => _list.remove(post));

  void reachedPostsBottom() {
    if (widget.getOlder != null) loadPostsList(getOlder: true);
  }

  /// Add new comment
  void _addComment(Comment c) async {
    if (_list == null) return;

    try {
      final p = _list.singleWhere((p) => p.id == c.postID, orElse: () => null);

      if (p == null) return;

      if (!_users.hasUser(c.userID))
        _users.add(await UsersHelper().getSingleWithThrow(c.userID));

      setState(() {
        p.comments.add(c);
      });
    } catch (e, stack) {
      print("$e\n$stack");
    }
  }

  /// Update a comment
  void _updateComment(Comment c) {
    if (_list == null) return;

    try {
      final p = _list.singleWhere((p) => p.id == c.postID, orElse: () => null);

      if (p == null) return;

      final index = p.comments.indexWhere((d) => d.id == c.id);

      if (index > -1) p.comments[index] = c;
    } catch (e, stack) {
      print("$e\n$stack");
    }
  }

  /// Remove a comment from the list
  void _removeComment(int commentID) {
    if (_list == null) return;

    _list.forEach((p) => p.comments.removeWhere((c) => c.id == commentID));
  }
}