import React from 'react';
import classNames from 'classnames';

import { ConversationHeader } from '../../conversation/ConversationHeader';
import { SessionCompositionBox } from './SessionCompositionBox';
import { SessionProgress } from '../SessionProgress'

import { Message } from '../../conversation/Message';
import { FriendRequest } from '../../conversation/FriendRequest';
import { TimerNotification } from '../../conversation/TimerNotification';


import { SessionScrollButton } from '../SessionScrollButton';

interface State {
  sendingProgess: number;
  prevSendingProgess: number;
  conversationKey: string;
  unreadCount: number;
  messages: Array<any>;
  selectedMessages: Array<string>;
  isScrolledToBottom: boolean;
  doneInitialScroll: boolean;
  displayScrollToBottomButton: boolean;
  messageFetchTimestamp: number;
  isRecording: boolean;
}

export class SessionConversation extends React.Component<any, State> {
  private messagesEndRef: React.RefObject<HTMLDivElement>;

  constructor(props: any) {
    super(props);
    
    const conversationKey = this.props.conversations.selectedConversation;
    const conversation = this.props.conversations.conversationLookup[conversationKey];
    const unreadCount = conversation.unreadCount;

    this.state = {
      sendingProgess: 0,
      prevSendingProgess: 0,
      conversationKey,
      unreadCount,
      messages: [],
      selectedMessages: [],
      isScrolledToBottom: !unreadCount,
      doneInitialScroll: false,
      displayScrollToBottomButton: false,
      messageFetchTimestamp: 0,
      isRecording: false,
    };

    this.handleScroll = this.handleScroll.bind(this);
    this.scrollToUnread = this.scrollToUnread.bind(this);
    this.scrollToBottom = this.scrollToBottom.bind(this);

    this.renderMessage = this.renderMessage.bind(this);
    this.renderTimerNotification = this.renderTimerNotification.bind(this);
    this.renderFriendRequest = this.renderFriendRequest.bind(this);

    this.onKeyDown = this.onKeyDown.bind(this);
    this.onStartedRecording = this.onStartedRecording.bind(this);
    this.onStoppedRecording = this.onStoppedRecording.bind(this);
    this.selectMessage = this.selectMessage.bind(this);
    this.resetSelection = this.resetSelection.bind(this);

    this.messagesEndRef = React.createRef();
  }

  public async componentWillMount() {
    await this.getMessages();
    
    // Inside a setTimeout to simultate onready()
    setTimeout(() => {
      this.scrollToUnread();
    }, 0);
    setTimeout(() => {
      this.setState({
        doneInitialScroll: true,
      });
    }, 100);
  }

  public componentDidUpdate(){
    // Keep scrolled to bottom unless user scrolls up
    if (this.state.isScrolledToBottom){
      this.scrollToBottom();
    }
  }

  public async componentWillReceiveProps() {
    const timestamp = this.getTimestamp();

    // If we have pulled messages in the last second, don't bother rescanning
    // This avoids getting messages on every re-render.
    if (timestamp > this.state.messageFetchTimestamp) {
      await this.getMessages();
    }
  }

  render() {
    console.log(`[vince][info] Props`, this.props);

    const { messages, conversationKey, doneInitialScroll, isRecording } = this.state;
    const loading = !doneInitialScroll || messages.length === 0;
    const selectionMode = !!this.state.selectedMessages.length;

    const conversation = this.props.conversations.conversationLookup[conversationKey];
    const conversationModel = window.getConversationByKey(conversationKey);
    const isRss = conversation.isRss;

    return (
      <div
        className={classNames('conversation-item', selectionMode && 'selection-mode')}
        tabIndex={0}
        onKeyDown={this.onKeyDown}  
      >
        <div className="conversation-header">
          {this.renderHeader()}
        </div>

        <SessionProgress
          visible={true}
          value={this.state.sendingProgess}
          prevValue={this.state.prevSendingProgess}
        />

        <div className="messages-wrapper">
          { loading && (
            <div className="messages-container__loading"></div>
          )}

          <div className="messages-container" onScroll={this.handleScroll}>
            {this.renderMessages()}
            <div ref={this.messagesEndRef} />
          </div>

          <SessionScrollButton display={true} onClick={this.scrollToBottom}/>
          { isRecording && (
            <div className="messages-wrapper--blocking-overlay"></div>
          )}
        </div>
        
        { !isRss && (
          <SessionCompositionBox
            sendMessage={conversationModel.sendMessage}
            onStartedRecording={this.onStartedRecording}
            onStoppedRecording={this.onStoppedRecording}
          />
        )}
        
      </div>
    );
  }

  public renderMessages() {
    const { messages } = this.state;

    // FIXME VINCE: IF MESSAGE IS THE TOP OF UNREAD, THEN INSERT AN UNREAD BANNER

    return (
      <>{
        messages.map((message: any) => {
          const messageProps = message.propsForMessage;
          const timerProps = message.propsForTimerNotification;
          const friendRequestProps = message.propsForFriendRequest;
          const attachmentProps = message.propsForAttachment;
          const groupNotificationProps = message.propsForGroupNotification;
          const quoteProps = message.propsForQuote;
          
          let item;
          // firstMessageOfSeries tells us to render the avatar only for the first message
          // in a series of messages from the same user
          item = messageProps     ? this.renderMessage(messageProps, message.firstMessageOfSeries) : item;
          item = timerProps       ? this.renderTimerNotification(timerProps) : item;
          item = quoteProps       ? this.renderMessage(timerProps, message.firstMessageOfSeries, quoteProps) : item;
          item = friendRequestProps
            ? this.renderFriendRequest(friendRequestProps): item;
          // item = attachmentProps  ? this.renderMessage(timerProps) : item;

          return item;
        })
      }</>
    );

  }

  public renderHeader() {
    const headerProps = this.getHeaderProps();

    return (
      <ConversationHeader
        id={headerProps.id}
        phoneNumber={headerProps.phoneNumber}
        isVerified={headerProps.isVerified}
        isMe={headerProps.isMe}
        isFriend={headerProps.isFriend}
        i18n={window.i18n}
        isGroup={headerProps.isGroup}
        isArchived={headerProps.isArchived}
        isPublic={headerProps.isPublic}
        isRss={headerProps.isRss}
        amMod={headerProps.amMod}
        members={headerProps.members}
        showBackButton={headerProps.showBackButton}
        timerOptions={headerProps.timerOptions}
        isBlocked={headerProps.isBlocked}
        hasNickname={headerProps.hasNickname}
        isFriendRequestPending={headerProps.isFriendRequestPending}
        isOnline={headerProps.isOnline}
        selectedMessages={headerProps.selectedMessages}
        onSetDisappearingMessages={headerProps.onSetDisappearingMessages}
        onDeleteMessages={headerProps.onDeleteMessages}
        onDeleteContact={headerProps.onDeleteContact}
        onResetSession={headerProps.onResetSession}
        onCloseOverlay={headerProps.onCloseOverlay}
        onDeleteSelectedMessages={headerProps.onDeleteSelectedMessages}
        onArchive={headerProps.onArchive}
        onMoveToInbox={headerProps.onMoveToInbox}
        onShowSafetyNumber={headerProps.onShowSafetyNumber}
        onShowAllMedia={headerProps.onShowAllMedia}
        onShowGroupMembers={headerProps.onShowGroupMembers}
        onGoBack={headerProps.onGoBack}
        onBlockUser={headerProps.onBlockUser}
        onUnblockUser={headerProps.onUnblockUser}
        onClearNickname={headerProps.onClearNickname}
        onChangeNickname={headerProps.onChangeNickname}
        onCopyPublicKey={headerProps.onCopyPublicKey}
        onLeaveGroup={headerProps.onLeaveGroup}
        onAddModerators={headerProps.onAddModerators}
        onRemoveModerators={headerProps.onRemoveModerators}
        onInviteFriends={headerProps.onInviteFriends}
      />
    );
  }


  public renderMessage(messageProps: any, firstMessageOfSeries: boolean, quoteProps?: any) {
    const selected = !! messageProps?.id
      && this.state.selectedMessages.includes(messageProps.id);

    return (
      <Message
        i18n = {window.i18n}
        text = {messageProps?.text}
        direction = {messageProps?.direction}
        selected = {selected}
        timestamp = {messageProps?.timestamp}
        attachments = {messageProps?.attachments}
        authorAvatarPath = {messageProps?.authorAvatarPath}
        authorColor = {messageProps?.authorColor}
        authorName = {messageProps?.authorName}
        authorPhoneNumber = {messageProps?.authorPhoneNumber}
        firstMessageOfSeries = {firstMessageOfSeries}
        authorProfileName = {messageProps?.authorProfileName}
        contact = {messageProps?.contact}
        conversationType = {messageProps?.conversationType}
        convoId = {messageProps?.convoId}
        expirationLength = {messageProps?.expirationLength}
        expirationTimestamp = {messageProps?.expirationTimestamp}
        id = {messageProps?.id}
        isDeletable = {messageProps?.isDeletable}
        isExpired = {messageProps?.isExpired}
        isModerator = {messageProps?.isModerator}
        isPublic = {messageProps?.isPublic}
        isRss = {messageProps?.isRss}
        multiSelectMode = {messageProps?.multiSelectMode}
        onBanUser = {messageProps?.onBanUser}
        onClickAttachment = {messageProps?.onClickAttachment}
        onClickLinkPreview = {messageProps?.onClickLinkPreview}
        onCopyPubKey = {messageProps?.onCopyPubKey}
        onCopyText = {messageProps?.onCopyText}
        onDelete = {messageProps?.onDelete}
        onDownload = {messageProps?.onDownload}
        onReply = {messageProps?.onReply}
        onRetrySend = {messageProps?.onRetrySend}
        onSelectMessage = {messageId => this.selectMessage(messageId)}
        onSelectMessageUnchecked = {messageProps?.onSelectMessageUnchecked}
        onShowDetail = {messageProps?.onShowDetail}
        onShowUserDetails = {messageProps?.onShowUserDetails}
        previews = {messageProps?.previews}
        quote = {quoteProps || undefined}
        senderIsModerator = {messageProps?.senderIsModerator}
        status = {messageProps?.status}
        textPending = {messageProps?.textPending}
      />
    );

  }

  public renderTimerNotification(timerProps: any) {
    return (
      <TimerNotification
        type={timerProps.type}
        phoneNumber={timerProps.phoneNumber}
        profileName={timerProps.profileName}
        name={timerProps.name}
        disabled={timerProps.disabled}
        timespan={timerProps.timespan}
        i18n={window.i18n}
      />
    );
  }
  
  public renderFriendRequest(friendRequestProps: any){
    return (
      <FriendRequest
        text={friendRequestProps.text}
        direction={friendRequestProps.direction}
        status={friendRequestProps.status}
        friendStatus={friendRequestProps.friendStatus}
        i18n={window.i18n}
        isBlocked={friendRequestProps.isBlocked}
        timestamp={friendRequestProps.timestamp}
        onAccept={friendRequestProps.onAccept}
        onDecline={friendRequestProps.onDecline}
        onDeleteConversation={friendRequestProps.onDeleteConversation}
        onRetrySend={friendRequestProps.onRetrySend}
        onBlockUser={friendRequestProps.onBlockUser}
        onUnblockUser={friendRequestProps.onUnblockUser}
      />
    );
  }

  public async getMessages(numMessages?: number, fetchInterval = window.CONSTANTS.MESSAGE_FETCH_INTERVAL, loopback = false){
    const { conversationKey, messageFetchTimestamp } = this.state;
    const timestamp = this.getTimestamp();

    // If we have pulled messages in the last interval, don't bother rescanning
    // This avoids getting messages on every re-render.
    const timeBuffer = timestamp - messageFetchTimestamp;
    if (timeBuffer < fetchInterval) {
      // Loopback gets messages after time has elapsed,
      // rather than completely cancelling the fetch.
      // if (loopback) {
      //   setTimeout(() => {
      //     this.getMessages(numMessages, fetchInterval, false);
      //   }, timeBuffer * 1000);
      // }      

      return { newTopMessage: undefined, previousTopMessage: undefined };
    }

    let msgCount = numMessages || window.CONSTANTS.DEFAULT_MESSAGE_FETCH_COUNT + this.state.unreadCount;
    msgCount = msgCount > window.CONSTANTS.MAX_MESSAGE_FETCH_COUNT
      ? window.CONSTANTS.MAX_MESSAGE_FETCH_COUNT
      : msgCount;

    const messageSet = await window.Signal.Data.getMessagesByConversation(
      conversationKey,
      { limit: msgCount, MessageCollection: window.Whisper.MessageCollection },
    );

    // Set first member of series here.
    const messageModels = messageSet.models;
    let messages = [];
    let previousSender;
    for (let i = 0; i < messageModels.length; i++){
      // Handle firstMessageOfSeries for conditional avatar rendering
      let firstMessageOfSeries = true;
      if (i > 0 && previousSender === messageModels[i].authorPhoneNumber){
        firstMessageOfSeries = false;
      }

      messages.push({...messageModels[i], firstMessageOfSeries});
      previousSender = messageModels[i].authorPhoneNumber;
    }

    const previousTopMessage = this.state.messages[0]?.id;
    const newTopMessage = messages[0]?.id;

    await this.setState({ messages, messageFetchTimestamp: timestamp });

    return { newTopMessage, previousTopMessage };
  }

  public getTimestamp() {
    return Math.floor(Date.now() / 1000);
  }

  public async handleScroll() {
    const { messages } = this.state;
    const messageContainer = document.getElementsByClassName('messages-container')[0];
    const isScrolledToBottom = messageContainer.scrollHeight - messageContainer.clientHeight <= messageContainer.scrollTop + 1;

    // FIXME VINCE: Update unread count
    // In models/conversations
    // Update unread count by geting all divs of .session-message
    // which are currently in view.

    // Pin scroll to bottom on new message, unless user has scrolled up
    if (this.state.isScrolledToBottom !== isScrolledToBottom){
      this.setState({ isScrolledToBottom });
    }

    // Fetch more messages when nearing the top of the message list
    const shouldFetchMoreMessages = messageContainer.scrollTop <= window.CONSTANTS.MESSAGE_CONTAINER_BUFFER_OFFSET_PX;
    
    if (shouldFetchMoreMessages){
      const numMessages = this.state.messages.length + window.CONSTANTS.DEFAULT_MESSAGE_FETCH_COUNT;
      
      // Prevent grabbing messags with scroll more frequently than once per 5s.
      const messageFetchInterval = 2;
      const previousTopMessage = (await this.getMessages(numMessages, messageFetchInterval, true))?.previousTopMessage;
      previousTopMessage && this.scrollToMessage(previousTopMessage);
    }
  }

  public scrollToUnread() {
    const { messages, unreadCount } = this.state;

    const message = messages[(messages.length - 1) - unreadCount];
    message && this.scrollToMessage(message.id);
  }

  public scrollToMessage(messageId: string) {
    const topUnreadMessage = document.getElementById(messageId);
    topUnreadMessage?.scrollIntoView();
  }

  public scrollToBottom() {
    // FIXME VINCE: Smooth scrolling that isn't slow@!
    // this.messagesEndRef.current?.scrollIntoView(
    //   { behavior: firstLoad ? 'auto' : 'smooth' }
    // );

    const messageContainer = document.getElementsByClassName('messages-container')[0];
    messageContainer.scrollTop = messageContainer.scrollHeight - messageContainer.clientHeight;
  }

  public getHeaderProps() {
    const {conversationKey} = this.state;
    const conversation = window.getConversationByKey(conversationKey);

    const expireTimer = conversation.get('expireTimer');
    const expirationSettingName = expireTimer
      ? window.Whisper.ExpirationTimerOptions.getName(expireTimer || 0)
      : null;

    const members = conversation.get('members') || [];

    return {
      id: conversation.id,
      name: conversation.getName(),
      phoneNumber: conversation.getNumber(),
      profileName: conversation.getProfileName(),
      color: conversation.getColor(),
      avatarPath: conversation.getAvatarPath(),
      isVerified: conversation.isVerified(),
      isFriendRequestPending: conversation.isPendingFriendRequest(),
      isFriend: conversation.isFriend(),
      isMe: conversation.isMe(),
      isClosable: conversation.isClosable(),
      isBlocked: conversation.isBlocked(),
      isGroup: !conversation.isPrivate(),
      isOnline: conversation.isOnline(),
      isArchived: conversation.get('isArchived'),
      isPublic: conversation.isPublic(),
      isRss: conversation.isRss(),
      amMod: conversation.isModerator(
        window.storage.get('primaryDevicePubKey')
      ),
      members,
      subscriberCount: conversation.get('subscriberCount'),
      selectedMessages: this.state.selectedMessages,
      expirationSettingName,
      showBackButton: Boolean(conversation.panels && conversation.panels.length),
      timerOptions: window.Whisper.ExpirationTimerOptions.map((item: any) => ({
        name: item.getName(),
        value: item.get('seconds'),
      })),
      hasNickname: !!conversation.getNickname(),

      onSetDisappearingMessages: (seconds: any) =>
      conversation.setDisappearingMessages(seconds),
      onDeleteMessages: () => conversation.destroyMessages(),
      onDeleteSelectedMessages: () => conversation.deleteSelectedMessages(),
      onCloseOverlay: () => conversation.resetMessageSelection(),
      onDeleteContact: () => conversation.deleteContact(),
      onResetSession: () => this.resetSelection(),

      // These are view only and don't update the Conversation model, so they
      //   need a manual update call.
      onShowSafetyNumber: () => {
        conversation.showSafetyNumber();
      },
      onShowAllMedia: async () => {
        conversation.updateHeader();
      },
      onShowGroupMembers: async () => {
        await conversation.showMembers();
        conversation.updateHeader();
      },
      onGoBack: () => {
        conversation.resetPanel();
        conversation.updateHeader();
      },

      onBlockUser: () => {
        conversation.block();
      },
      onUnblockUser: () => {
        conversation.unblock();
      },
      onChangeNickname: () => {
        conversation.changeNickname();
      },
      onClearNickname: () => {
        conversation.setNickname(null);
      },
      onCopyPublicKey: () => {
        conversation.copyPublicKey();
      },
      onArchive: () => {
        conversation.unload('archive');
        conversation.setArchived(true);
      },
      onMoveToInbox: () => {
        conversation.setArchived(false);
      },
      onLeaveGroup: () => {
        window.Whisper.events.trigger('leaveGroup', conversation);
      },

      onInviteFriends: () => {
        window.Whisper.events.trigger('inviteFriends', conversation);
      },

      onAddModerators: () => {
        window.Whisper.events.trigger('addModerators', conversation);
      },

      onRemoveModerators: () => {
        window.Whisper.events.trigger('removeModerators', conversation);
      },

      onAvatarClick: (pubkey: any) => {
        if (conversation.isPrivate()) {
          window.Whisper.events.trigger('onShowUserDetails', {
            userPubKey: pubkey,
          });
        } else if (!conversation.isRss()) {
          conversation.showGroupSettings();
        }
      },
    };
  };

  public selectMessage(messageId: string) {
    const selectedMessages = this.state.selectedMessages.includes(messageId)
      // Add to array if not selected. Else remove.
      ? this.state.selectedMessages.filter(id => id !== messageId)
      : [...this.state.selectedMessages, messageId];
    
    this.setState({ selectedMessages },
      () => console.log(`[vince] SelectedMessages: `, this.state.selectedMessages)
    );
  }

  public resetSelection(){
    this.setState({selectedMessages: []});
  }

  public getGroupSettingsProps() {
    const {conversationKey} = this.state;
    const conversation = window.getConversationByKey[conversationKey];

    const ourPK = window.textsecure.storage.user.getNumber();
    const members = conversation.get('members') || [];

    return {
      id: conversation.id,
      name: conversation.getName(),
      phoneNumber: conversation.getNumber(),
      profileName: conversation.getProfileName(),
      color: conversation.getColor(),
      avatarPath: conversation.getAvatarPath(),
      isGroup: !conversation.isPrivate(),
      isPublic: conversation.isPublic(),
      isAdmin: conversation.get('groupAdmins').includes(ourPK),
      isRss: conversation.isRss(),
      memberCount: members.length,

      timerOptions: window.Whisper.ExpirationTimerOptions.map((item: any) => ({
        name: item.getName(),
        value: item.get('seconds'),
      })),

      onSetDisappearingMessages: (seconds: any) =>
        conversation.setDisappearingMessages(seconds),

      onGoBack: () => {
        conversation.hideConversationRight();
      },

      onUpdateGroupName: () => {
        window.Whisper.events.trigger('updateGroupName', conversation);
      },
      onUpdateGroupMembers: () => {
        window.Whisper.events.trigger('updateGroupMembers', conversation);
      },

      onLeaveGroup: () => {
        window.Whisper.events.trigger('leaveGroup', conversation);
      },

      onInviteFriends: () => {
        window.Whisper.events.trigger('inviteFriends', conversation);
      },
      onShowLightBox: (lightBoxOptions = {}) => {
        conversation.showChannelLightbox(lightBoxOptions);
      },
    };
  };

  private onStartedRecording() {
    this.setState({
      isRecording: true,
      selectedMessages: [],
    })
  }

  private onStoppedRecording() {
    this.setState({
      isRecording: false,
    });
    
    console.log(`[vince] Stopped recording entirely`);
  }

  private onKeyDown(event: any) {
    const selectionMode = !!this.state.selectedMessages.length;
    
    const messageContainer = document.getElementsByClassName('messages-container')[0];
    const pageHeight = messageContainer.clientHeight;
    const arrowScrollPx = 50;
    const pageScrollPx = 0.80 * pageHeight;
    
    console.log(`[vince][key] event: `, event);

    console.log(`[vince][key] key: `, event.key);
    console.log(`[vince][key] key: `, event.keyCode);
    if (event.key === 'Escape') {
      
    }

    switch(event.key){
      case 'Escape':
        if (selectionMode){
          this.resetSelection();
        }
        break;

      // Scrolling
      case 'ArrowUp':
        messageContainer.scrollBy(0, -arrowScrollPx);
        break;
      case 'ArrowDown':
        messageContainer.scrollBy(0, arrowScrollPx);
        break;
      case 'PageUp':
        messageContainer.scrollBy(0, -pageScrollPx);
        break;
      case 'PageDown':
        messageContainer.scrollBy(0, pageScrollPx);
        break;
      default:
        break;
    }



  }
}


