import 'dart:async';

import 'package:comunic/helpers/conversations_helper.dart';
import 'package:comunic/helpers/events_helper.dart';
import 'package:comunic/helpers/users_helper.dart';
import 'package:comunic/lists/conversation_messages_list.dart';
import 'package:comunic/lists/users_list.dart';
import 'package:comunic/models/conversation_message.dart';
import 'package:comunic/models/new_conversation_message.dart';
import 'package:comunic/ui/tiles/conversation_message_tile.dart';
import 'package:comunic/ui/widgets/safe_state.dart';
import 'package:comunic/ui/widgets/scroll_watcher.dart';
import 'package:comunic/utils/files_utils.dart';
import 'package:comunic/utils/intl_utils.dart';
import 'package:comunic/utils/list_utils.dart';
import 'package:comunic/utils/ui_utils.dart';
import 'package:flutter/material.dart';

/// Conversation screen
///
/// @author Pierre HUBERT

enum ErrorLevel { NONE, MINOR, MAJOR }
enum _OlderMessagesLevel { NONE, LOADING, NO_MORE_AVAILABLE }

class ConversationScreen extends StatefulWidget {
  final int conversationID;

  const ConversationScreen({Key key, this.conversationID})
      : assert(conversationID != null),
        super(key: key);

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

class _ConversationScreenState extends SafeState<ConversationScreen> {
  //Helpers
  final ConversationsHelper _conversationsHelper = ConversationsHelper();
  final UsersHelper _usersHelper = UsersHelper();

  // Class members
  ConversationMessagesList _messages;
  UsersList _usersInfo = UsersList();
  ErrorLevel _error = ErrorLevel.NONE;
  bool _isMessageValid = false;
  bool _isSendingMessage = false;
  TextEditingController _textEditingController = TextEditingController();
  ScrollWatcher _scrollController;
  _OlderMessagesLevel _loadingOlderMessages = _OlderMessagesLevel.NONE;

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

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

  void _setError(ErrorLevel err) => setState(() => _error = err);

  void _setSending(bool sending) => setState(() => _isSendingMessage = sending);

  void _setLoadingOlderMessagesState(_OlderMessagesLevel state) => setState(() {
        _loadingOlderMessages = state;
      });

  /// Method called when an error occurred while loading messages
  void _errorLoading() {
    _setError(_messages == null ? ErrorLevel.MAJOR : ErrorLevel.MINOR);
  }

  /// Load the first conversations
  Future<void> _init() async {
    _scrollController = ScrollWatcher(onReachBottom: _loadOlderMessages);

    // Fetch latest messages
    await _loadMessages(false);
    await _loadMessages(true);

    await _conversationsHelper
        .registerConversationEvents(widget.conversationID);

    this.listen<NewConversationMessageEvent>((ev) async {
      if (ev.msg.conversationID == widget.conversationID) {
        await _conversationsHelper.saveMessage(ev.msg);
        await _applyNewMessages(ConversationMessagesList()..add(ev.msg));
      }
    });

    this.listen<UpdatedConversationMessageEvent>((ev) async {
      if (ev.msg.conversationID == widget.conversationID) {
        await _conversationsHelper.saveMessage(ev.msg);
        setState(() => _messages.replace(ev.msg));
      }
    });

    this.listen<DeletedConversationMessageEvent>((ev) async {
      if (ev.msg.conversationID == widget.conversationID) {
        await _conversationsHelper.removeMessage(ev.msg.id);
        setState(() => _messages.removeMsg(ev.msg.id));
      }
    });
  }

  /// Free resources when this conversation widget is no longer required
  void _deallocate() {
    _conversationsHelper.unregisterConversationEvents(widget.conversationID);
  }

  /// Load a list of messages
  Future<void> _loadMessages(bool online) async {
    if (!mounted) return;

    //First, get the messages
    final messages = await _conversationsHelper.getNewMessages(
        conversationID: widget.conversationID,
        lastMessageID: _messages == null ? 0 : _messages.lastMessageID,
        online: online);

    if (messages == null) return _errorLoading();

    // In case we are offline and we did not get any message we do not do
    // anything (we wait for the online request)
    if (messages.length == 0 && !online) return;

    await _applyNewMessages(messages);
  }

  /// Get older messages
  Future<void> _loadOlderMessages() async {
    if (_loadingOlderMessages != _OlderMessagesLevel.NONE ||
        _messages == null ||
        _messages.length == 0) return;

    // Let's start to load older messages
    _setLoadingOlderMessagesState(_OlderMessagesLevel.LOADING);

    final messages = await _conversationsHelper.getOlderMessages(
        conversationID: widget.conversationID,
        oldestMessagesID: _messages.firstMessageID);

    // Mark as not loading anymore
    _setLoadingOlderMessagesState(_OlderMessagesLevel.NONE);

    // Check for errors
    if (messages == null) {
      _errorLoading();
      return;
    }

    // Check if there is no more unread messages
    if (messages.length == 0) {
      _setLoadingOlderMessagesState(_OlderMessagesLevel.NO_MORE_AVAILABLE);
      return;
    }

    // Apply the messages
    _applyNewMessages(messages);
  }

  /// Apply new messages [messages] must not be null
  Future<void> _applyNewMessages(ConversationMessagesList messages) async {
    // We ignore new messages once the area is no longer visible
    if (!this.mounted) return;

    //Then get information about users
    final usersToGet =
        findMissingFromList(_usersInfo.usersID, messages.getUsersID());

    final users = await _usersHelper.getUsersInfo(usersToGet);

    if (users == null) _errorLoading();

    // Save the new list of messages
    setState(() {
      _usersInfo.addAll(users);
      if (_messages == null)
        _messages = messages;
      else
        _messages.addAll(messages);

      //Reverse the order of the messages (if required)
      if (messages.length > 0) {
        _messages.sort();
        final reverse = _messages.reversed;
        _messages = ConversationMessagesList();
        _messages.addAll(reverse);
      }
    });

    // Remove previous errors
    _setError(ErrorLevel.NONE);
  }

  /// Pick and send an image
  Future<void> _sendImage(BuildContext context) async {
    final image = await pickImage(context);

    if (image == null) return null;

    _submitMessage(
      context,
      NewConversationMessage(
        conversationID: widget.conversationID,
        message: null,
        image: image,
      ),
    );

    // In case a message was already written in the input
    _updatedText(_textEditingController.text);
  }

  /// Send a new text message
  Future<void> _submitTextMessage(BuildContext context, String content) async {
    if (await _submitMessage(
            context,
            NewConversationMessage(
              conversationID: widget.conversationID,
              message: content,
            )) ==
        SendMessageResult.SUCCESS) _clearSendMessageForm();
  }

  /// Submit a new message
  Future<SendMessageResult> _submitMessage(
      BuildContext context, NewConversationMessage message) async {
    //Send the message
    _setSending(true);
    final result = await _conversationsHelper.sendMessage(message);
    _setSending(false);

    //Check the result of the operation
    if (result != SendMessageResult.SUCCESS)
      Scaffold.of(context).showSnackBar(
        SnackBar(
          content: Text(
            result == SendMessageResult.MESSAGE_REJECTED
                ? tr("Message rejected by the server!")
                : tr("Could not send message!"),
          ),
          duration: Duration(milliseconds: 500),
        ),
      );

    return result;
  }

  void _updatedText(String text) {
    setState(() {
      _isMessageValid = text.length > 4;
    });
  }

  /// Clear send message form
  void _clearSendMessageForm() {
    setState(() {
      _textEditingController = TextEditingController();
      _isMessageValid = false;
    });
  }

  /// Check if a message is the last message of a user or not
  bool _isLastMessage(int index) {
    return index == 0 ||
        (index > 0 && _messages[index - 1].userID != _messages[index].userID);
  }

  /// Check if a message is the first message of a user or not
  bool _isFirstMessage(int index) {
    return index == _messages.length - 1 ||
        (index < _messages.length - 1 &&
            _messages[index + 1].userID != _messages[index].userID);
  }

  /// Error handling
  Widget _buildError() {
    return buildErrorCard(tr("Could not load the list of messages!"));
  }

  /// Widget shown when loading older messages
  Widget _buildLoadingOlderMessage() {
    return Container(
      padding: EdgeInsets.all(8.0),
      child: CircularProgressIndicator(),
    );
  }

  /// Notice shown when there is no messages to show
  Widget _buildNoMessagesNotice() {
    return Expanded(
      child: Center(
        child: Text(tr("There is no message yet in this converation.")),
      ),
    );
  }

  /// Messages list
  Widget _buildMessagesList() {
    return Expanded(
      child: ListView.builder(
          controller: _scrollController,
          reverse: true,
          itemCount: _messages.length,
          itemBuilder: (c, i) {
            return ConversationMessageTile(
              message: _messages.elementAt(i),
              userInfo: _usersInfo.getUser(_messages[i].userID),
              isLastMessage: _isLastMessage(i),
              isFirstMessage: _isFirstMessage(i),
              onRequestMessageUpdate: _updateMessage,
              onRequestMessageDelete: _deleteMessage,
            );
          }),
    );
  }

  /// Send message form
  Widget _buildSendMessageForm() {
    return new Container(
      margin: const EdgeInsets.symmetric(horizontal: 8.0),
      child: new Row(
        children: <Widget>[
          // Image area
          new Container(
            margin: new EdgeInsets.symmetric(horizontal: 4.0),
            child: new IconButton(
              icon: new Icon(
                Icons.photo_camera,
                color: _isSendingMessage
                    ? Theme.of(context).disabledColor
                    : Theme.of(context).accentColor,
              ),
              onPressed: () => _sendImage(context),
            ),
          ),

          // Message area
          new Flexible(
            child: new TextField(
              keyboardType: TextInputType.text,
              maxLines: null,
              maxLength: 200,
              maxLengthEnforced: true,

              // Show max length only when there is some text already typed
              buildCounter: smartInputCounterWidgetBuilder,

              enabled: !_isSendingMessage,
              controller: _textEditingController,
              onChanged: _updatedText,
              onSubmitted: _isMessageValid
                  ? (s) => _submitTextMessage(context, s)
                  : null,
              decoration: new InputDecoration.collapsed(
                hintText: tr("Send a message"),
              ),
            ),
          ),

          // Send button
          new Container(
            margin: const EdgeInsets.symmetric(horizontal: 4.0),
            child: new IconButton(
              icon: new Icon(
                Icons.send,
                color: !_isSendingMessage && _isMessageValid
                    ? Theme.of(context).accentColor
                    : Theme.of(context).disabledColor,
              ),
              onPressed: !_isSendingMessage && _isMessageValid
                  ? () =>
                      _submitTextMessage(context, _textEditingController.text)
                  : null,
            ),
          ),
        ],
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    if (_error == ErrorLevel.MAJOR) return _buildError();

    if (_messages == null) return buildCenteredProgressBar();

    return Column(
      children: <Widget>[
        Container(
          child: _error == ErrorLevel.MINOR ? _buildError() : null,
        ),
        Container(
          child: _loadingOlderMessages == _OlderMessagesLevel.LOADING
              ? _buildLoadingOlderMessage()
              : null,
        ),
        _messages.length == 0 ? _buildNoMessagesNotice() : _buildMessagesList(),
        Divider(),
        _buildSendMessageForm()
      ],
    );
  }

  /// Request message content update
  Future<void> _updateMessage(ConversationMessage message) async {
    final newContent = await askUserString(
        context: context,
        title: tr("Update message"),
        message: tr("Please enter new message content:"),
        defaultValue: message.message.content,
        hint: tr("New message"));

    if (newContent == null) return;

    if (!await _conversationsHelper.updateMessage(message.id, newContent)) {
      showSimpleSnack(context, tr("Could not update message content!"));
      return;
    }
  }

  /// Request message deletion
  Future<void> _deleteMessage(ConversationMessage message) async {
    final choice = await showDialog<bool>(
      context: context,
      builder: (c) => AlertDialog(
        title: Text(tr("Confirm deletion")),
        content: Text(
          tr("Do you really want to delete this message ? The operation can not be cancelled !"),
          textAlign: TextAlign.justify,
        ),
        actions: <Widget>[
          FlatButton(
            child: Text(
              tr("Cancel").toUpperCase(),
            ),
            onPressed: () => Navigator.pop(c, false),
          ),
          FlatButton(
            child: Text(
              tr("Confirm").toUpperCase(),
              style: TextStyle(color: Colors.red),
            ),
            onPressed: () => Navigator.pop(c, true),
          ),
        ],
      ),
    );

    if (choice == null || !choice) return;

    // Execute the request
    if (!await _conversationsHelper.deleteMessage(message.id))
      showSimpleSnack(context, tr("Could not delete conversation message!"));
  }
}