package org.ebookdroid.droids.fb2.codec;

import static org.ebookdroid.droids.fb2.codec.FB2Page.MARGIN_X;
import static org.ebookdroid.droids.fb2.codec.FB2Page.PAGE_WIDTH;

import org.ebookdroid.common.settings.AppSettings;

import android.graphics.Bitmap;
import android.text.TextPaint;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map.Entry;
import java.util.TreeMap;

import org.emdev.common.fonts.FontManager;
import org.emdev.common.fonts.SystemFontProvider;
import org.emdev.common.fonts.data.FontFamilyType;
import org.emdev.common.fonts.data.FontStyle;
import org.emdev.common.fonts.typeface.TypefaceEx;
import org.emdev.common.textmarkup.CustomTextPaintContainer;
import org.emdev.common.textmarkup.JustificationMode;
import org.emdev.common.textmarkup.MarkupElement;
import org.emdev.common.textmarkup.MarkupEndDocument;
import org.emdev.common.textmarkup.TextStyle;
import org.emdev.common.textmarkup.Words;
import org.emdev.common.textmarkup.image.DiskImageData;
import org.emdev.common.textmarkup.image.IImageData;
import org.emdev.common.textmarkup.image.MemoryImageData;
import org.emdev.common.textmarkup.line.Image;
import org.emdev.common.textmarkup.line.LineStream;
import org.emdev.utils.LengthUtils;
import org.emdev.utils.collections.SparseArrayEx;

public class ParsedContent {

    private static final CustomTextPaintContainer DEFAULT_PAINTS = new CustomTextPaintContainer(null);

    final ArrayList<MarkupElement> docMarkup = new ArrayList<MarkupElement>();

    final HashMap<String, ArrayList<MarkupElement>> streams = new HashMap<String, ArrayList<MarkupElement>>();

    private final TreeMap<String, Image> images = new TreeMap<String, Image>();
    private final TreeMap<String, LineStream> notes = new TreeMap<String, LineStream>();

    public final SparseArrayEx<Words> words = new SparseArrayEx<Words>();

    private String cover;

    public TypefaceEx[] fonts;
    public TypefaceEx mono;
    public final CustomTextPaintContainer paints;

    public ParsedContent() {
        paints = DEFAULT_PAINTS;
    }

    public ParsedContent(TextPaint defPaint) {
        paints = new CustomTextPaintContainer(defPaint);
    }

    public void loadFonts() {
        final String fontPack = AppSettings.current().fb2FontPack;
        final FontFamilyType family = FontFamilyType.SERIF;
        loadFonts(fontPack, family);
    }

    public void loadFonts(final String fontPack, final FontFamilyType family) {
        final FontStyle[] styles = FontStyle.values();
        fonts = new TypefaceEx[styles.length];
        for (final FontStyle style : styles) {
            final TypefaceEx font = FontManager.getFont(fontPack, family, style);
            fonts[style.ordinal()] = font;
            this.paints.getTextPaint(font, TextStyle.TEXT.getFontSize());
            // System.out.println("Preloaded: " + font);
        }
        mono = FontManager.getFont(SystemFontProvider.SYSTEM_FONT_PACK, FontFamilyType.MONO, FontStyle.REGULAR);
        // System.out.println("Preloaded: " + mono);
    }

    public void clear() {
        docMarkup.clear();
        for (final Entry<String, ArrayList<MarkupElement>> entry : streams.entrySet()) {
            final ArrayList<MarkupElement> value = entry.getValue();
            if (value != null) {
                value.clear();
            }
        }
        streams.clear();
    }

    public void recycle() {
        for (final Image image : images.values()) {
            image.data.recycle();
        }
        images.clear();
        for (final Words w : words) {
            w.recycle();
        }
        words.clear();
    }

    public ArrayList<MarkupElement> getMarkupStream(final String streamName) {
        if (streamName == null) {
            return docMarkup;
        }
        ArrayList<MarkupElement> stream = streams.get(streamName);
        if (stream == null) {
            stream = new ArrayList<MarkupElement>();
            streams.put(streamName, stream);
        }
        return stream;
    }

    public void addImage(final String tmpBinaryName, final String encoded) {
        if (tmpBinaryName != null && encoded != null) {
            IImageData data = new MemoryImageData(encoded);
            if (AppSettings.current().fb2CacheImagesOnDisk) {
                data = new DiskImageData((MemoryImageData) data);
            }
            images.put("I" + tmpBinaryName, new Image(data, true));
            images.put("O" + tmpBinaryName, new Image(data, false));
        }
    }

    public void addImage(final String tmpBinaryName, final char[] tmpBinary, final int tmpBinaryStart,
            final int tmpBinaryLength) {
        if (tmpBinaryName != null && tmpBinary != null && tmpBinaryLength > 0) {
            IImageData data = new MemoryImageData(tmpBinary, tmpBinaryStart, tmpBinaryLength);
            if (AppSettings.current().fb2CacheImagesOnDisk) {
                data = new DiskImageData((MemoryImageData) data);
            }
            images.put("I" + tmpBinaryName, new Image(data, true));
            images.put("O" + tmpBinaryName, new Image(data, false));
        }
    }

    public Image getImage(final String name, final boolean inline) {
        if (name == null) {
            return null;
        }
        Image img = images.get((inline ? "I" : "O") + name);
        if (img == null && name.startsWith("#")) {
            img = images.get((inline ? "I" : "O") + name.substring(1));
        }
        return img;
    }

    public LineStream getNote(final String noteName, boolean hyphenEnabled) {
        LineStream note = notes.get(noteName);
        if (note != null) {
            return note;
        }

        ArrayList<MarkupElement> stream = getMarkupStream(noteName);
        if (LengthUtils.isEmpty(stream) && noteName.startsWith("#")) {
            stream = getMarkupStream(noteName.substring(1));
        }
        if (stream != null) {
            note = createLines(stream, PAGE_WIDTH - 2 * MARGIN_X, JustificationMode.Justify, hyphenEnabled);
            notes.put(noteName, note);
        }

        return note;
    }

    public LineStream createLines(final List<MarkupElement> markup, final int maxLineWidth, final JustificationMode jm,
            final boolean hyphenEnabled) {

        final LineStream lines = new LineStream(this, maxLineWidth, jm, hyphenEnabled);
        if (LengthUtils.isNotEmpty(markup)) {
            for (final MarkupElement me : markup) {
                if (me instanceof MarkupEndDocument) {
                    break;
                }
                me.publishToLines(lines);
            }
        }
        return lines;
    }

    public LineStream getStreamLines(final String streamName, final int maxWidth, final JustificationMode jm,
            final boolean hyphenEnabled) {

        final ArrayList<MarkupElement> stream = getMarkupStream(streamName);
        if (stream != null) {
            return createLines(stream, maxWidth, jm, hyphenEnabled);
        }
        return null;
    }

    public void setCover(final String value) {
        this.cover = value;
    }

    public Bitmap getCoverImage() {
        final Image image = getImage(cover, false);
        if (image != null) {
            return image.data.getBitmap();
        }
        return null;
    }

}
