package industries.doublehelix.furrynotemanager.siteImporters;

import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.sql.SQLException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Locale;
import java.util.TreeSet;

import javax.imageio.ImageIO;
import javax.net.ssl.HttpsURLConnection;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Document.OutputSettings;
import org.jsoup.nodes.Element;
import org.jsoup.safety.Whitelist;
import org.jsoup.select.Elements;

import industries.doublehelix.furrynotemanager.DBTools;
import industries.doublehelix.furrynotemanager.Folder;
import industries.doublehelix.furrynotemanager.Note;
import industries.doublehelix.furrynotemanager.User;
import industries.doublehelix.furrynotemanager.WebTools;
import industries.doublehelix.furrynotemanager.gui.CaptchaDialog;

public class FurAffinity implements Importable {
	private static final String baseUrl = "https://www.furaffinity.net";
	private static final String pmListBaseUrl = baseUrl + "/msg/pms/";
	private static final String pmBaseUrl = baseUrl + "/viewmessage/";
	
	private SimpleDateFormat dateParser = new SimpleDateFormat("MMMM dd, yyyy, hh:mm a", Locale.ENGLISH);

	@Override
	public boolean login(User user) throws MalformedURLException, IOException {
		// <img src="https://www.furaffinity.net/captcha.jpg?random=1484862186068" id="captcha_img">
		Document doc = WebTools.getPage("https://www.furaffinity.net/login/?mode=imagecaptcha", baseUrl);
		Element captchaImg = doc.getElementById("captcha_img");

		if (captchaImg == null) return false;
		BufferedImage image = ImageIO.read(new URL(captchaImg.attr("abs:src")));
		String captcha = "";
		while(captcha != null && captcha.trim().isEmpty()) {
			CaptchaDialog dialog = new CaptchaDialog(null, user.getSite(), image);
			captcha = dialog.getCaptcha();
		}
		if (captcha == null) return false; // user aborted captcha dialog
		
		// https://www.furaffinity.net/login/?ref=https://www.furaffinity.net/
		// post:
		// action=login
		// captcha=<captcha>
		// g-recaptcha-response=
		// login=Login to FurAffinity
		// name=<name>
		// pass=<pass>
		// use_old_captcha=1
		String postParams = "action=login&captcha=" + URLEncoder.encode(captcha, "utf-8")
			+ "&g-recaptcha-response=&login=Login to FurAffinity&name=" + URLEncoder.encode(user.getName(), "utf-8")
			+ "&pass=" + URLEncoder.encode(user.getPassword(), "utf-8") + "&use_old_captcha=1";
		byte[] postData = postParams.getBytes( StandardCharsets.UTF_8 );
		URL loginURL = new URL("https://www.furaffinity.net/login/?ref=https://www.furaffinity.net/");
		HttpsURLConnection con = (HttpsURLConnection) loginURL.openConnection();
		con.setRequestMethod("POST");
		con.setRequestProperty( "Content-Type", "application/x-www-form-urlencoded"); 
		con.setRequestProperty( "charset", "utf-8");
		con.setRequestProperty( "Content-Length", Integer.toString(postData.length));
		con.setDoOutput(true); 
		con.setInstanceFollowRedirects(false); // won't get location header otherwise
		try (DataOutputStream wr = new DataOutputStream(con.getOutputStream())) {
			wr.write(postData);
			//wr.flush(); //already done by try-with-ressources?
		}
		
		int rCode = con.getResponseCode();
		if (/*rCode == HttpURLConnection.HTTP_OK ||*/
				rCode == HttpsURLConnection.HTTP_MOVED_TEMP || rCode == HttpsURLConnection.HTTP_MOVED_PERM) {
			String location = con.getHeaderField("Location");
			// /login/?msg=1 = user/pass wrong
			// /login/?msg=2 = captcha wrong (user/pass unchecked)
			// http://www.furaffinity.net/ = login worked
			
			if (location.contains("msg=1")) return false;
			if (location.contains("msg=2")) {
				System.err.println("wrong captcha for FA");
				return login(user); // try again
			}
			return true;
		} else {
	    	System.err.println("Response code of " + con.getResponseCode() + " on FA login");
	    	return false;
	    }
	}

	@Override
	public void logout() throws MalformedURLException, IOException {
		URL logoutURL = new URL("https://www.furaffinity.net/logout/");
		try (BufferedReader reader = new BufferedReader(new InputStreamReader(logoutURL.openStream()))) {
			reader.readLine();
			// don't actually care about the result
		}
	}

	@Override
	public TreeSet<Note> getAllBareboneNotesSince(Calendar calendar, User user,
			SiteImporter importer) throws MalformedURLException, SQLException, IOException {
		TreeSet<Note> notes = new TreeSet<>();
		
		System.out.println("get bb notes since " + calendar.toString());
		
		if (importer != null) importer.setProgressText(SiteImporter.INDETERMINATE);
		
		Document doc = WebTools.getPageWithLogin("https://www.furaffinity.net/controls/switchbox/inbox/", baseUrl,
				user, this, 3);
		if (doc == null){
			System.err.println("Getting notes of FA failed - no login for " + user.getUserId());
			return null; // if it fails at any stage here, just report complete faiilure
		}
		
		// %% = escape, so we can run it through the formatter twice
		String progressText = user.getName() + "@FA: Getting note index: %1$s - %%1$d";
		
		notes.addAll(getAllBareboneNotesFromFolderSince(calendar, user, importer, Folder.getFolder(DBTools.FOLDER_ID_INBOX), String.format(progressText, "Inbox"), 0));
		
		if (importer != null && importer.isCanceled()) return notes;
		
		doc = WebTools.getPageWithLogin("https://www.furaffinity.net/controls/switchbox/outbox/", baseUrl, user, this, 3);
		if (doc == null){
			System.err.println("Getting notes of FA failed - no login for " + user.getUserId());
			return null; // if it fails at any stage here, just report complete faiilure
		}
		notes.addAll(getAllBareboneNotesFromFolderSince(calendar, user, importer, Folder.getFolder(DBTools.FOLDER_ID_OUTBOX), String.format(progressText, "Outbox"), 0));
		
		if (importer != null && importer.isCanceled()) return notes;
		
		doc = WebTools.getPageWithLogin("https://www.furaffinity.net/controls/switchbox/archive/", baseUrl, user, this, 3);
		if (doc == null){
			System.err.println("Getting notes of FA failed - no login for " + user.getUserId());
			return null; // if it fails at any stage here, just report complete faiilure
		}
		notes.addAll(getAllBareboneNotesFromFolderSince(calendar, user, importer, Folder.getFolder(DBTools.FOLDER_ID_FA_ARCHIVE), String.format(progressText, "Archive"), 0));
		
		if (importer != null && importer.isCanceled()) return notes;
		
		doc = WebTools.getPageWithLogin("https://www.furaffinity.net/controls/switchbox/trash/", baseUrl, user, this, 3);
		if (doc == null){
			System.err.println("Getting notes of FA failed - no login for " + user.getUserId());
			return null; // if it fails at any stage here, just report complete faiilure
		}
		notes.addAll(getAllBareboneNotesFromFolderSince(calendar, user, importer, Folder.getFolder(DBTools.FOLDER_ID_FA_TRASH), String.format(progressText, "Trash"), 0));
		
		return notes;
	}
	
	/**
	 * get note list from current folder (needs to be primed by calling https://www.furaffinity.net/controls/switchbox/[..])
	 * @param calendar
	 * @param user
	 * @param importer
	 * @param folder
	 * @param progressTextBase the base for the progress text, page number will be added
	 * @param page starting page
	 * @return
	 * @throws IOException 
	 */
	private TreeSet<Note> getAllBareboneNotesFromFolderSince(Calendar calendar, User user,
			SiteImporter importer, Folder folder, String progressTextBase, int page) throws IOException {
		if (importer != null) importer.setProgressText(String.format(progressTextBase, page));
		
		TreeSet<Note> bbNotes = new TreeSet<Note>();
		Document doc = WebTools.getPageWithLogin(pmListBaseUrl + page, baseUrl, user, this, 3);
		if (doc == null){
			System.err.println("Getting notes of FA failed - no login for " + user.getUserId());
			return bbNotes; // if it fails at any stage here, just report complete faiilure
		}
		if (importer != null && importer.isCanceled()) return new TreeSet<Note>();
		
		Element noteListContainer = doc.getElementById("notes-list");
		Elements noteContainers = noteListContainer.getElementsByClass("note");
		boolean oldNotesReached = false;
		for (Element noteContainer : noteContainers) {
			if (importer != null && importer.isCanceled()) return new TreeSet<Note>();
			Note note = new Note(user);
			note.setFolder(folder);
			Element checkBox = noteContainer.getElementsByTag("input").first();
			note.setOriginalNoteId(Integer.parseInt(checkBox.attr("value")));
			
			Element noteLink = noteContainer.getElementsByClass("notelink").first();
			note.setTitle(noteLink.text());
			note.setUnread(noteLink.hasClass("note-unread"));
			
			if (noteLink.hasClass("high_prio")) {
				note.setPriority(3);
			} else if (noteLink.hasClass("medium_prio")) {
				note.setPriority(2);
			} else if (noteLink.hasClass("low_prio")) {
				note.setPriority(1);
			}
				
			Elements fromColumn = noteContainer.getElementsByClass("col-from");
			if (!fromColumn.isEmpty()) {
				// Inbox, Archive, Trash
				Elements userLinks = fromColumn.first().getElementsByTag("a");
				if (!userLinks.isEmpty()) {
					// me = not in a sub-link, also null in Note, so ignore
					// also the link does not contain user symbols like ~ so all the merrier
					note.setSender(userLinks.first().text());
				}
			}
			
			Elements toColumn = noteContainer.getElementsByClass("col-to");
			if (!toColumn.isEmpty()) {
				// Outbox, Archive, Trash
				Elements userLinks = toColumn.first().getElementsByTag("a");
				if (!userLinks.isEmpty()) {
					// me = not in a sub-link, also null in Note, so ignore
					// also the link does not contain user symbols like ~ so all the merrier
					note.setReceiver(userLinks.first().text());
				}
			}
			
			Element dateSpan = noteContainer.getElementsByClass("popup_date").first();
			// could be directly or in mouseover (title), see which one parses
			// also, remove st/nd/rd/th from it as dateparser can't handle them
			// NOTE: time zone will probably be wrong if the user hasn't set FA to use his - not our problem
			String dateText = dateSpan.text().replaceAll("(?<=\\d)(st|nd|rd|th)", "");
			String dateMouseOver = dateSpan.attr("title").replaceAll("(?<=\\d)(st|nd|rd|th)", "");
			Calendar cal = Calendar.getInstance();
			try {
				cal.setTime(dateParser.parse(dateText));
			} catch (ParseException e) {
				try {
					cal.setTime(dateParser.parse(dateMouseOver));
				} catch (ParseException e1) {
					// shouldn't happen
					e1.printStackTrace();
				}
			}
			
			oldNotesReached = cal.before(calendar);
			if (oldNotesReached) break;
			
			note.setDate(cal);
			
			bbNotes.add(note);
		}
		
		// tree down to the page links from the nearest ID's one:
		// <form id="pms-form"><table><tbody><tr><td><div class="pagination-container"><table><tbody><tr><td>
		// <div class="pagination"><a href="/msg/pms/2/">Next page ></a>
		// FA should really put in more IDs
		Element form = doc.getElementById("pms-form");
		Element pageLinkContainer = form.getElementsByClass("pagination").first();
		Elements pageLinks = pageLinkContainer.getElementsByTag("a");
		boolean hasNextPage = false;
		for (Element link : pageLinks) {
			if (link.text().toLowerCase().contains("next")) {
				if (link.attr("href").equals("#")) {
					break;
				} else {
					hasNextPage = true;
					break;
				}
			}
		}
		
		if (importer != null && importer.isCanceled()) return new TreeSet<Note>();
		if (hasNextPage && !oldNotesReached) {
			bbNotes.addAll(getAllBareboneNotesFromFolderSince(calendar, user, importer, folder, progressTextBase, page + 1));
		}
		
		return bbNotes;
	}

	@Override
	public TreeSet<Note> getNotes(TreeSet<Note> bareboneNotes, User user, SiteImporter importer)
			throws IOException {
		
		int maxVal = bareboneNotes.size();
		String progressText = user.getName() + "@FA: Downloading Notes (%1$d/" + maxVal + ")";
		
		// prime with 0 to get the progress dialog to appear or switch from indeterminate to progressing
		if (importer != null) importer.setProgressText(String.format(progressText, 0));
		
		int curNote = 1;
		for (Note bbNote : bareboneNotes) {
			int percent = (curNote * 100) / maxVal;
			if (percent == 100 && curNote != (maxVal - 1)) System.out.println("PROGRESS IS 100 STILL WORK TO DO (" + curNote + "/" + maxVal + ")");
			if (importer != null && importer.isCanceled()) return bareboneNotes;
			
			if (importer != null) {
				if (curNote % 5 == 0) {
					importer.setProgressText(String.format(progressText, curNote));
				}
				importer.setProgressValue(percent);
			}
			
			Document doc = WebTools.getPageWithLogin(pmBaseUrl + bbNote.getOriginalNoteId(), baseUrl, user, this, 3);
			if (doc == null){
				System.err.println("Getting notes of FA failed - no login for " + user.getUserId());
				return bareboneNotes; // if it fails at any stage here, just report complete faiilure
			}
			
			
			Element form = doc.getElementById("pms-form");
			Element mainTable = form.getElementsByClass("maintable").first();
			
			Element noteContent = mainTable.getElementsByClass("alt1").first();
			
			// remove the first two font tags with their <br>s and one br addittionally.
	        // they are the title again and the to/from, which we already have, and a spacer between those and the note text
	        Elements elemsToRemove = noteContent.getElementsByTag("font");
	        elemsToRemove.get(0).remove();
	        elemsToRemove.get(1).remove();
	        elemsToRemove = noteContent.getElementsByTag("br");
	        elemsToRemove.get(0).remove();
	        elemsToRemove.get(1).remove();
	        elemsToRemove.get(2).remove();
	        //System.out.println(noteContent.html());
			
			bbNote.setText(noteContent.html());
			
			// get raw text for search
			String strippedNoteContent = Jsoup.clean(noteContent.html(), "", Whitelist.none(), new OutputSettings().prettyPrint(false));
			bbNote.setRawText(strippedNoteContent);
			
			++curNote;
		}
		
		return bareboneNotes;
	}

	@Override
	public String getLoggedInUser() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public boolean isAccessViolationErrorPage(String page) {
		Document doc = Jsoup.parse(page, baseUrl);
		Elements links = doc.getElementsByTag("a");

		for (Element link : links) {
			String linkHref = link.attr("abs:href");
			if (linkHref.toLowerCase().contains("/login") &&
					link.text().toLowerCase().contains("Click here to continue")) {
				return true;
			}
		}
		return false;
	}

	@Override
	public void cancel() {
		// TODO Auto-generated method stub
		
	}
}
