package com.orgzly.android.misc;

import android.util.Log;

import com.orgzly.android.Book;
import com.orgzly.android.BookName;
import com.orgzly.android.LipsumBookGenerator;
import com.orgzly.android.OrgzlyTest;
import com.orgzly.android.util.MiscUtils;

import org.junit.Test;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;

import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.fail;

public class BookParsingTest extends OrgzlyTest {
    private static final String TAG = BookParsingTest.class.getName();

    private boolean runResourcesDemandingTest() {
        if (true) return false;

        Log.d(TAG, "Deciding whether to run test for" +
                " MODEL:" + android.os.Build.MODEL +
                " FINGERPRINT:" + android.os.Build.FINGERPRINT +
                " BRAND:" + android.os.Build.BRAND +
                " DEVICE:" + android.os.Build.DEVICE +
                " HARDWARE:" + android.os.Build.HARDWARE
        );

        switch (android.os.Build.MODEL) {
            /* Do not run test for these. */
            case "Full Android on x86 Emulator":
            case "Android SDK built for x86_64":
            case "GT-I5500":
            case "GT-I9300":
            case "HTC One V":
                return false;
        }

        return true;
    }

    @Test
    public void testBookContent1() throws IOException {
        onBook("Sample book used for tests\n\n\n* Note")
                .onLoad()
                .isContent("Sample book used for tests")
                .isNoteTitle(1, "Note")
                .isNoteLevel(1, 1)
                .isWhenSaved("Sample book used for tests\n\n* Note\n");
    }

    @Test
    public void testBookContentPrefixedWithSpaces() {
        onBook("  Sample book used for tests\n\n\n* Note")
                .onLoad()
                .isContent("  Sample book used for tests")
                .isNoteTitle(1, "Note")
                .isNoteLevel(1, 1)
                .isWhenSaved("  Sample book used for tests\n\n* Note\n");
    }

    @Test
    public void testBookContent2() throws IOException {
        onBook("\nSample book used for tests\n\n\n* Note")
                .onLoad()
                .isContent("Sample book used for tests")
                .isNoteTitle(1, "Note")
                .isNoteLevel(1, 1)
                .isWhenSaved("Sample book used for tests\n\n* Note\n");
    }

    @Test
    public void testSavingWithPriorities() throws IOException {
        onBook("\nSample book used for tests\n\n\n* Note 1\n* [#B] Note 2\n* [#A] Note 3")
                .onLoad()
                .isContent("Sample book used for tests")
                .isWhenSaved("Sample book used for tests\n\n* Note 1\n* [#B] Note 2\n* [#A] Note 3\n");
    }

    @Test
    public void testNotIndented1() throws IOException {
        onBook("* Note 1\nSCHEDULED: <2015-02-11 Wed +1d>").onLoad()
                .isWhenSaved("* Note 1\nSCHEDULED: <2015-02-11 Wed +1d>\n\n");
    }

    @Test
    public void testNotIndented2() throws IOException {
        onBook("* Note 1\n:LOGBOOK:\n:END:").onLoad()
                .isWhenSaved("* Note 1\n:LOGBOOK:\n:END:\n\n");
    }

    @Test
    public void testIndented1() throws IOException {
        onBook("* Note 1\n  SCHEDULED: <2015-02-11 Wed +1d>").onLoad()
                .isWhenSaved("* Note 1\n  SCHEDULED: <2015-02-11 Wed +1d>\n\n");
    }

    @Test
    public void testIndented2() throws IOException {
        onBook("* Note 1\n  :LOGBOOK:\n  :END:").onLoad()
                .isWhenSaved("* Note 1\n  :LOGBOOK:\n  :END:\n\n");
    }

    @Test
    public void testEmptyProperties() throws IOException {
        onBook("* Note 1\n  :PROPERTIES:\n  :END:").onLoad()
                .isWhenSaved("* Note 1\n");

    }

    @Test
    public void testProperties() throws IOException {
        onBook("* Note 1\n" +
                "  :PROPERTIES:\n" +
                "  :name: value\n" +
                "  :END:").onLoad()
                .isWhenSaved("* Note 1\n" +
                        "  :PROPERTIES:\n" +
                        "  :name:     value\n" +
                        "  :END:\n\n");
    }

    @Test
    public void testPropertiesMultiple() throws IOException {
        onBook("* Note 1\n" +
                "  :PROPERTIES:\n" +
                "  :name2: value2\n" +
                "  :name1: value1\n" +
                "  :END:").onLoad()
                .isWhenSaved("* Note 1\n" +
                        "  :PROPERTIES:\n" +
                        "  :name2:    value2\n" +
                        "  :name1:    value1\n" +
                        "  :END:\n\n");
    }

    @Test
    public void testPropertiesEmpty() throws IOException {
        onBook("* Note 1\n" +
                "  :PROPERTIES:\n" +
                "  :END:").onLoad()
                .isWhenSaved("* Note 1\n");
    }

    @Test
    public void testParsingClockTimesOutsideLogbook() {
        onBook("* STARTED Test times\n" +
               "  CLOCK: [2016-10-27 Thu 18:40]--[2016-10-27 Thu 18:51] =>  0:11\n" +
               "  CLOCK: [2016-10-27 Thu 18:10]--[2016-10-27 Thu 18:25] =>  0:15\n" +
               "  CLOCK: [2016-10-27 Thu 17:50]--[2016-10-27 Thu 18:05] =>  0:15\n")
                .onLoad()
                .isWhenSaved("* STARTED Test times\n" +
                             "  CLOCK: [2016-10-27 Thu 18:40]--[2016-10-27 Thu 18:51] =>  0:11\n" +
                             "  CLOCK: [2016-10-27 Thu 18:10]--[2016-10-27 Thu 18:25] =>  0:15\n" +
                             "  CLOCK: [2016-10-27 Thu 17:50]--[2016-10-27 Thu 18:05] =>  0:15\n\n");
    }

    /*
     * Books in different languages, different sizes and formats ...
     */

    @Test
    public void testLoadingTextDv() throws IOException {
        onBookFile("html/dv.org").onLoad().onGet();
    }

    @Test
    public void testLoadingTextZh() throws IOException {
        onBookFile("html/zh.org").onLoad().onGet();
    }

    @Test
    public void testLoadingHuge() throws IOException {
        if (runResourcesDemandingTest()) {
            onBookFile("org/org-issues.10x.org").onLoad();
        }
    }

    /*
     * Files generated using http://generator.lorem-ipsum.info/ ...
     */

    @Test
    public void testLoadingArabicLipsum() throws IOException {
        onBookFile("lipsum/Arabic-Lipsum.org").onLoad().onGet();
    }

    @Test
    public void testLoadingChineseLipsum() throws IOException {
        onBookFile("lipsum/Chinese-Lipsum.org").onLoad().onGet();
    }

    @Test
    public void testLoadingGreekLipsum() throws IOException {
        onBookFile("lipsum/Greek-Lipsum.org").onLoad().onGet();
    }

    @Test
    public void testLoadingHebrewLipsum() throws IOException {
        onBookFile("lipsum/Hebrew-Lipsum.org").onLoad().onGet();
    }

    @Test
    public void testLoadingHindiLipsum() throws IOException {
        onBookFile("lipsum/Hindi-Lipsum.org").onLoad().onGet();
    }

    @Test
    public void testLoadingJapaneseLipsum() throws IOException {
        onBookFile("lipsum/Japanese-Lipsum.org").onLoad().onGet();
    }

    @Test
    public void testLoadingL33TspeakLipsum() throws IOException {
        onBookFile("lipsum/L33tspeak-Lipsum.org").onLoad().onGet();
    }

    @Test
    public void testLoadingRussianLipsum() throws IOException {
        onBookFile("lipsum/Russian-Lipsum.org").onLoad().onGet();
    }

    /*
     * Loading images as books ...
     */

    @Test
    public void testLoadingLargerJpg() {
        if (runResourcesDemandingTest()) {
            onBookFile("images/IMG_2932.org").onLoadFailed();
        }
    }

    @Test
    public void testLoadingSmallPng() {
        onBookFile("images/logo11w.org").onLoad();
    }

    @Test
    public void testLion() {
        if (runResourcesDemandingTest()) {
            onBookFile("images/lion-wide.org").onLoad();
        }
    }

    /*
     * Generated lipsum ...
     */

    @Test
    public void testLoadingLatinLipsumHugeBookContentWithTitleTitleAndNoteContent() {
        if (runResourcesDemandingTest()) {
            String data = LipsumBookGenerator.generateOrgString(1000000, new int[] { 1000000, 1000000 });
            onBook(data).onLoadFailed();
        }
    }

    @Test
    public void testLoadingLatinLipsumHugeNoteTitle() {
        if (runResourcesDemandingTest()) {
            String data = LipsumBookGenerator.generateOrgString(0, new int[] { 2000000, 0 });
            onBook(data).onLoadFailed();
        }
    }

    @Test
    public void testLoadingLatinLipsumLargeNoteTitles() {
        if (runResourcesDemandingTest()) {
            String data = LipsumBookGenerator.generateOrgString(0, new int[] { 1000000, 0, 1500000, 1 });
            onBook(data).onLoad();
        }
    }

    @Test
    public void testLoadingLatinLipsumHugeNoteTitleWithContent() {
        if (runResourcesDemandingTest()) {
            String data = LipsumBookGenerator.generateOrgString(0, new int[] { 1000000, 1000000 });
            onBook(data).onLoadFailed();
        }
    }

    @Test
    public void testBookContentTooBig() {
        if (runResourcesDemandingTest()) {
            String data = LipsumBookGenerator.generateOrgString(2000000, null);
            onBook(data).onLoadFailed();
        }
    }

    public TestedBook onBookFile(String resourceName) {
        return new TestedBook(new TestedBookSourceFromResource(resourceName));
    }

    public TestedBook onBook(String content) {
        return new TestedBook(new TestedBookSourceFromContent(content));
    }

    class TestedBook {
        private final TestedBookSource bookSource;

        private Book book;

        public TestedBook(TestedBookSource bookSource) {
            this.bookSource = bookSource;
        }

        public TestedBook onLoad() {
            shelf.clearDatabase();

            File tmpFile = bookSource.getTmpFile();
            try {
                /* Load from file. */
                shelf.loadBookFromFile("Notebook", BookName.Format.ORG, tmpFile);

            } catch (IOException e) {
                e.printStackTrace();
                fail(e.toString());

            } finally {
                tmpFile.delete();
            }

            book = shelf.getBooks().get(0);

            return this;
        }

        public TestedBook onGet() {
            try {
                book = shelf.getBooks().get(0);

            } catch (Exception e) {
                e.printStackTrace();
                fail(e.toString());
            }

            return this;
        }

        public TestedBook onGetFailed() {
            try {
                book = shelf.getBooks().get(0);
                fail("Book should fail to load");

            } catch (Exception e) {
                e.printStackTrace();
            }

            return this;
        }

        public TestedBook onLoadFailed() {
            shelf.clearDatabase();

            File tmpFile = bookSource.getTmpFile();
            try {
                /* Load from file. */
                shelf.loadBookFromFile("Notebook", BookName.Format.ORG, tmpFile);
                fail("Book should fail to load");

            } catch (IOException e) {
                e.printStackTrace();

            } finally {
                tmpFile.delete();
            }

            return this;
        }

        public TestedBook isContent(String str) {
            if (book == null) fail("Notebook not loaded. Call onLoad().");

            Book book = shelf.getBooks().get(0);
            assertEquals("Content", str, book.getPreface());
            return this;
        }

        public TestedBook isNoteTitle(int note, String str) {
            if (book == null) fail("Notebook not loaded. Call onLoad()");

            assertEquals("Title", str, shelf.getNote(note).getHead().getTitle());
            return this;
        }

        public TestedBook isNoteLevel(int note, int level) {
            if (book == null) fail("Notebook not loaded. Call onLoad()");

            assertEquals("Note level", level, shelf.getNote(note).getPosition().getLevel());
            return this;
        }

        public TestedBook isWhenSaved(String expacted) {
            if (book == null) fail("Notebook not loaded. Call onLoad()");

            try {
                /* Write from db -> temp file. */
                File file = shelf.getTempBookFile();
                try {
                    shelf.writeBookToFile(book, BookName.Format.ORG, file);
                    assertEquals("Notebook", expacted, MiscUtils.readStringFromFile(file));
                } finally {
                    file.delete();
                }

            } catch (IOException e) {
                e.printStackTrace();
                fail(e.toString());
            }

            return this;
        }
    }

    private interface TestedBookSource {
        File getTmpFile();
    }

    private class TestedBookSourceFromContent implements TestedBookSource {
        private String content;

        public TestedBookSourceFromContent(String content) {
            this.content = content;
        }

        @Override
        public File getTmpFile() {
            File file = null;

            try {
                file = shelf.getTempBookFile();
                MiscUtils.writeStringToFile(content, file);
            } catch (IOException e) {
                e.printStackTrace();
                fail(e.toString());
            }

            return file;
        }
    }

    private class TestedBookSourceFromResource implements TestedBookSource {
        private String resourceName;

        public TestedBookSourceFromResource(String resourceName) {
            this.resourceName = resourceName;
        }

        @Override
        public File getTmpFile() {
            InputStream stream = BookParsingTest.class.getClassLoader().getResourceAsStream("assets/" + resourceName);

            if (stream == null) {
                fail("Resource " + resourceName + " not found inside assets/");
            }

            File file = null;

            try {
                file = shelf.getTempBookFile();
                MiscUtils.writeStreamToFile(stream, file);
                stream.close();
            } catch (IOException e) {
                e.printStackTrace();
                fail(e.toString());
            }

            return file;
        }
    }
}
