package com.zfdang.zsmth_android.newsmth;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.media.ExifInterface;
import android.os.Environment;
import android.text.TextUtils;
import android.util.Log;

import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import com.zfdang.SMTHApplication;
import com.zfdang.zsmth_android.Settings;
import com.zfdang.zsmth_android.WebviewCookieHandler;
import com.zfdang.zsmth_android.helpers.MakeList;
import com.zfdang.zsmth_android.helpers.StringUtils;
import com.zfdang.zsmth_android.models.Board;
import com.zfdang.zsmth_android.models.BoardListContent;
import com.zfdang.zsmth_android.models.BoardSection;
import com.zfdang.zsmth_android.models.Mail;
import com.zfdang.zsmth_android.models.MailListContent;
import com.zfdang.zsmth_android.models.Post;
import com.zfdang.zsmth_android.models.Topic;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import io.reactivex.Observable;
import io.reactivex.annotations.NonNull;
import io.reactivex.functions.Function;
import io.reactivex.functions.Predicate;
import io.reactivex.schedulers.Schedulers;
import okhttp3.Cache;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;
import retrofit2.converter.scalars.ScalarsConverterFactory;

/**
 * Created by zfdang on 2016-3-16.
 */
public class SMTHHelper {

  static final private String TAG = "SMTHHelper";
  public static final String USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.101 Safari/537.36";

  public OkHttpClient mHttpClient;

  // WWW service of SMTH, but actually most of services are actually from nForum
  static private final String SMTH_WWW_URL = "https://www.newsmth.net/";
  static private final String SMTH_IMAGE_PREFIX_CDN = "https://static.newsmth.net";
  static private final String SMTH_IMAGE_PREFIX_DIRECT = "https://www.newsmth.net";
  // Mobile service of SMTH; this is used for webchat sharing & open in browser
  static public final String SMTH_MOBILE_URL = "https://m.newsmth.net";

  public SMTHWWWService wService;
  static private final String SMTH_WWW_ENCODING = "GB2312";

  // All boards cache file
  public static int BOARD_TYPE_FAVORITE = 1;
  public static int BOARD_TYPE_ALL = 2;
  static private final String ALL_BOARD_CACHE_FILE = "SMTH_ALLBD_CACHE_KRYO";
  static private final String FAVORITE_BOARD_CACHE_PREFIX = "SMTH_FAVBD_CACHE_KYRO";

  // singleton
  private static SMTHHelper instance = null;

  public static synchronized SMTHHelper getInstance() {
    if (instance == null) {
      instance = new SMTHHelper();
    }
    return instance;
  }

  // response from WWW is GB2312, we need to conver it to UTF-8
  // http://www.newsmth.net/mainpage.html
  public static String DecodeResponseFromWWW(byte[] bytes) {
    String result = null;
    try {
      result = new String(bytes, SMTH_WWW_ENCODING);
    } catch (UnsupportedEncodingException e) {
      Log.d("DecodeResponseFromWWW", e.toString());
    }
    return result;
  }

  // https://stackoverflow.com/questions/25509296/trusting-all-certificates-with-okhttp
  private static OkHttpClient getUnsafeOkHttpClient() {
    try {
      // Create a trust manager that does not validate certificate chains
      final TrustManager[] trustAllCerts = new TrustManager[] {
          new X509TrustManager() {
            @Override
            public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType)
                throws CertificateException {
            }

            @Override
            public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType)
                throws CertificateException {
            }

            @Override
            public java.security.cert.X509Certificate[] getAcceptedIssuers() {
              return new java.security.cert.X509Certificate[] {};
            }
          }
      };

      // Install the all-trusting trust manager
      final SSLContext sslContext = SSLContext.getInstance("SSL");
      sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
      // Create an ssl socket factory with our all-trusting manager
      final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();

      OkHttpClient.Builder builder = new OkHttpClient.Builder();
      builder.sslSocketFactory(sslSocketFactory, (X509TrustManager) trustAllCerts[0]);
      builder.hostnameVerifier(new HostnameVerifier() {
        @Override
        public boolean verify(String hostname, SSLSession session) {
          return true;
        }
      });

      OkHttpClient okHttpClient = builder.build();
      return okHttpClient;
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

  // protected constructor, can only be called by getInstance
  protected SMTHHelper() {

    // set your desired log level
    HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
    logging.setLevel(HttpLoggingInterceptor.Level.NONE);

    // 设置缓存路径
    File httpCacheDirectory = new File(SMTHApplication.getAppContext().getCacheDir(), "Responses");
    int cacheSize = 250 * 1024 * 1024; // 250 MiB
    Cache cache = new Cache(httpCacheDirectory, cacheSize);

    mHttpClient = getUnsafeOkHttpClient().newBuilder().addInterceptor(logging).addInterceptor(new Interceptor() {
      @Override
      public Response intercept(Chain chain) throws IOException {
        Request request = chain.request().newBuilder().header("User-Agent", USER_AGENT).build();
        return chain.proceed(request);
      }
    }).addNetworkInterceptor(new Interceptor() {
      // for error response, do not cache its content
      @Override
      public Response intercept(Chain chain) throws IOException {
        Response originalResponse = chain.proceed(chain.request());
        // if (originalResponse.isSuccessful() &&
        // originalResponse.body().toString().contains("您未登录,请登录后继续操作")) {
        if (originalResponse.isSuccessful() && originalResponse.body().contentLength() > 4096) {
          // only cache large response
          // the size of response with "您未登录,请登录后继续操作" is 2033
          return originalResponse;
        } else {
          return originalResponse.newBuilder().header("Cache-Control", "no-cache").build();
        }
      }
    }).cookieJar(new WebviewCookieHandler()) // https://gist.github.com/scitbiz/8cb6d8484bb20e47d241cc8e117fa705
        .cache(cache).readTimeout(15, TimeUnit.SECONDS).connectTimeout(10, TimeUnit.SECONDS).build();

    // mRetrofit = new Retrofit.Builder()
    // .baseUrl(SMTH_MOBILE_URL)
    // .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
    // .addConverterFactory(ScalarsConverterFactory.create())
    // .client(mHttpClient)
    // .build();

    Retrofit wRetrofit = new Retrofit.Builder().baseUrl(SMTH_WWW_URL)
        .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
        .addConverterFactory(ScalarsConverterFactory.create())
        .addConverterFactory(GsonConverterFactory.create())
        .client(mHttpClient)
        .build();
    wService = wRetrofit.create(SMTHWWWService.class);
  }

  // query active user status
  // since wService.queryActiveUserStatus does not return correct faceurl, try to
  // query user information again
  public static Observable<UserStatus> queryActiveUserStatus() {
    final SMTHHelper helper = SMTHHelper.getInstance();
    return helper.wService.queryActiveUserStatus()
        .subscribeOn(Schedulers.io())
        .observeOn(Schedulers.io())
        .map(new Function<UserStatus, UserStatus>() {
          @Override
          public UserStatus apply(@NonNull UserStatus userStatus) throws Exception {
            String userid = userStatus.getId();
            if (userid != null && !userid.equals("guest")) {
              // get correct faceURL
              UserInfo user = helper.wService.queryUserInformation(userid).blockingFirst();
              if (user != null) {
                userStatus.setFace_url(user.getFace_url());
              }
            }
            return userStatus;
          }
        });
  }

  private static Bitmap loadResizedBitmapFromFile(final String filename, final int targetWidth, final int targetHeight,
      boolean bCompress) {
    try {
      Bitmap bitmap;

      // o.inPurgeable = true;
      bitmap = BitmapFactory.decodeFile(filename, null);
      Log.d(TAG, "loadResizedBitmapFromFile: "
          + String.format("Pre-sized bitmap size: (%dx%d).", bitmap.getWidth(), bitmap.getHeight()));

      if (bCompress) {
        // create bitmap which matches exactly within the target size
        // calc exact destination size
        // http://developer.android.com/reference/android/graphics/Matrix.ScaleToFit.html
        Matrix m = new Matrix();
        RectF inRect = new RectF(0, 0, bitmap.getWidth(), bitmap.getHeight());
        RectF outRect = new RectF(0, 0, targetWidth, targetHeight);
        m.setRectToRect(inRect, outRect, Matrix.ScaleToFit.CENTER);
        float[] values = new float[9];
        m.getValues(values);

        Log.d(TAG, "loadResizedBitmapFromFile: " + String.format("Zoom: (%fx%f).", values[0], values[4]));
        if (values[0] < 1.0 || values[4] < 1.0) {
          bitmap = Bitmap.createScaledBitmap(bitmap, (int) (bitmap.getWidth() * values[0]),
              (int) (bitmap.getHeight() * values[4]), true);
          Log.d(TAG, "loadResizedBitmapFromFile: " + "reduce size");
        }
      }

      Log.d(TAG, "loadResizedBitmapFromFile: "
          + String.format("Final bitmap size: (%dx%d).", bitmap.getWidth(), bitmap.getHeight()));
      return bitmap;
    } catch (final OutOfMemoryError e) {
      return null;
    }
  }

  /**
   * Returns the contents of the file in a byte array
   *
   * @param file File this method should read
   * @return byte[] Returns a byte[] array of the contents of the file
   */
  private static byte[] getBytesFromFile(File file) {
    byte[] bytes = null;
    try {
      InputStream is = new FileInputStream(file);

      // Get the size of the file
      long length = file.length();
      if (length > Integer.MAX_VALUE) {
        Log.e(TAG, "getBytesFromFile: " + "File is too large to process");
        return null;
      }

      // Create the byte array to hold the data
      bytes = new byte[(int) length];

      // Read in the bytes
      int offset = 0;
      int numRead;
      while ((offset < bytes.length) && ((numRead = is.read(bytes, offset, bytes.length - offset)) >= 0)) {
        offset += numRead;
      }

      // Ensure all the bytes have been read in
      if (offset < bytes.length) {
        throw new IOException("Could not completely read file " + file.getName());
      }
      is.close();
    } catch (IOException e) {
      Log.e(TAG, "getBytesFromFile: " + Log.getStackTraceString(e));
    }

    return bytes;
  }

  public static String saveBitmapToFile(final Bitmap bitmap, final String filename) {
    try {
      // if (TextUtils.equals(Environment.getExternalStorageState(),
      // Environment.MEDIA_MOUNTED)) {
      String path = SMTHApplication.getAppContext().getCacheDir().getPath() + "/uploaded";
      File dir = new File(path);
      if (!dir.exists()) {
        dir.mkdirs();
      }

      File outFile = new File(dir, new File(filename).getName());

      FileOutputStream fstream = new FileOutputStream(outFile);
      bitmap.compress(Bitmap.CompressFormat.JPEG, 85, fstream);
      fstream.flush();
      fstream.close();

      Log.d(TAG, "saveBitmapToFile: " + outFile.getAbsolutePath());
      return outFile.getAbsolutePath();
      // }
    } catch (Exception e) {
      Log.e(TAG, "saveBitmapToFile: ", e);
    }
    return null;
  }

  public static void copyExif(String oldPath, String newPath) {
    try {
      ExifInterface oldExif = new ExifInterface(oldPath);
      String[] attributes = new String[] {
          ExifInterface.TAG_APERTURE, ExifInterface.TAG_DATETIME, ExifInterface.TAG_DATETIME_DIGITIZED,
          ExifInterface.TAG_EXPOSURE_TIME,
          ExifInterface.TAG_FLASH, ExifInterface.TAG_FOCAL_LENGTH, ExifInterface.TAG_GPS_ALTITUDE,
          ExifInterface.TAG_GPS_ALTITUDE_REF,
          ExifInterface.TAG_GPS_DATESTAMP, ExifInterface.TAG_GPS_LATITUDE, ExifInterface.TAG_GPS_LATITUDE_REF,
          ExifInterface.TAG_GPS_LONGITUDE, ExifInterface.TAG_GPS_LONGITUDE_REF, ExifInterface.TAG_GPS_PROCESSING_METHOD,
          ExifInterface.TAG_GPS_TIMESTAMP, ExifInterface.TAG_IMAGE_LENGTH, ExifInterface.TAG_IMAGE_WIDTH,
          ExifInterface.TAG_ISO,
          ExifInterface.TAG_MAKE, ExifInterface.TAG_MODEL, ExifInterface.TAG_ORIENTATION, ExifInterface.TAG_SUBSEC_TIME,
          ExifInterface.TAG_SUBSEC_TIME_DIG, ExifInterface.TAG_SUBSEC_TIME_ORIG, ExifInterface.TAG_WHITE_BALANCE
      };

      ExifInterface newExif = new ExifInterface(newPath);
      for (String attribute : attributes) {
        String value = oldExif.getAttribute(attribute);
        if (value != null)
          newExif.setAttribute(attribute, value);
      }
      newExif.saveAttributes();
    } catch (IOException e) {
      Log.e(TAG, "copyExif: ", e);
    }
  }

  public static byte[] getBitmapBytesWithResize(final String filename, boolean bCompress) {
    // final SMTHHelper helper = SMTHHelper.getInstance();
    Log.d(TAG, "getBitmapBytesWithResize: " + filename);

    if (filename.toLowerCase().endsWith(".gif")) {
      // gif, don't resize
      File infile = new File(filename);
      return getBytesFromFile(infile);
    } else {
      // static image, resize it
      Bitmap theBitmap = loadResizedBitmapFromFile(filename, 1200, 1200, bCompress);

      // save bitmap to temp file
      String newfilename = saveBitmapToFile(theBitmap, filename);

      // copy exif information from old file to new file
      copyExif(filename, newfilename);

      // read data
      File infile = new File(newfilename);
      return getBytesFromFile(infile);
    }
  }

  public static Observable<AjaxResponse> publishPost(String boardEngName, String subject, String content,
      String signature,
      String replyPostID) {
    SMTHHelper helper = SMTHHelper.getInstance();
    return helper.wService.publishPost(boardEngName, subject, content, signature, replyPostID)
        .subscribeOn(Schedulers.io())
        .observeOn(Schedulers.io());
  }

  public static Observable<AjaxResponse> editPost(String boardEngName, String postID, String subject, String content) {
    SMTHHelper helper = SMTHHelper.getInstance();
    return helper.wService.editPost(boardEngName, postID, subject, content).subscribeOn(Schedulers.io())
        .observeOn(Schedulers.io());
  }

  public static Observable<AjaxResponse> sendMail(String userid, String title, String content) {
    SMTHHelper helper = SMTHHelper.getInstance();
    return helper.wService.sendMail("NULL", userid, title, content, "0", "on", "").subscribeOn(Schedulers.io())
        .observeOn(Schedulers.io());
  }

  public static Post ParseMailContentFromWWW(String content) {
    Document doc = Jsoup.parse(content);

    Post post = new Post();
    post.parsePostContent(doc, false);
    return post;
  }

  public static List<Post> ParsePostListFromWWW(String content, Topic topic) {
    // final String TAG = "ParsePostListFromWWW";
    List<Post> results = new ArrayList<>();

    Document doc = Jsoup.parse(content);

    // find total posts for this topic, and total pages
    Elements lis = doc.select("li.page-pre");
    if (lis.size() > 0) {
      Element li = lis.first();
      // 贴数:152 分页:
      // Log.d(TAG, li.text());

      Pattern pattern = Pattern.compile("(\\d+)", Pattern.DOTALL);
      Matcher matcher = pattern.matcher(li.text());
      if (matcher.find()) {
        String totalPostString = matcher.group(0);
        topic.setTotalPostNoFromString(totalPostString);
        // Log.d(TAG, totalPostString);
      }
    }

    // find all posts
    Elements tables = doc.select("table.article");
    for (Element table : tables) {
      Post post = new Post();

      // find author for this post
      // <span class="a-u-name"><a href="/nForum/user/query/CZB">CZB</a></span>
      Elements authors = table.select("span.a-u-name");
      if (authors.size() > 0) {
        Element author = authors.get(0);
        String authorName = author.text();
        post.setAuthor(authorName);
      }

      // find post id for this post
      // <samp class="ico-pos-reply"></samp><a
      // href="/nForum/article/WorkLife/post/1113865" class="a-post">回复</a></li>
      Elements links = table.select("li a.a-post");
      if (links.size() > 0) {
        Element link = links.first();
        String postID = StringUtils.getLastStringSegment(link.attr("href"));
        post.setPostID(postID);
      }

      // find post position
      // <span class="a-pos">第1楼</span>
      Elements positions = table.select("span.a-pos");
      if (positions.size() > 0) {
        Element position = positions.first();
        post.setPosition(position.text());
      }

      // find & parse post content
      Elements contents = table.select("td.a-content");
      if (contents.size() == 1) {
        post.parsePostContent(contents.get(0), true);
      }
      results.add(post);
    }

    if (results.size() == 0) {
      // there might be some problems with the response
      // <div class="error">
      // <h5>产生错误的可能原因：</h5>
      // <ul>
      // <li>
      // <samp class="ico-pos-dot"></samp>指定的文章不存在或链接错误</li>
      // </ul>
      // </div>
      Elements divs = doc.select("div.error");
      if (divs.size() > 0) {
        Element div = divs.first();

        topic.setTotalPostNoFromString("1");

        Post post = new Post();
        post.setAuthor("错误信息");
        post.setRawContent(div.toString());
        results.add(post);
      }
    }

    return results;
  }

  public static Topic ParseTopicFromElement(Element ele, String type) {
    if ("top10".equals(type) || "sectionhot".equals(type)) {
      // two <A herf> nodes

      // normal hot topic
      // <li><a href="/nForum/article/OurEstate/1685281"
      // title="lj让我走垫资(114)">lj让我走垫资&nbsp;(114)</a></li>

      // special hot topic -- 近期热帖: 1. board信息，没有reply_count
      // <li>
      // <div><a href="/nForum/board/Picture"><span class="board">[贴图]</span></a><a
      // href="/nForum/article/ShiDa/59833" title=" 南都副总编及编辑被处分开除"><span
      // class="title"> 南都副总编及编辑被处分开除</span></a></div>
      // </li>

      Elements as = ele.select("a[href]");
      if (as.size() == 2) {
        Element a1 = as.get(0);
        Element a2 = as.get(1);

        String boardChsName = a1.text().replace("]", "").replace("[", "");
        String boardEngName = StringUtils.getLastStringSegment(a1.attr("href"));

        String title = a2.attr("title");
        String topicID = StringUtils.getLastStringSegment(a2.attr("href"));

        Topic topic = new Topic();
        String reply_count = StringUtils.getReplyCountInParentheses(title);
        if (reply_count.length() > 0) {
          title = title.substring(0, title.length() - reply_count.length() - 2);
          topic.setTotalPostNoFromString(reply_count);
        }

        topic.setBoardEngName(boardEngName);
        topic.setBoardChsName(boardChsName);
        topic.setTopicID(topicID);
        topic.setTitle(title);

        // Log.d(TAG, topic.toString());
        return topic;
      }
    } else if ("pictures".equals(type)) {
      // three <A herf> nodes

      // <li>
      // <a href="/nForum/article/SchoolEstate/430675"><img
      // src="http://images.newsmth.net/nForum/img/hotpic/SchoolEstate_430675.jpg"
      // title="点击查看原帖" /></a>
      // <br /><a class="board" href="/nForum/board/SchoolEstate">[学区房]</a>
      // <br /><a class="title" href="/nForum/article/SchoolEstate/430675"
      // title="这个小学排名还算靠谱吧， AO爸爸排的。。。">这个小学排名还算靠谱吧， AO爸爸排的。。。</a>
      // </li>
      Elements as = ele.select("a[href]");
      if (as.size() == 3) {
        Element a1 = as.get(1);
        Element a2 = as.get(2);

        String boardChsName = a1.text().replace("]", "").replace("[", "");
        String boardEngName = StringUtils.getLastStringSegment(a1.attr("href"));

        String title = a2.attr("title");
        String topicID = StringUtils.getLastStringSegment(a2.attr("href"));

        Topic topic = new Topic();
        topic.setBoardEngName(boardEngName);
        topic.setBoardChsName(boardChsName);
        topic.setTopicID(topicID);
        topic.setTitle(title);

        // Log.d(TAG, topic.toString());
        return topic;
      }
    }
    return null;
  }

  // parse guidance page, to find all hot topics
  public static List<Topic> ParseHotTopicsFromWWW(String content) {
    List<Topic> results = new ArrayList<>();
    if (content == null || content.length() == 0) {
      return results;
    }

    Topic topic;
    Document doc = Jsoup.parse(content);

    // find top10
    // <div id="top10">
    Elements top10s = doc.select("div#top10");
    if (top10s.size() == 1) {
      // add separator
      topic = new Topic("本日十大热门话题");
      results.add(topic);

      // parse hot hopic
      Element top10 = top10s.first();
      Elements lis = top10.getElementsByTag("li");

      for (Element li : lis) {
        topic = ParseTopicFromElement(li, "top10");
        if (topic != null) {
          // Log.d(TAG, topic.toString());
          results.add(topic);
        }
      }
    }

    // find hotspot
    // <div id="hotspot" class="block">
    // skip this part, it's tedious
    // Elements hotspots = doc.select("div#hotspot div.topics");
    // if(hotspots.size() == 1) {
    // // add separator
    // topic = new Topic("近期热帖");
    // results.add(topic);
    //
    // // parse hot hopic
    // Element hotspot = hotspots.first();
    // Elements lis = hotspot.getElementsByTag("li");
    //
    // for(Element li: lis) {
    // topic = ParseTopicFromElement(li, "hotspot");
    // if(topic != null) {
    //// Log.d(TAG, topic.toString());
    // results.add(topic);
    // }
    // }
    // }

    // find hot picture
    // <div id="pictures" class="block">
    Elements pictures = doc.select("div#pictures");
    for (Element section : pictures) {
      // add separator
      Elements sectionNames = section.getElementsByTag("h3");
      if (sectionNames.size() == 1) {
        Element sectionName = sectionNames.first();
        topic = new Topic(sectionName.text());
        results.add(topic);
      }

      Elements lis = section.select("div li");
      for (Element li : lis) {
        // Log.d(TAG, li.toString());
        topic = ParseTopicFromElement(li, "pictures");
        if (topic != null) {
          // Log.d(TAG, topic.toString());
          results.add(topic);
        }
      }
    }

    // find hot topics from each section
    // <div id="hotspot" class="block">
    Elements sections = doc.select("div.b_section");
    for (Element section : sections) {
      // add separator
      Elements sectionNames = section.getElementsByTag("h3");
      if (sectionNames.size() == 1) {
        Element sectionName = sectionNames.first();
        String name = sectionName.text();
        if (name.equals("系统与祝福")) {
          continue;
        }
        topic = new Topic(name);
        results.add(topic);
      }

      Elements lis = section.select("div.topics li");
      for (Element li : lis) {
        // Log.d(TAG, li.toString());
        topic = ParseTopicFromElement(li, "sectionhot");
        if (topic != null) {
          // Log.d(TAG, topic.toString());
          results.add(topic);
        }
      }
    }

    return results;
  }

  // parse board topics from WWW
  public static List<Topic> ParseBoardTopicsFromWWW(String content) {
    List<Topic> results = new ArrayList<>();
    if (content == null) {
      return results;
    }

    Document doc = Jsoup.parse(content);

    // <li class="page-select"><a title="当前页">2</a></li>
    String currentPage = null;
    Elements lis = doc.select("li.page-select");
    if (lis.size() > 0) {
      Element li = lis.first();
      currentPage = li.text();
      // Log.d(TAG, "ParseBoardTopicsFromWWW: " + currentPage);
    }

    // <tr class="top">
    // <td class="title_8">
    // <a target="_blank" href="/nForum/article/FamilyLife/1757972219"
    // title="在新窗口打开此主题">
    // <samp class="tag ico-pos-article-light"></samp>
    // </a>
    // </td>
    // <td class="title_9"><a href="/nForum/article/FamilyLife/1757972219">2岁女孩找妈妈
    // 其父母终于被找到</a>
    // <samp class="tag-att ico-pos-article-attach"></samp><span
    // class="threads-tab">[<a
    // href="/nForum/article/FamilyLife/1757972219?p=2">2</a>]</span></td>
    // <td class="title_10">2016-05-05</td>
    // <td class="title_12">|&ensp;<a href="/nForum/user/query/Muscle"
    // class="c63f">Muscle</a></td>
    // <td class="title_11 middle"></td>
    // <td class="title_11 middle"></td>
    // <td class="title_11 middle">14</td>
    // <td class="title_10"><a href="/nForum/article/FamilyLife/1757972219?p=2#a14"
    // title="跳转至最后回复">21:38:58&emsp;</a></td>
    // <td class="title_12">|&ensp;<a href="/nForum/user/query/kxxx"
    // class="c09f">kxxx</a></td>
    // </tr>

    // get all trs
    Elements trs = doc.select("table.board-list tbody tr");
    for (Element tr : trs) {
      // Log.d(TAG, "ParseBoardTopicsFromWWW: " + tr.toString());
      Topic topic = new Topic();

      String trClass = tr.attr("class");
      if (TextUtils.equals(trClass, "top")) {
        // is sticky
        topic.isSticky = true;
      }

      Elements tds = tr.getElementsByTag("td");
      for (Element td : tds) {
        String tdClass = td.attr("class");
        // Log.d(TAG, "ParseBoardTopicsFromWWW: td.class = " + tdClass);

        if (TextUtils.equals(tdClass, "title_9")) {
          // <td class="title_9"><a href="/nForum/article/FamilyLife/1757972219">2岁女孩找妈妈
          // 其父母终于被找到</a>
          Elements as = td.getElementsByTag("a");
          if (as.size() > 0) {
            Element a = as.first();
            topic.setTitle(a.text());

            String topicURL = a.attr("href");
            topic.setTopicID(StringUtils.getLastStringSegment(topicURL));
          }
          // <samp class="tag-att ico-pos-article-attach"></samp>
          // find attachment flag
          Elements samps = td.getElementsByTag("samp");
          if (samps.size() > 0) {
            topic.setHasAttach(true);
          }
        } else if (TextUtils.equals(tdClass, "title_10")) {
          // <td class="title_10">2016-05-05</td>
          // <td class="title_10"><a href="/nForum/article/FamilyLife/1757972219?p=2#a14"
          // title="跳转至最后回复">21:38:58&emsp;</a></td>
          String publishDate = topic.getPublishDate();
          if (publishDate == null || publishDate.length() == 0) {
            topic.setPublishDate(td.text());
          } else {
            topic.setReplyDate(td.text());
          }
        } else if (TextUtils.equals(tdClass, "title_12")) {
          // <td class="title_12">|&ensp;<a href="/nForum/user/query/Muscle"
          // class="c63f">Muscle</a></td>
          // <td class="title_12">|&ensp;<a href="/nForum/user/query/kxxx"
          // class="c09f">kxxx</a></td>
          String author = topic.getAuthor();
          String value = td.text().replace("|", "").trim();
          if (author == null || author.length() == 0) {
            topic.setAuthor(value);
          } else {
            topic.setReplier(value);
          }
        } else if (TextUtils.equals(tdClass, "title_11 middle")) {
          // <td class="title_11 middle">评分</td>
          // <td class="title_11 middle">like</td>
          // <td class="title_11 middle">回复: 14</td>
          String score = topic.getScore();
          String likes = topic.getLikes();
          String value = td.text();
          if (score == null) {
            topic.setScore(value);
          } else if (likes == null) {
            topic.setLikes(value);
          } else {
            topic.setReplyCounts(value);
          }
        }
      }

      // Log.d("ParseBoardTopics", topic.toString());
      if (topic.getTitle() != null && topic.getTitle().length() > 0) {
        results.add(topic);
      }
    }

    return results;
  }

  // parse topics from nForum search results
  // <tr>
  // <td class="title_8">1.</td>
  // <td class="title_14">
  // <a target="_blank" href="/nForum/article/PocketLife/2217534"
  // title="在新窗口打开此主题">
  // <samp class="tag ico-pos-article-normal"></samp>
  // </a>
  // </td>
  // <td class="title_9"><a
  // href="/nForum/article/PocketLife/2217534">更改了一下zSMTH&#45;Android的颜色搭配</a></td>
  // <td class="title_10">08:47:21&emsp;</td>
  // <td class="title_12">|&ensp;<a href="/nForum/user/query/mozilla"
  // class="c63f">mozilla</a></td>
  // <td class="title_11 middle">9</td>
  // <td class="title_10"><a href="/nForum/article/PocketLife/2217534?p=1#a9"
  // title="跳转至最后回复">11:04:03&emsp;</a></td>
  // <td class="title_12">|&ensp;<a href="/nForum/user/query/mozilla"
  // class="c09f">rasper</a></td>
  // </tr>
  public static List<Topic> ParseSearchResultFromWWW(String content) {
    final String TAG = "ParseSearchResult";

    List<Topic> results = new ArrayList<>();
    if (content == null) {
      return results;
    }
    // Log.d(TAG, content);

    // parse topics using Jsoup
    Document doc = Jsoup.parse(content);

    // get all lis
    Elements divs = doc.select("div.b-content");
    if (divs.size() == 0) {
      Log.d(TAG, "ParseSearchResultFromWWW: " + "Did not find div.b-content");
      return results;
    }
    Element div = divs.first();

    Elements trs = div.getElementsByTag("tr");
    for (Element tr : trs) {
      // Log.d(TAG, "ParseSearchResultFromWWW: " + tr.toString());
      Elements tds = tr.getElementsByTag("td");
      if (tds.size() == 0) {
        continue;
      }

      Topic topic = new Topic();
      String title = "";
      String author = "";
      String replier = "";
      String publishDate = "";
      String replyDate = "";
      String topicID = "";

      for (Element td : tds) {
        if (TextUtils.equals(td.attr("class"), "title_9")) {
          title = td.text();
          Elements As = td.getElementsByTag("A");
          if (As.size() > 0) {
            Element A = As.first();
            topicID = StringUtils.getLastStringSegment(A.attr("href"));
          }
        } else if (TextUtils.equals(td.attr("class"), "title_10")) {
          if (publishDate.length() == 0) {
            publishDate = td.text();
          } else {
            replyDate = td.text();
          }
        } else if (TextUtils.equals(td.attr("class"), "title_12")) {
          String person = td.text().replace("|", "").trim();
          if (author.length() == 0) {
            author = person;
          } else {
            replier = person;
          }
        }
      }
      topic.setAuthor(author);
      topic.setTopicID(topicID);
      topic.setTitle(title);
      topic.setReplier(replier);
      topic.setPublishDate(publishDate);
      topic.setReplyDate(replyDate);

