package com.tylersuehr.socialtextview;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.support.annotation.ColorInt;
import android.support.annotation.IntDef;
import android.support.v7.widget.AppCompatTextView;
import android.text.SpannableString;
import android.text.Spanned;
import android.util.AttributeSet;
import android.util.Patterns;
import android.view.View;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.CLASS;

/**
 * Copyright © 2017 <NAME>
 *
 * @author <NAME>
 * @version 1.0
 */
public class SocialTextView extends AppCompatTextView {
    /* Constants for social media flags */
    private static final int HASHTAG = 1;
    private static final int MENTION = 2;
    private static final int PHONE = 4;
    private static final int URL = 8;
    private static final int EMAIL = 16;

    private static Pattern patternHashtag;
    private static Pattern patternMention;

    /* Mutable properties */
    private boolean underlineEnabled;
    private int selectedColor;
    private int hashtagColor;
    private int mentionColor;
    private int phoneColor;
    private int emailColor;
    private int urlColor;

    private OnLinkClickListener linkClickListener;

    /* Stores enabled link modes */
    private int flags;


    public SocialTextView(Context c) {
        this(c, null);
    }

    public SocialTextView(Context c, AttributeSet attrs) {
        this(c, attrs, 0);
    }

    public SocialTextView(Context c, AttributeSet attrs, int def) {
        super(c, attrs, def);

        // Set the link movement method by default
        setMovementMethod(AccurateMovementMethod.getInstance());

        // Set XML attributes
        TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.SocialTextView);
        this.flags = a.getInt(R.styleable.SocialTextView_linkModes, -1);
        this.hashtagColor = a.getColor(R.styleable.SocialTextView_hashtagColor, Color.RED);
        this.mentionColor = a.getColor(R.styleable.SocialTextView_mentionColor, Color.RED);
        this.phoneColor = a.getColor(R.styleable.SocialTextView_phoneColor, Color.RED);
        this.emailColor = a.getColor(R.styleable.SocialTextView_emailColor, Color.RED);
        this.urlColor = a.getColor(R.styleable.SocialTextView_urlColor, Color.RED);
        this.selectedColor = a.getColor(R.styleable.SocialTextView_selectedColor, Color.LTGRAY);
        this.underlineEnabled = a.getBoolean(R.styleable.SocialTextView_underlineEnabled, false);
        if (a.hasValue(R.styleable.SocialTextView_android_text)) {
            setLinkText(a.getString(R.styleable.SocialTextView_android_text));
        }
        if (a.hasValue(R.styleable.SocialTextView_android_hint)) {
            setLinkHint(a.getString(R.styleable.SocialTextView_android_hint));
        }
        a.recycle();
    }

    /**
     * Overridden to ensure that highlighted text is always transparent.
     */
    @Override
    public void setHighlightColor(@ColorInt int color) {
        super.setHighlightColor(Color.TRANSPARENT);
    }

    public void setLinkText(String text) {
        setText(createSocialMediaSpan(text));
    }

    public void appendLinkText(String text) {
        append(createSocialMediaSpan(text));
    }

    public void setLinkHint(String textHint) {
        setHint(createSocialMediaSpan(textHint));
    }

    public void setOnLinkClickListener(OnLinkClickListener linkClickListener) {
        this.linkClickListener = linkClickListener;
    }

    public OnLinkClickListener getOnLinkClickListener() {
        return linkClickListener;
    }

    /**
     * Creates a spannable string containing the touchable spans of each link item
     * for each enabled link mode in the given text.
     *
     * @param text Text
     * @return {@link SpannableString}
     */
    private SpannableString createSocialMediaSpan(String text) {
        final Set<LinkItem> items = collectLinkItemsFromText(text);
        final SpannableString textSpan = new SpannableString(text);

        // Create a span for each link item
        for (final LinkItem item : items) {
            textSpan.setSpan(new TouchableSpan(getColorByMode(item.mode), selectedColor, underlineEnabled) {
                @Override
                public void onClick(View widget) {
                    // Trigger callback when span is touched
                    if (linkClickListener != null) {
                        linkClickListener.onLinkClicked(item.mode, item.matched);
                    }
                }
            }, item.start, item.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        }

        return textSpan;
    }

    /**
     * Checks which flags are enable so that the appropriate link items can be
     * collected from each respective mode.
     *
     * @param text Text
     * @return Set of {@link LinkItem}
     */
    private Set<LinkItem> collectLinkItemsFromText(String text) {
        final Set<LinkItem> items = new HashSet<>();

        // Check for hashtag links, if possible
        if ((flags & HASHTAG) == HASHTAG) {
            collectLinkItems(HASHTAG, items, getHashtagPattern().matcher(text));
        }

        // Check for mention links, if possible
        if ((flags & MENTION) == MENTION) {
            collectLinkItems(MENTION, items, getMentionPattern().matcher(text));
        }

        // Check for phone links, if possible
        if ((flags & PHONE) == PHONE) {
            collectLinkItems(PHONE, items, Patterns.PHONE.matcher(text));
        }

        // Check for url links, if possible
        if ((flags & URL) == URL) {
            collectLinkItems(URL, items, Patterns.WEB_URL.matcher(text));
        }

        // Check for email links, if possible
        if ((flags & EMAIL) == EMAIL) {
            collectLinkItems(EMAIL, items, Patterns.EMAIL_ADDRESS.matcher(text));
        }

        return items;
    }

    /**
     * Iterates through all the matches found by the given matcher and adds a new
     * {@link LinkItem} for each match into the given collection of link items.
     *
     * @param mode    {@link LinkOptions}
     * @param items   Collection of {@link LinkItem}
     * @param matcher {@link Matcher}
     */
    private void collectLinkItems(@LinkOptions int mode, Collection<LinkItem> items, Matcher matcher) {
        while (matcher.find()) {
            int matcherStart = matcher.start();
            String matchedText = matcher.group();

            if (matchedText.startsWith(" ")) {
                matcherStart += 1;
                matchedText = matchedText.substring(1);
            }
            items.add(new LinkItem(
                    matchedText,
                    matcherStart,
                    matcher.end(),
                    mode
            ));
        }
    }

    /**
     * Gets the corresponding color for a given mode.
     *
     * @param mode {@link #HASHTAG}, {@link #MENTION}, {@link #EMAIL}, {@link #PHONE}, {@link #URL}
     * @return Color
     */
    private int getColorByMode(@LinkOptions int mode) {
        switch (mode) {
            case HASHTAG:
                return hashtagColor;
            case MENTION:
                return mentionColor;
            case PHONE:
                return phoneColor;
            case URL:
                return urlColor;
            case EMAIL:
                return emailColor;

            default:
                throw new IllegalArgumentException("Invalid mode!");
        }
    }

    /**
     * Lazy loads the 'hashtag' regex pattern.
     *
     * @return {@link Pattern}
     */
    private static Pattern getHashtagPattern() {
        if (patternHashtag == null) {
            patternHashtag = Pattern.compile("(?:^|\\s|$)#[\\p{L}0-9_]*");
        }
        return patternHashtag;
    }

    /**
     * Lazy loads the 'mention' regex pattern.
     *
     * @return {@link Pattern}
     */
    private static Pattern getMentionPattern() {
        if (patternMention == null) {
            patternMention = Pattern.compile("(?:^|\\s|$|[.])@[\\p{L}0-9_]*");
        }
        return patternMention;
    }


    /**
     * Data structure to store information about a link.
     */
    private final class LinkItem {
        private final String matched;
        private final int start;
        private final int end;
        private final int mode;

        private LinkItem(String matched, int start, int end, int mode) {
            this.matched = matched;
            this.start = start;
            this.end = end;
            this.mode = mode;
        }
    }


    @Retention(RetentionPolicy.SOURCE)
    @Target({PARAMETER, METHOD, LOCAL_VARIABLE, FIELD})
    @IntDef(value = {HASHTAG, MENTION, PHONE, EMAIL, URL})
    public @interface LinkOptions {
    }


    /**
     * Listener for link clicks in text view.
     */
    public interface OnLinkClickListener {
        void onLinkClicked(int linkType, String matchedText);
    }
}