package de.beyondsoft.ownchat.page.chat;

import android.net.Uri;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;

import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.github.lzyzsd.circleprogress.DonutProgress;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;

import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import butterknife.Optional;
import de.beyondsoft.ownchat.R;
import de.beyondsoft.ownchat.model.Message;
import de.beyondsoft.ownchat.utils.BeyondSoftLog;
import de.beyondsoft.ownchat.utils.DateUtils;
import de.beyondsoft.ownchat.utils.ui.GlideApp;
import de.beyondsoft.ownchat.utils.ui.ViewUtils;
import hani.momanii.supernova_emoji_library.Helper.EmojiconTextView;

public abstract class BaseChatAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    final static int DATE_TYPE       = 0;
    final static int LEFT_CHAT_TYPE  = 1;
    final static int RIGHT_CHAT_TYPE = 2;
    final static int LOADING_TYPE    = 3;

    private long mUserId;

    public final ArrayList<Message> mMessages  = new ArrayList<>();
    private final List<Integer> mDatePositions = new ArrayList<>();
    final List<Date> mDateList                 = new ArrayList<>();
    public final List<Integer> mPositionOffset = new ArrayList<>();

    private boolean mIsLoading;
    private int mLastOffset = 0;
    private Calendar mCurrentDay;
    private Calendar mLastDay;
    private MessageClickListener mMessageClickListener;
    private boolean mIsRetrying;
    private HashMap<Integer, Integer> mDownloadProgressMap = new HashMap<>();

    BaseChatAdapter(long userId) {
        mUserId = userId;
        mCurrentDay = Calendar.getInstance();
        mLastDay = Calendar.getInstance();
    }

    void setMessages(ArrayList<Message> messages) {
        mMessages.clear();
        mDatePositions.clear();
        mDateList.clear();
        mPositionOffset.clear();

        mIsLoading = false;
        mLastOffset = 0;
        mCurrentDay = Calendar.getInstance();
        mLastDay = Calendar.getInstance();
        mIsRetrying = false;

        if(!messages.isEmpty()) {
            mPositionOffset.add(0);
            mDateList.add(new Date(messages.get(0).createdAt));
        }

        int messageSize = messages.size();

        for(int i = 1; i < messageSize; i++) {
            mCurrentDay.setTimeInMillis(messages.get(i).createdAt);
            mLastDay.setTimeInMillis(messages.get(i - 1).createdAt);

            if(mCurrentDay.get(Calendar.DAY_OF_YEAR) != mLastDay.get(Calendar.DAY_OF_YEAR)) {
                mDatePositions.add(i + mLastOffset);
                mPositionOffset.add(0);
                mDateList.add(mLastDay.getTime());
                mLastOffset++;
            }

            mPositionOffset.add(mLastOffset);
            mDateList.add(new Date(messages.get(i).createdAt));
        }

        mMessages.addAll(messages);

        String[] messageTexts = new String[mMessages.size()];
        for(int i = 0; i < messageTexts.length; i++) {
            messageTexts[i] = mMessages.get(i).message;
        }

        notifyDataSetChanged();
    }

    // TODO: Clear the mDatePositions, mDateList, and mPositionOffset lists,
    // TODO: but keep the mMessages list to populate it. Then recalculate the
    // TODO: dates and the offsets.
    // TODO:
    void addMessagesWhenScrollingDown(ArrayList<Message> messages) {
        mDatePositions.clear();
        mDateList.clear();
        mPositionOffset.clear();

        mIsLoading = false;
        mLastOffset = 0;
        mCurrentDay = Calendar.getInstance();
        mLastDay = Calendar.getInstance();
        mIsRetrying = false;

        if(!mMessages.isEmpty()) {
            mPositionOffset.add(0);
            mDateList.add(new Date(mMessages.get(0).createdAt));
        }

        for(Message message : messages) {
            if(!mMessages.contains(message)) {
                mMessages.add(message);
            }
        }

        Collections.sort(mMessages, (m1, m2) -> Long.compare(m1.createdAt, m2.createdAt) * -1);

        int messageSize = mMessages.size();

        for(int i = 1; i < messageSize; i++) {
            mCurrentDay.setTimeInMillis(mMessages.get(i).createdAt);
            mLastDay.setTimeInMillis(mMessages.get(i - 1).createdAt);

            if(mCurrentDay.get(Calendar.DAY_OF_YEAR) != mLastDay.get(Calendar.DAY_OF_YEAR)) {
                mDatePositions.add(i + mLastOffset);
                mPositionOffset.add(0);
                mDateList.add(mLastDay.getTime());
                mLastOffset++;
            }

            mPositionOffset.add(mLastOffset);
            mDateList.add(new Date(mMessages.get(i).createdAt));
        }

        String[] messageTexts = new String[mMessages.size()];
        for(int i = 0; i < messageTexts.length; i++) {
            messageTexts[i] = mMessages.get(i).message;
        }

        notifyDataSetChanged();
    }

    void addMessages(ArrayList<Message> messages, boolean isAddingNewerMessages) {
        if(messages == null || messages.isEmpty() || mMessages.equals(messages)) {
            return;
        }

        ArrayList<Message> filteredList = new ArrayList<>();

        for(Message message : messages) {
            if(!mMessages.contains(message)) {
                filteredList.add(message);
            }
        }

        int currentMessagesCount = mMessages.size();
        int lastOffsetBeforeParsing = mLastOffset;
        int datesInserted = 0;

        Calendar calendar = Calendar.getInstance();
        calendar.setTimeInMillis(filteredList.get(0).createdAt);

        if(mCurrentDay.get(Calendar.DAY_OF_YEAR) != calendar.get(Calendar.DAY_OF_YEAR)) {
            mDatePositions.add(currentMessagesCount + mLastOffset);
            datesInserted++;
            mDateList.add(new Date(filteredList.get(0).createdAt));
            mPositionOffset.add(0);
            mLastOffset++;
        }

        mPositionOffset.add(mLastOffset);
        mDateList.add(new Date(filteredList.get(0).createdAt));
        int messageSize = filteredList.size();

        for(int i = 1; i < messageSize; i++) {
            mCurrentDay.setTimeInMillis(filteredList.get(i).createdAt);
            mLastDay.setTimeInMillis(filteredList.get(i - 1).createdAt);

            if(mCurrentDay.get(Calendar.DAY_OF_YEAR) != mLastDay.get(Calendar.DAY_OF_YEAR)) {
                mDatePositions.add(currentMessagesCount + mLastOffset + i);
                datesInserted++;
                mPositionOffset.add(0);
                mDateList.add(mLastDay.getTime());
                mLastOffset++;
            }

            mPositionOffset.add(mLastOffset);
            mDateList.add(new Date(filteredList.get(i).createdAt));
        }

        if(!isAddingNewerMessages) {
            mMessages.addAll(filteredList);
        } else {
            mMessages.addAll(0, filteredList);
            Collections.sort(mMessages, (m1, m2) -> Long.compare(m1.createdAt, m2.createdAt) * -1);
        }

        if(!isAddingNewerMessages) {
            notifyItemRangeInserted(currentMessagesCount + lastOffsetBeforeParsing, filteredList.size() + datesInserted);
        } else {
            notifyItemRangeInserted(0, filteredList.size() + datesInserted);
        }
    }

    void setItemProgress(int itemPosition, int progress) {
        mDownloadProgressMap.put(itemPosition, progress);
        notifyItemChanged(itemPosition);
    }

    void stopItemProgress(int itemPosition) {
        mDownloadProgressMap.remove(itemPosition);
        notifyItemChanged(itemPosition);
    }

    void resetItemsProgress() {
        mDownloadProgressMap.clear();
        notifyDataSetChanged();
    }

    void addMessage(Message message) {
        if(mMessages.contains(message)) {
            return;
        }

        ArrayList<Message> currentMessages = new ArrayList<>();
        currentMessages.add(message);
        currentMessages.addAll(mMessages);
        mMessages.clear();
        mMessages.addAll(currentMessages);

        Calendar lastDay = Calendar.getInstance();

        if(mMessages.size() > 1) {
            lastDay.setTimeInMillis(mMessages.get(1).createdAt);
        }

        Calendar calendar = Calendar.getInstance();
        calendar.setTimeInMillis(message.createdAt);

        if(calendar.get(Calendar.DAY_OF_YEAR) != lastDay.get(Calendar.DAY_OF_YEAR)) {
            // New date inserted
            setMessages(new ArrayList<>(mMessages));
            return;
        }

        List<Integer> positionOffset = new ArrayList<>();
        positionOffset.add(0);
        positionOffset.addAll(mPositionOffset);
        mPositionOffset.clear();
        mPositionOffset.addAll(positionOffset);

        List<Date> currentDateList = new ArrayList<>();
        currentDateList.add(new Date(message.createdAt));
        currentDateList.addAll(mDateList);
        mDateList.clear();
        mDateList.addAll(currentDateList);

        List<Integer> currentDatePositions = new ArrayList<>();
        boolean newDateAdded = false;

        if(calendar.get(Calendar.DAY_OF_YEAR) != lastDay.get(Calendar.DAY_OF_YEAR)) {
            currentDatePositions.add(1);
            newDateAdded = true;
        }

        for(int i = 0, size = mDatePositions.size(); i < size; i++) {
            currentDatePositions.add(mDatePositions.get(i) + (newDateAdded ? 2 : 1));
        }

        mDatePositions.clear();
        mDatePositions.addAll(currentDatePositions);

        notifyItemInserted(0);
    }

    void setLoading(boolean loading) {
        mIsLoading = loading;

        if(loading) {
            notifyItemRangeInserted(getItemCount(), 1);
        } else {
            notifyItemRemoved(getItemCount() - 1);
        }
    }

    public int getPosition(Message message) {
        int position = 0;

        for(int i = 0; i < mMessages.size(); i++) {
            if(message.equals(mMessages.get(i))) {
                position = i;
                break;
            }
        }

        position += mPositionOffset.get(position);

        return position;
    }

    @Override
    public int getItemCount() {
        return mMessages.size() + mLastOffset + (mIsLoading ? 1 : 0);
    }

    void updateMessage(Message newMessage, String oldMessageId) {
        int index = -1;
        for(int i = 0; i < mMessages.size(); i++) {
            if(oldMessageId.equals(mMessages.get(i).id)) {
                index = i;
                break;
            }
        }

        if(index == -1) {
            Log.e("BaseChatAdapter", "message not found!! " + newMessage.toString());
            return;
        }

        mMessages.set(index, newMessage);
        notifyItemChanged(index);
    }

    @Override
    public int getItemViewType(int position) {
        if(mDatePositions.contains(position)) {
            return DATE_TYPE;
        }

        if(position == mMessages.size() + mLastOffset) {
            return LOADING_TYPE;
        }

        return mMessages.get(position - mPositionOffset.get(position)).senderId == mUserId ? RIGHT_CHAT_TYPE : LEFT_CHAT_TYPE;
    }

    void setMessageClickListener(MessageClickListener messageClickListener) {
        mMessageClickListener = messageClickListener;
    }

    void enableMessageRetry(String messageId) {
        mIsRetrying = false;
        Message myMessage = null;
        int index = -1;
        for(int i = 0; i < mMessages.size(); i++) {
            Message message = mMessages.get(i);
            if(messageId.equals(message.id)) {
                index = i;
                myMessage = message;
                myMessage.status = Message.Status.FAILED;
                break;
            }
        }

        if(myMessage == null) {
            Log.e("BaseChatAdapter", "Message not found! " + messageId);
            return;
        }

        mMessages.set(index, myMessage);
        notifyItemChanged(index);
    }

    void setSingleMessage(MessageViewHolder viewHolder, Message message, int position, boolean isLeft) {
        setMessage(viewHolder, message, position, false, isLeft);
    }

    void setGroupMessage(MessageViewHolder viewHolder, Message message, int position, boolean isLeft) {
        setMessage(viewHolder, message, position, true, isLeft);
    }

    private void setMessage(MessageViewHolder viewHolder, Message message, int position, boolean isGroup, boolean isLeft) {
        if(isGroup) {
            viewHolder.timeTextView.setText(String.format(Locale.getDefault(), "%s | %s", isLeft ? message.senderName : viewHolder.timeTextView.getContext().getString(R.string.chat_you_name), DateUtils.toHourFormatHour(message.createdAt)));

            if(TextUtils.isEmpty(message.senderAvatar)) {
                viewHolder.contactImageView.setImageResource(R.drawable.avatar);
            } else {
                GlideApp.with(viewHolder.contactImageView.getContext())
                        .load(message.senderAvatar)
                        .placeholder(R.drawable.avatar)
                        .diskCacheStrategy(DiskCacheStrategy.ALL)
                        .into(viewHolder.contactImageView);
            }
        } else {
            viewHolder.timeTextView.setText(DateUtils.toHourFormatHour(message.createdAt));
        }

        if(!TextUtils.isEmpty(message.filePath)) {
            viewHolder.messageTextView.setVisibility(View.GONE);

            int maxWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200, viewHolder.filenameTextView.getResources().getDisplayMetrics());

            if(mDownloadProgressMap.containsKey(position)) {
                viewHolder.fileImageView.setVisibility(View.GONE);
                viewHolder.donutProgress.setVisibility(View.VISIBLE);
                viewHolder.donutProgress.setProgress(mDownloadProgressMap.get(position));
                viewHolder.donutProgress.setText(String.format(Locale.getDefault(), "%d %%", mDownloadProgressMap.get(position)));

                if(!message.filePath.toLowerCase().endsWith(".png") && !message.filePath.toLowerCase().endsWith(".jpg") && !message.filePath.toLowerCase().endsWith(".gif")) {
                    viewHolder.filenameTextView.setVisibility(View.VISIBLE);
                    viewHolder.filenameTextView.setText(message.originalFilename);
                    viewHolder.filenameTextView.setMaxWidth(maxWidth);
                } else {
                    viewHolder.filenameTextView.setVisibility(View.GONE);
                }
            } else {
                viewHolder.donutProgress.setVisibility(View.GONE);
                viewHolder.fileImageView.setVisibility(View.VISIBLE);

                if(!message.filePath.toLowerCase().endsWith(".png") && !message.filePath.toLowerCase().endsWith(".jpg") && !message.filePath.toLowerCase().endsWith(".gif")) {
                    viewHolder.filenameTextView.setVisibility(View.VISIBLE);
                    viewHolder.filenameTextView.setText(message.originalFilename);
                    viewHolder.filenameTextView.setMaxWidth(maxWidth);
                } else {
                    viewHolder.filenameTextView.setVisibility(View.GONE);
                }

                viewHolder.fileImageView.setImageDrawable(null);

                GlideApp.with(viewHolder.fileImageView.getContext())
                        .load(TextUtils.isEmpty(message.smallerImage) ? message.filePath : message.smallerImage)
                        .placeholder(R.drawable.document)
                        .override(500, 500)
                        .diskCacheStrategy(DiskCacheStrategy.ALL)
                        .into(viewHolder.fileImageView);
            }

            if(!TextUtils.isEmpty(message.message)) {
                viewHolder.messageTextView.setVisibility(View.VISIBLE);
                viewHolder.messageTextView.setMaxWidth(maxWidth);
                viewHolder.messageTextView.setText(message.message);
            }
        } else {
            viewHolder.donutProgress.setVisibility(View.GONE);
            viewHolder.fileImageView.setVisibility(View.GONE);
            viewHolder.filenameTextView.setVisibility(View.GONE);
            viewHolder.messageTextView.setVisibility(View.VISIBLE);
            viewHolder.messageTextView.setText(message.message);
        }
    }

    static class DateViewHolder extends RecyclerView.ViewHolder {
        @BindView(R.id.chat_item_date)
        TextView dateTextView;

        DateViewHolder(View itemView) {
            super(itemView);
            ButterKnife.bind(this, itemView);
        }
    }

    class MessageViewHolder extends RecyclerView.ViewHolder {

        @Nullable
        @BindView(R.id.chat_item_message_status_progress_bar)
        ProgressBar statusProgressBar;

        @Nullable
        @BindView(R.id.chat_item_message_status)
        ImageView statusImageView;

        @Nullable
        @BindView(R.id.chat_item_sending_failed)
        TextView sendingFailedTextView;

        @Nullable
        @BindView(R.id.chat_item_contact_image)
        ImageView contactImageView;

        @BindView(R.id.chat_item_time)
        TextView timeTextView;

        @BindView(R.id.chat_item_message)
        EmojiconTextView messageTextView;

        @BindView(R.id.chat_item_file)
        ImageView fileImageView;

        @BindView(R.id.chat_item_progress)
        DonutProgress donutProgress;

        @BindView(R.id.chat_item_file_name)
        TextView filenameTextView;

        @BindView(R.id.chat_item_container)
        RelativeLayout chatItemContainer;

        MessageViewHolder(View itemView) {
            super(itemView);
            ButterKnife.bind(this, itemView);
        }

        @Optional
        @OnClick(R.id.chat_item_container)
        void jumpToMessage() {
            int currentPosition = getAdapterPosition();

            int position = currentPosition - mPositionOffset.get(currentPosition);
            if(position >= mMessages.size()) {
                position = 0;
            }

            Message messageToJumpTo = mMessages.get(position);

            mMessageClickListener.jumpToMessage(messageToJumpTo);
        }

        @Optional
        @OnClick(R.id.chat_item_sending_failed)
        void retrySendingClicked() {
            if(mIsRetrying) {
                return;
            }

            mIsRetrying = true;
            int currentPosition = getAdapterPosition();

            Message messageToResend = mMessages.get(currentPosition - mPositionOffset.get(currentPosition));

            if(statusProgressBar != null) {
                statusProgressBar.setVisibility(View.VISIBLE);
            }

            ViewUtils.setProgressBarColor(statusProgressBar, R.color.colorWhite);

            if(statusImageView != null) {
                statusImageView.setVisibility(View.GONE);
            }

            if(sendingFailedTextView != null) {
                sendingFailedTextView.setVisibility(View.GONE);
            }

            mMessageClickListener.resendMessage(messageToResend);
        }

        @Optional
        @OnClick(R.id.chat_item_file)
        void fileClicked() {
            int currentPosition = getAdapterPosition();
            Message message = mMessages.get(currentPosition - mPositionOffset.get(currentPosition));

            if(message.status != Message.Status.DELIVERED) {
                mMessageClickListener.localFileClicked(Uri.parse(message.uri));
                return;
            }

            if(mDownloadProgressMap.containsKey(currentPosition)) {
                return;
            }

            mDownloadProgressMap.put(currentPosition, 0);
            mMessageClickListener.fileClicked(message, getAdapterPosition());
        }
    }

    static class ProgressViewHolder extends RecyclerView.ViewHolder {
        ProgressViewHolder(View itemView) {
            super(itemView);
        }
    }

    interface MessageClickListener {
        void fileClicked(Message message, int itemPosition);

        void localFileClicked(Uri uri);

        void resendMessage(Message message);

        void jumpToMessage(Message message);
    }

    public int getPositionOffsetForIndex(int index) {
        if(mPositionOffset.size() == 0 || index < 0) {
            return 0;
        }

        return mPositionOffset.get(index);
    }
}
