/*******************************************************************************
* This file is part of RedReader.
*
* RedReader is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* RedReader is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with RedReader. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
package org.quantumbadger.redreader.reddit.prepared.markdown;
import android.graphics.Typeface;
import android.net.Uri;
import android.support.v7.app.AppCompatActivity;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.style.ClickableSpan;
import android.text.style.RelativeSizeSpan;
import android.text.style.StrikethroughSpan;
import android.text.style.StyleSpan;
import android.text.style.SuperscriptSpan;
import android.text.style.TypefaceSpan;
import android.view.View;
import org.quantumbadger.redreader.common.LinkHandler;
import org.quantumbadger.redreader.views.LinkifiedTextView;
import java.util.ArrayList;
import java.util.List;
// TODO number links
public final class MarkdownParagraph {
final CharArrSubstring raw;
final MarkdownParagraph parent;
final MarkdownParser.MarkdownParagraphType type;
final int[] tokens;
final int level, number;
final Spanned spanned;
final List<Link> links;
public class Link {
final String title;
final String subtitle;
private final String url;
public Link(String title, String subtitle, String url) {
this.title = title;
this.subtitle = subtitle;
this.url = url;
}
public void onClicked(AppCompatActivity activity) {
LinkHandler.onLinkClicked(activity, url, false);
}
public void onLongClicked(AppCompatActivity activity) {
LinkHandler.onLinkLongClicked(activity, url);
}
}
public MarkdownParagraph(CharArrSubstring raw, MarkdownParagraph parent, MarkdownParser.MarkdownParagraphType type,
int[] tokens, int level, int number) {
this.raw = raw;
this.parent = parent;
this.type = type;
this.tokens = tokens;
this.level = level;
this.number = number;
links = new ArrayList<>();
spanned = internalGenerateSpanned();
if(tokens == null && raw != null) raw.replaceUnicodeSpaces();
}
private Spanned internalGenerateSpanned() {
if(type == MarkdownParser.MarkdownParagraphType.CODE || type == MarkdownParser.MarkdownParagraphType.HLINE) {
return null;
}
if(tokens == null) {
return new SpannableString(raw.toString());
}
final SpannableStringBuilder builder = new SpannableStringBuilder();
int boldStart = -1, italicStart = -1, strikeStart = -1, linkStart = -1, caretStart = -1,
parentOpenCount = 0, parentCloseCount = 0;
for(int i = 0; i < tokens.length; i++) {
final int token = tokens[i];
switch(token) {
case MarkdownTokenizer.TOKEN_ASTERISK:
case MarkdownTokenizer.TOKEN_UNDERSCORE:
if(italicStart < 0) {
italicStart = builder.length();
} else {
builder.setSpan(new StyleSpan(Typeface.ITALIC), italicStart, builder.length(),
Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
italicStart = -1;
}
break;
case MarkdownTokenizer.TOKEN_ASTERISK_DOUBLE:
case MarkdownTokenizer.TOKEN_UNDERSCORE_DOUBLE:
if(boldStart < 0) {
boldStart = builder.length();
} else {
builder.setSpan(new StyleSpan(Typeface.BOLD), boldStart, builder.length(),
Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
boldStart = -1;
}
break;
case MarkdownTokenizer.TOKEN_TILDE_DOUBLE:
if(strikeStart == -1) {
strikeStart = builder.length();
} else {
builder.setSpan(new StrikethroughSpan(), strikeStart, builder.length(),
Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
strikeStart = -1;
}
break;
case MarkdownTokenizer.TOKEN_GRAVE:
final int codeStart = builder.length();
while(tokens[++i] != MarkdownTokenizer.TOKEN_GRAVE) {
builder.append((char)tokens[i]);
}
builder.setSpan(new TypefaceSpan("monospace"), codeStart, builder.length(),
Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
break;
case MarkdownTokenizer.TOKEN_BRACKET_SQUARE_OPEN:
linkStart = builder.length();
break;
case MarkdownTokenizer.TOKEN_BRACKET_SQUARE_CLOSE:
final int urlStart = indexOf(tokens, MarkdownTokenizer.TOKEN_PAREN_OPEN, i + 1);
final int urlEnd = indexOf(tokens, MarkdownTokenizer.TOKEN_PAREN_CLOSE, urlStart + 1);
final StringBuilder urlBuilder = new StringBuilder(urlEnd - urlStart);
for(int j = urlStart + 1; j < urlEnd; j++) {
urlBuilder.append((char)tokens[j]);
}
final String linkText = String.valueOf(builder.subSequence(linkStart, builder.length()));
final String url = urlBuilder.toString();
if(url.startsWith("/spoiler")) {
builder.delete(linkStart, builder.length());
builder.append("[Spoiler]");
final Uri.Builder spoilerUriBuilder = Uri.parse("rr://msg/").buildUpon();
spoilerUriBuilder.appendQueryParameter("title", "Spoiler");
spoilerUriBuilder.appendQueryParameter("message", linkText);
links.add(new Link("Spoiler", null, spoilerUriBuilder.toString()));
} else if(url.length() > 3 && url.charAt(2) == ' '
&& (url.charAt(0) == '#' || url.charAt(0) == '/')) {
final String subtitle;
switch(url.charAt(1)) {
case 'b':
subtitle = "Spoiler: Book";
break;
case 'g':
subtitle = "Spoiler: Speculation";
break;
case 's':
default:
subtitle = "Spoiler";
break;
}
final Uri.Builder spoilerUriBuilder = Uri.parse("rr://msg/").buildUpon();
spoilerUriBuilder.appendQueryParameter("title", subtitle);
spoilerUriBuilder.appendQueryParameter("message", url.substring(3));
links.add(new Link(linkText, subtitle, spoilerUriBuilder.toString()));
} else {
links.add(new Link(linkText, url, url));
}
// TODO
//builder.insert(linkStart, "[NUMBER HERE]");
final ClickableSpan span = new ClickableSpan() {
@Override
public void onClick(final View widget) {
final AppCompatActivity activity = ((LinkifiedTextView)widget).getActivity();
LinkHandler.onLinkClicked(activity, url);
}
};
builder.setSpan(span, linkStart, builder.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
i = urlEnd;
break;
case MarkdownTokenizer.TOKEN_CARET:
if(caretStart < 0) {
caretStart = builder.length();
} else {
builder.append(' ');
}
break;
case ' ':
builder.append(' ');
if(caretStart >= 0 && parentOpenCount == parentCloseCount) {
builder.setSpan(new SuperscriptSpan(), caretStart, builder.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
builder.setSpan(new RelativeSizeSpan(0.6f), caretStart, builder.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
caretStart = -1;
}
break;
case '(':
if (caretStart >= 0){
parentOpenCount++;
if (caretStart != builder.length()){
builder.append('(');
}
} else {
parentOpenCount = 0;
builder.append('(');
}
break;
case ')':
if (caretStart >= 0){
parentCloseCount++;
if (parentOpenCount != parentCloseCount){
builder.append(')');
} else {
builder.setSpan(new SuperscriptSpan(), caretStart, builder.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
builder.setSpan(new RelativeSizeSpan(0.6f), caretStart, builder.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
caretStart = -1;
}
} else {
parentCloseCount = 0;
builder.append(')');
}
break;
default:
builder.append((char)token);
break;
}
}
if(caretStart >= 0) {
builder.setSpan(new SuperscriptSpan(), caretStart, builder.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
builder.setSpan(new RelativeSizeSpan(0.6f), caretStart, builder.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
}
if(type == MarkdownParser.MarkdownParagraphType.HEADER) {
while(builder.length() > 0 && builder.charAt(builder.length() - 1) == '#') {
builder.delete(builder.length() - 1, builder.length());
}
}
return builder;
}
private static int indexOf(final int[] haystack, final int needle, final int startPos) {
for(int i = startPos; i < haystack.length; i++) {
if(haystack[i] == needle) return i;
}
return -1;
}
public boolean isEmpty() {
if(type == MarkdownParser.MarkdownParagraphType.HLINE) return false;
if(type == MarkdownParser.MarkdownParagraphType.EMPTY) return true;
if(tokens == null) {
return raw.countSpacesAtStart() == raw.length;
} else {
for(final int token : tokens) {
if(!MarkdownTokenizer.isUnicodeWhitespace(token)) return false;
}
return true;
}
}
}