package net.letime.lecjoa.adapter;

import android.content.Context;
import android.graphics.drawable.Drawable;
import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.content.res.AppCompatResources;
import android.support.v7.widget.RecyclerView;
import android.text.Spanned;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.ToggleButton;

import net.letime.lecjoa.R;
import net.letime.lecjoa.entity.Attachment;
import net.letime.lecjoa.entity.Emoji;
import net.letime.lecjoa.entity.Status;
import net.letime.lecjoa.interfaces.StatusActionListener;
import net.letime.lecjoa.util.CustomEmojiHelper;
import net.letime.lecjoa.util.DateUtils;
import net.letime.lecjoa.util.HtmlUtils;
import net.letime.lecjoa.util.LinkHelper;
import net.letime.lecjoa.util.ThemeUtils;
import net.letime.lecjoa.view.RoundedTransformation;
import net.letime.lecjoa.viewdata.StatusViewData;
import com.mikepenz.iconics.utils.Utils;
import com.squareup.picasso.Picasso;

import at.connyduck.sparkbutton.SparkButton;
import at.connyduck.sparkbutton.SparkEventListener;

import java.util.Date;
import java.util.List;

abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
    private View container;
    private TextView displayName;
    private TextView username;
    private ImageButton replyButton;
    private SparkButton reblogButton;
    private SparkButton favouriteButton;
    private ImageButton moreButton;
    private boolean favourited;
    private boolean reblogged;
    private ImageView mediaPreview0;
    private ImageView mediaPreview1;
    private ImageView mediaPreview2;
    private ImageView mediaPreview3;
    private ImageView mediaOverlay0;
    private ImageView mediaOverlay1;
    private ImageView mediaOverlay2;
    private ImageView mediaOverlay3;
    private TextView sensitiveMediaWarning;
    private View sensitiveMediaShow;
    private TextView mediaLabel;
    private ToggleButton contentWarningButton;

    ImageView avatar;
    TextView timestampInfo;
    TextView content;
    TextView contentWarningDescription;

    StatusBaseViewHolder(View itemView) {
        super(itemView);
        container = itemView.findViewById(R.id.status_container);
        displayName = itemView.findViewById(R.id.status_display_name);
        username = itemView.findViewById(R.id.status_username);
        timestampInfo = itemView.findViewById(R.id.status_timestamp_info);
        content = itemView.findViewById(R.id.status_content);
        avatar = itemView.findViewById(R.id.status_avatar);
        replyButton = itemView.findViewById(R.id.status_reply);
        reblogButton = itemView.findViewById(R.id.status_reblog);
        favouriteButton = itemView.findViewById(R.id.status_favourite);
        moreButton = itemView.findViewById(R.id.status_more);
        reblogged = false;
        favourited = false;
        mediaPreview0 = itemView.findViewById(R.id.status_media_preview_0);
        mediaPreview1 = itemView.findViewById(R.id.status_media_preview_1);
        mediaPreview2 = itemView.findViewById(R.id.status_media_preview_2);
        mediaPreview3 = itemView.findViewById(R.id.status_media_preview_3);
        mediaOverlay0 = itemView.findViewById(R.id.status_media_overlay_0);
        mediaOverlay1 = itemView.findViewById(R.id.status_media_overlay_1);
        mediaOverlay2 = itemView.findViewById(R.id.status_media_overlay_2);
        mediaOverlay3 = itemView.findViewById(R.id.status_media_overlay_3);
        sensitiveMediaWarning = itemView.findViewById(R.id.status_sensitive_media_warning);
        sensitiveMediaShow = itemView.findViewById(R.id.status_sensitive_media_button);
        mediaLabel = itemView.findViewById(R.id.status_media_label);
        contentWarningDescription = itemView.findViewById(R.id.status_content_warning_description);
        contentWarningButton = itemView.findViewById(R.id.status_content_warning_button);
    }

    protected abstract int getMediaPreviewHeight(Context context);

    private void setDisplayName(String name, List<Emoji> customEmojis) {
        CharSequence emojifiedName = CustomEmojiHelper.emojifyString(name, customEmojis, displayName);
        displayName.setText(emojifiedName);
    }

    private void setUsername(String name) {
        Context context = username.getContext();
        String format = context.getString(R.string.status_username_format);
        String usernameText = String.format(format, name);
        username.setText(usernameText);
    }

    private void setContent(Spanned content, Status.Mention[] mentions, List<Emoji> emojis,
                            StatusActionListener listener) {
        Spanned emojifiedText = CustomEmojiHelper.emojifyText(content, emojis, this.content);

        LinkHelper.setClickableText(this.content, emojifiedText, mentions, listener);
    }

    void setAvatar(String url, @Nullable String rebloggedUrl) {
        if (TextUtils.isEmpty(url)) {
            avatar.setImageResource(R.drawable.avatar_default);
        } else {
            Picasso.with(avatar.getContext())
                    .load(url)
                    .placeholder(R.drawable.avatar_default)
                    .transform(new RoundedTransformation(25))
                    .into(avatar);
        }
    }

    protected void setCreatedAt(@Nullable Date createdAt) {
        // This is the visible timestampInfo.
        String readout;
        /* This one is for screen-readers. Frequently, they would mispronounce timestamps like "17m"
         * as 17 meters instead of minutes. */
        CharSequence readoutAloud;
        if (createdAt != null) {
            long then = createdAt.getTime();
            long now = new Date().getTime();
            readout = DateUtils.getRelativeTimeSpanString(timestampInfo.getContext(), then, now);
            readoutAloud = android.text.format.DateUtils.getRelativeTimeSpanString(then, now,
                    android.text.format.DateUtils.SECOND_IN_MILLIS,
                    android.text.format.DateUtils.FORMAT_ABBREV_RELATIVE);
        } else {
            // unknown minutes~
            readout = "?m";
            readoutAloud = "? minutes";
        }
        timestampInfo.setText(readout);
        timestampInfo.setContentDescription(readoutAloud);
    }

    protected void showContent(boolean show) {
        if (show) {
            container.setVisibility(View.VISIBLE);
            container.getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
        } else {
            container.setVisibility(View.INVISIBLE);
            container.getLayoutParams().height = Utils.convertDpToPx(container.getContext(), 24);
        }
    }

    private void setIsReply(boolean isReply) {
        if (isReply) {
            replyButton.setImageResource(R.drawable.ic_reply_all_24dp);
        } else {
            replyButton.setImageResource(R.drawable.ic_reply_24dp);
        }

    }

    private void setReblogged(boolean reblogged) {
        this.reblogged = reblogged;
        reblogButton.setChecked(reblogged);
    }

    // This should only be called after setReblogged, in order to override the tint correctly.
    private void setRebloggingEnabled(boolean enabled, Status.Visibility visibility) {
        reblogButton.setEnabled(enabled && visibility != Status.Visibility.PRIVATE);

        if (enabled) {
            int inactiveId;
            int activeId;
            if (visibility == Status.Visibility.PRIVATE) {
                inactiveId = ThemeUtils.getDrawableId(reblogButton.getContext(),
                        R.attr.status_reblog_disabled_drawable, R.drawable.reblog_private_dark);
                activeId = R.drawable.reblog_private_active;
            } else {
                inactiveId = ThemeUtils.getDrawableId(reblogButton.getContext(),
                        R.attr.status_reblog_inactive_drawable, R.drawable.reblog_inactive_dark);
                activeId = R.drawable.reblog_active;
            }
            reblogButton.setInactiveImage(inactiveId);
            reblogButton.setActiveImage(activeId);
        } else {
            int disabledId;
            if (visibility == Status.Visibility.DIRECT) {
                disabledId = ThemeUtils.getDrawableId(reblogButton.getContext(),
                        R.attr.status_reblog_direct_drawable, R.drawable.reblog_direct_dark);
            } else {
                disabledId = ThemeUtils.getDrawableId(reblogButton.getContext(),
                        R.attr.status_reblog_disabled_drawable, R.drawable.reblog_private_dark);
            }
            reblogButton.setInactiveImage(disabledId);
            reblogButton.setActiveImage(disabledId);
        }
    }

    private void setFavourited(boolean favourited) {
        this.favourited = favourited;
        favouriteButton.setChecked(favourited);
    }

    private void setMediaPreviews(final List<Attachment> attachments, boolean sensitive,
                                  final StatusActionListener listener, boolean showingContent) {
        final ImageView[] previews = {
                mediaPreview0, mediaPreview1, mediaPreview2, mediaPreview3
        };
        final ImageView[] overlays = {
                mediaOverlay0, mediaOverlay1, mediaOverlay2, mediaOverlay3
        };
        Context context = mediaPreview0.getContext();

        int mediaPreviewUnloadedId =
                ThemeUtils.getDrawableId(itemView.getContext(), R.attr.media_preview_unloaded_drawable,
                        android.R.color.black);

        final int n = Math.min(attachments.size(), Status.MAX_MEDIA_ATTACHMENTS);

        for (int i = 0; i < n; i++) {
            String previewUrl = attachments.get(i).getPreviewUrl();
            String description = attachments.get(i).getDescription();

            if (TextUtils.isEmpty(description)) {
                previews[i].setContentDescription(context.getString(R.string.action_view_media));
            } else {
                previews[i].setContentDescription(description);
            }

            previews[i].setVisibility(View.VISIBLE);

            if (TextUtils.isEmpty(previewUrl)) {
                Picasso.with(context)
                        .load(mediaPreviewUnloadedId)
                        .into(previews[i]);
            } else {
                Picasso.with(context)
                        .load(previewUrl)
                        .placeholder(mediaPreviewUnloadedId)
                        .into(previews[i]);
            }

            final Attachment.Type type = attachments.get(i).getType();
            if (type == Attachment.Type.VIDEO | type == Attachment.Type.GIFV) {
                overlays[i].setVisibility(View.VISIBLE);
            } else {
                overlays[i].setVisibility(View.GONE);
            }

            final int urlIndex = i;
            previews[i].setOnClickListener(v -> listener.onViewMedia(getAdapterPosition(), urlIndex, v));

            if (n <= 2) {
                previews[0].getLayoutParams().height = getMediaPreviewHeight(context) * 2;
                previews[1].getLayoutParams().height = getMediaPreviewHeight(context) * 2;
            } else {
                previews[0].getLayoutParams().height = getMediaPreviewHeight(context);
                previews[1].getLayoutParams().height = getMediaPreviewHeight(context);
                previews[2].getLayoutParams().height = getMediaPreviewHeight(context);
                previews[3].getLayoutParams().height = getMediaPreviewHeight(context);
            }
        }

        String hiddenContentText;
        if (sensitive) {
            hiddenContentText = context.getString(R.string.status_sensitive_media_template,
                    context.getString(R.string.status_sensitive_media_title),
                    context.getString(R.string.status_sensitive_media_directions));

        } else {
            hiddenContentText = context.getString(R.string.status_sensitive_media_template,
                    context.getString(R.string.status_media_hidden_title),
                    context.getString(R.string.status_sensitive_media_directions));
        }

        sensitiveMediaWarning.setText(HtmlUtils.fromHtml(hiddenContentText));

        sensitiveMediaWarning.setVisibility(showingContent ? View.GONE : View.VISIBLE);
        sensitiveMediaShow.setVisibility(showingContent ? View.VISIBLE : View.GONE);
        sensitiveMediaShow.setOnClickListener(v -> {
            if (getAdapterPosition() != RecyclerView.NO_POSITION) {
                listener.onContentHiddenChange(false, getAdapterPosition());
            }
            v.setVisibility(View.GONE);
            sensitiveMediaWarning.setVisibility(View.VISIBLE);
        });
        sensitiveMediaWarning.setOnClickListener(v -> {
            if (getAdapterPosition() != RecyclerView.NO_POSITION) {
                listener.onContentHiddenChange(true, getAdapterPosition());
            }
            v.setVisibility(View.GONE);
            sensitiveMediaShow.setVisibility(View.VISIBLE);
        });


        // Hide any of the placeholder previews beyond the ones set.
        for (int i = n; i < Status.MAX_MEDIA_ATTACHMENTS; i++) {
            previews[i].setVisibility(View.GONE);
        }
    }

    @NonNull
    private static String getLabelTypeText(Context context, Attachment.Type type) {
        switch (type) {
            default:
            case IMAGE:
                return context.getString(R.string.status_media_images);
            case GIFV:
            case VIDEO:
                return context.getString(R.string.status_media_video);
        }
    }

    @DrawableRes
    private static int getLabelIcon(Attachment.Type type) {
        switch (type) {
            default:
            case IMAGE:
                return R.drawable.ic_photo_24dp;
            case GIFV:
            case VIDEO:
                return R.drawable.ic_videocam_24dp;
        }
    }

    private void setMediaLabel(List<Attachment> attachments, boolean sensitive,
                               final StatusActionListener listener) {
        if (attachments.size() == 0) {
            mediaLabel.setVisibility(View.GONE);
            return;
        }
        mediaLabel.setVisibility(View.VISIBLE);

        // Set the label's text.
        Context context = itemView.getContext();
        String labelText = getLabelTypeText(context, attachments.get(0).getType());
        if (sensitive) {
            String sensitiveText = context.getString(R.string.status_sensitive_media_title);
            labelText += String.format(" (%s)", sensitiveText);
        }
        mediaLabel.setText(labelText);

        // Set the icon next to the label.
        int drawableId = getLabelIcon(attachments.get(0).getType());
        Drawable drawable = AppCompatResources.getDrawable(context, drawableId);
        ThemeUtils.setDrawableTint(context, drawable, android.R.attr.textColorTertiary);
        mediaLabel.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null);

        mediaLabel.setOnClickListener(v -> listener.onViewMedia(getAdapterPosition(), 0, null));
    }

    private void hideSensitiveMediaWarning() {
        sensitiveMediaWarning.setVisibility(View.GONE);
        sensitiveMediaShow.setVisibility(View.GONE);
    }

    private void setSpoilerText(String spoilerText, List<Emoji> emojis,
                                final boolean expanded, final StatusActionListener listener) {
        CharSequence emojiSpoiler =
                CustomEmojiHelper.emojifyString(spoilerText, emojis, contentWarningDescription);
        contentWarningDescription.setText(emojiSpoiler);
        contentWarningDescription.setVisibility(View.VISIBLE);
        contentWarningButton.setVisibility(View.VISIBLE);
        contentWarningButton.setChecked(expanded);
        contentWarningButton.setOnCheckedChangeListener((buttonView, isChecked) -> {
            contentWarningDescription.invalidate();
            if (getAdapterPosition() != RecyclerView.NO_POSITION) {
                listener.onExpandedChange(isChecked, getAdapterPosition());
            }
            if (isChecked) {
                content.setVisibility(View.VISIBLE);
            } else {
                content.setVisibility(View.GONE);
            }
        });
        if (expanded) {
            content.setVisibility(View.VISIBLE);
        } else {
            content.setVisibility(View.GONE);
        }
    }

    private void hideSpoilerText() {
        contentWarningDescription.setVisibility(View.GONE);
        contentWarningButton.setVisibility(View.GONE);
        content.setVisibility(View.VISIBLE);
    }

    private void setupButtons(final StatusActionListener listener, final String accountId) {
        /* Originally position was passed through to all these listeners, but it caused several
         * bugs where other statuses in the list would be removed or added and cause the position
         * here to become outdated. So, getting the adapter position at the time the listener is
         * actually called is the appropriate solution. */
        avatar.setOnClickListener(v -> listener.onViewAccount(accountId));
        replyButton.setOnClickListener(v -> {
            int position = getAdapterPosition();
            if (position != RecyclerView.NO_POSITION) {
                listener.onReply(position);
            }
        });
        reblogButton.setEventListener(new SparkEventListener() {
            @Override
            public void onEvent(ImageView button, boolean buttonState) {
                int position = getAdapterPosition();
                if (position != RecyclerView.NO_POSITION) {
                    listener.onReblog(!reblogged, position);
                }
            }

            @Override
            public void onEventAnimationEnd(ImageView button, boolean buttonState) {
            }

            @Override
            public void onEventAnimationStart(ImageView button, boolean buttonState) {
            }
        });
        favouriteButton.setEventListener(new SparkEventListener() {
            @Override
            public void onEvent(ImageView button, boolean buttonState) {
                int position = getAdapterPosition();
                if (position != RecyclerView.NO_POSITION) {
                    listener.onFavourite(!favourited, position);
                }
            }

            @Override
            public void onEventAnimationEnd(ImageView button, boolean buttonState) {
            }

            @Override
            public void onEventAnimationStart(ImageView button, boolean buttonState) {
            }
        });
        moreButton.setOnClickListener(v -> {
            int position = getAdapterPosition();
            if (position != RecyclerView.NO_POSITION) {
                listener.onMore(v, position);
            }
        });
        /* Even though the content TextView is a child of the container, it won't respond to clicks
         * if it contains URLSpans without also setting its listener. The surrounding spans will
         * just eat the clicks instead of deferring to the parent listener, but WILL respond to a
         * listener directly on the TextView, for whatever reason. */
        View.OnClickListener viewThreadListener = v -> {
            int position = getAdapterPosition();
            if (position != RecyclerView.NO_POSITION) {
                listener.onViewThread(position);
            }
        };
        content.setOnClickListener(viewThreadListener);
        container.setOnClickListener(viewThreadListener);
    }

    void setupWithStatus(StatusViewData.Concrete status, final StatusActionListener listener,
                         boolean mediaPreviewEnabled) {
        setDisplayName(status.getUserFullName(), status.getAccountEmojis());
        setUsername(status.getNickname());
        setCreatedAt(status.getCreatedAt());
        setIsReply(status.getInReplyToId() != null);
        setContent(status.getContent(), status.getMentions(), status.getStatusEmojis(), listener);
        setAvatar(status.getAvatar(), status.getRebloggedAvatar());
        setReblogged(status.isReblogged());
        setFavourited(status.isFavourited());
        List<Attachment> attachments = status.getAttachments();
        boolean sensitive = status.isSensitive();
        if (mediaPreviewEnabled) {
            setMediaPreviews(attachments, sensitive, listener, status.isShowingContent());

            if (attachments.size() == 0) {
                hideSensitiveMediaWarning();
            }
            // Hide the unused label.
            mediaLabel.setVisibility(View.GONE);
        } else {
            setMediaLabel(attachments, sensitive, listener);
            // Hide all unused views.
            mediaPreview0.setVisibility(View.GONE);
            mediaPreview1.setVisibility(View.GONE);
            mediaPreview2.setVisibility(View.GONE);
            mediaPreview3.setVisibility(View.GONE);
            hideSensitiveMediaWarning();
        }

        setupButtons(listener, status.getSenderId());
        setRebloggingEnabled(status.getRebloggingEnabled(), status.getVisibility());
        if (status.getSpoilerText() == null || status.getSpoilerText().isEmpty()) {
            hideSpoilerText();
        } else {
            setSpoilerText(status.getSpoilerText(), status.getStatusEmojis(), status.isExpanded(), listener);
        }
    }


}
