/*
 * DSBDirect
 * Copyright (C) 2019 Fynn Godau
 *
 * This program 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.
 *
 * This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * This software is not affiliated with heinekingmedia GmbH, the
 * developer of the DSB platform.
 */

package godau.fynn.dsbdirect.table.reader;

import android.content.Context;
import android.util.Log;
import godau.fynn.dsbdirect.Utility;
import godau.fynn.dsbdirect.table.Entry;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;

public class Untis extends Reader {

    private static final int UNDEFINED   =  0;
    private static final int CLASS       =  1;
    private static final int SUBJECT     =  2;
    private static final int LESSON      =  3;
    private static final int TYPE        =  4;
    private static final int TEACHER     =  5;
    private static final int ROOM        =  6;
    private static final int OLD_SUBJECT =  7;
    private static final int OLD_TEACHER =  8;
    private static final int OLD_CLASS   =  9;
    private static final int INFO        = 10;
    private static final int MASTER_SIZE = 11;


    public Untis(String html, Context context) {
        super(html, context);
    }

    private Document d;

    @Override
    public ArrayList<Entry> read() {

        // Leave brs be
        mHtml = mHtml.replaceAll("<br>", "&lt;br&gt;");

        // Leave strikes be
        mHtml = mHtml.replaceAll("<s(trike)*>", "&lt;strike&gt;");
        mHtml = mHtml.replaceAll("</s(trike)*>", "&lt;&#47;strike&gt;");

        if (d == null) {
            d = Jsoup.parse(mHtml);
        }

        // Tables are inside center tags
        Elements centers = d.getElementsByTag("center");


        // Every other center contains an advertisement for Untis
        for (int centerIndex = 0; centerIndex < centers.size(); centerIndex += 2) {

            Element center = centers.get(centerIndex);

            // Get which date this center is about
            String dateString = center.selectFirst("div").text();
            String[] dateDigits = dateString.split(" ")[0].split("\\.");

            Calendar calendar = Utility.zeroOClock(Calendar.getInstance());
            calendar.set(Calendar.DAY_OF_MONTH, Integer.parseInt(dateDigits[0]));
            calendar.set(Calendar.MONTH, Integer.parseInt(dateDigits[1]) - 1);
            calendar.set(Calendar.YEAR, Integer.parseInt(dateDigits[2]));
            Date date = calendar.getTime();

            // Get info box, if present
            Elements infoTables = center.getElementsByClass("info");
            if (infoTables.size() != 0) {
                Element infoTableBody = infoTables.first().getElementsByTag("tbody").first();

                Elements infoTableTrs = infoTableBody.getElementsByTag("tr");

                /* First tr will (probably) contain "Nachrichten zum Tag" headline, but we check it anyway because
                 * the headline is inside a th tag, not a td tag.
                 */
                for (Element tr : infoTableTrs) {

                    Elements tds = tr.getElementsByTag("td");


                    if (tds.size() > 0 && !isUselessLine(tds.first().text())) {
                        // Construct an entry for this line

                        // If there are two columns: separate them with a ':'
                        addEntry(null, null, null,
                                Utility.smartConcatenate(tds, ": "),
                                date
                        );
                    }


                }

            }

            // Get main table
            Elements mainTables = center.getElementsByClass("mon_list"); // There should be exactly one
            if (mainTables.size() > 0) {
                Element mainTableBody = mainTables.first().getElementsByTag("tbody").first();

                Elements mainTableTrs = mainTableBody.getElementsByTag("tr");

                // Get definitions from the first row
                Object[] mainTableDefinitionObjects = mainTableTrs.first().getElementsByTag("th")
                        .eachText().toArray();
                String[] mainTableDefinitions = Arrays.copyOf(mainTableDefinitionObjects,
                        mainTableDefinitionObjects.length, String[].class);
                int[] positions = getMasterTablePositions(mainTableDefinitions);

                String inlineClass = null;

                // Get every row
                // Start with 1 because first row contained definitions
                row:
                for (int trIndex = 1; trIndex < mainTableTrs.size(); trIndex++) {
                    Element tr = mainTableTrs.get(trIndex);

                    Elements tds = tr.getElementsByTag("td");
                    String[] masterRow = new String[MASTER_SIZE];

                    // Get value from every column
                    for (int tdIndex = 0; tdIndex < tds.size(); tdIndex++) {
                        Element td = tds.get(tdIndex);



                        String s = td.text();

                        if (td.hasClass("inline_header")) {
                            // This is an inline class definition
                            inlineClass = s;
                            continue row;
                        } else {
                            masterRow[positions[tdIndex]] = s;
                        }
                    }

                    // Add inline class if class not provided by entry
                    if (masterRow[CLASS] == null && inlineClass != null) {
                        masterRow[CLASS] = inlineClass;
                    }

                    constructEntry(masterRow, date);

                }

            }


        }

        Log.d("UNTISREAD", "Read out " + getEntries().size() + " entries");
        return getEntries();

    }

    public String getSchoolName() {

        if (d == null) {
            Document d = Jsoup.parse(mHtml);
        }


        Element monHead = d.getElementsByClass("mon_head").first();
        Element p = monHead.getElementsByTag("tbody").first()
                .getElementsByTag("tr").first()
                .getElementsByAttributeValue("align", "right").first()
                .getElementsByTag("p").first();

        return p.textNodes().get(0).text();
    }

    /**
     * Constructs an entry and then adds it
     */
    private void constructEntry(String[] masterRow, Date date) {
        String classString = ratherThisThanThat(masterRow[CLASS], masterRow[OLD_CLASS]);
        String subject = ratherThisThanThat(masterRow[SUBJECT], masterRow[OLD_SUBJECT]);

        String combinedClassString = Utility.smartConcatenate(new String[]{classString, subject}, " · ");

        String lesson = masterRow[LESSON];

        String teacher = ratherThisThanThat(masterRow[TEACHER], masterRow[OLD_TEACHER]);

        String info = Utility.smartConcatenate(new String[]{
                masterRow[TYPE], masterRow[ROOM], masterRow[INFO]
        }, " · ");

        addEntry(combinedClassString, lesson, teacher, info, date);


    }

    private String ratherThisThanThat(String string1, String string2) {
        // Prefers to return string1, but returns string2 surrounded by strike tags if string1 is null, empty or just dashes.

        final String DASHES_REGEX = "-+(?!.)"; // matches one or more "-" if nothing else follows it

        if (string1 != null && !string1.isEmpty() && !string1.matches(DASHES_REGEX)) {
            return string1;
        } else if (string2 != null) { // Don't concatenate around with null

            return "<strike>" + string2 + "</strike>";
        } else {
            return null;
        }
    }

    private int[] getMasterTablePositions(String[] definitions) {
        // Return where in the imaginary master table each column belongs

        int[] positions = new int[definitions.length];

        // Test each definition for matches with strings
        for (int i = 0; i < definitions.length; i++) {
            switch (definitions[i]) {
                case "Klasse(n)":
                    positions[i] = CLASS; continue;
                case "Fach":
                    positions[i] = SUBJECT; continue;
                case "Stunde":
		case "Std.":
                    positions[i] = LESSON; continue;
                case "Art":
                    positions[i] = TYPE; continue;
                case "Vertreter":
                case "Vertr. von":
                    positions[i] = TEACHER; continue;
                case "Raum":
                    positions[i] = ROOM; continue;
                case "(Fach)":
                    positions[i] = OLD_SUBJECT; continue;
                case "(Lehrer)":
                    positions[i] = OLD_TEACHER; continue;
                case "(Klasse(n))":
                    positions[i] = OLD_CLASS; continue;
                case "Vertretungs-Text":
                case "Vertr.Text":
                case "Text":
                    positions[i] = INFO; continue;
                default:
                    positions[i] = UNDEFINED;
            }
        }

        return positions;
    }

    private boolean isUselessLine(String string) {
        String[] uselessLines = {"Abwesende Klassen", "Betroffene Klassen", "Abwesende Lehrer"};

        for (String useless :
                uselessLines) {
            if (string.contains(useless)) {
                // If it contains something useless, this line is useless
                return true;
            }
        }

        // It didn't contain anything useless and thus is not useless
        return false;
    }
}
