package eu.toldi.rss;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.security.NoTypePermission;
import com.thoughtworks.xstream.security.NullPermission;
import com.thoughtworks.xstream.security.PrimitiveTypePermission;

import javax.swing.table.AbstractTableModel;
import javax.swing.tree.DefaultMutableTreeNode;
import java.io.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * A hírcsatornákat és a hozzájuk tartozó cikkeket tárolásáért,valamint azok táblázatban való megjelenítésért felelős objektum.
 * A fájl legefelső tagja <code>list</code> típusú. Ennek az ágai egy-egy <code>feed</code> vagy <code>feedGroup</code>.
 * A feed típus egy ágában szerepelni kell a hírcsatorna linkjét (<code>link</code> tag). Ezen kívül tartalmazhatja a nevét,
 * valamint a hozzá tartozó cikkek listáját (<code>articleList</code> tag)
 * A feedGroup tipusú ágakban a csoport nevének kell szerepelnie,valamint a hozzá tartozó hírcsatornák listája (<code>feedList</code> tag).
 * Az utóbbin belüli ágak formátuma megegyeznek a korábban leírtakkal (<code>feed</code> tag).
 */
public class FeedData extends AbstractTableModel {

    private FeedList feeds = new FeedList();
    private List<Article> articleList = new ArrayList<Article>();


    /**
     * FeedData előállítása fájlból
     *
     * @param filename A fájl elérési útvonala
     *                 A megadott fájlnak egy XML fájlnak kell lennie,amit a FeedData.save() metódus állított elő
     * @see FeedData#save(String)
     */
    public FeedData(String filename) {
        super();
        XStream xStream = initXstream();
        try {
            feeds = new FeedList((ArrayList<Feed>) xStream.fromXML(new FileInputStream(filename)));
            for (int i = 0; i < feeds.size(); i++) {
                feeds.get(i).updateFeed();
                for (int j = 0; j < feeds.get(i).getArticleCount(); j++) {
                    articleList.add(feeds.get(i).get(j));
                }
            }
        } catch (FileNotFoundException e) {
            //Nincs? Semmi gond,mostmár lesz!
            save(filename);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    private Feed limit = null;

    /**
     * Hírcsatorna hozzáadása a listához
     *
     * @param f A hozzáadandó hírcsatorna
     *          Megjegyzés: A hozzáadás során a hírcsatornán található összes cikket kimeti
     */
    public synchronized void addFeed(Feed f) {
        feeds.add(f);
        for (int i = 0; i < f.getArticleCount(); i++) {
            if(!hasAricle(f.get(i)))
                articleList.add(f.get(i));
        }
    }

    /**
     * @see AbstractTableModel#getRowCount()
     */
    @Override
    public int getRowCount() {
        return (limit == null) ? articleList.size() : limit.getArticleCount();
    }

    /**
     * @see AbstractTableModel#getColumnCount()
     */
    @Override
    public int getColumnCount() {
        return 4;
    }

    /**
     * @see AbstractTableModel#getColumnName(int)
     */
    @Override
    public String getColumnName(int oszlop) {
        switch (oszlop) {
            case 0:
                return "Title";
            case 1:
                return "Author";
            case 2:
                return "Date";
            default:
                return "Description";
        }
    }

    /**
     * @see AbstractTableModel#getColumnName(int)
     */
    @Override
    public Class<?> getColumnClass(int oszlop) {
        switch (oszlop) {
            case 2:
                return LocalDateTime.class;
            case 0:
            case 1:
            default:
                return String.class;
        }
    }

    /**
     * @see AbstractTableModel#getValueAt(int, int)
     */
    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {

        switch (columnIndex) {
            case 0:
                return get(rowIndex).getTitle();
            case 1:
                return get(rowIndex).getAuthor();
            case 2:
                return get(rowIndex).getPubDate();
            default:
                return get(rowIndex).getDescription();
        }

    }

    /**
     * Megkeres egy megadott helyen található cikket és visszatér vele
     *
     * @param i Az cikk helye(sora)
     * @return Az adott helyen található cikk
     */
    public Article get(int i) {
        return (limit == null) ? articleList.get(i) : limit.get(i);
    }

    /**
     * Egy sor elemére kattintva ez a metódus hívódik meg. Ez megnyitja az adott helyen található cikket (Amennyiben ez lehetséges)
     *
     * @param row
     */
    public void clicked(int row) {
        get(row).open();
    }

    /**
     * A hírcsatornák körének szűkítése egy hírcsatornára vagy egy hírcsatorna csoportra
     *
     * @param feed A hírcsatorna, amely cikkjeit szeretnénk megtekinteni
     */
    public void limitTo(Feed feed) {
        limit = feed;
    }

    /**
     * Ezt a metódust egy JTree felépítésere lehet használni.
     *
     * @return egy JTree-hez használhato TreeNode
     * @see javax.swing.JTree
     * @see DefaultMutableTreeNode
     */
    public DefaultMutableTreeNode getRootNode() {
        DefaultMutableTreeNode root = new DefaultMutableTreeNode("Feeds");
        for (int i = 0; i < feeds.size(); i++) {
            root.add(feeds.get(i).getTreeNode());
        }
        return root;
    }

    /**
     * A hírcsatornák listájának lekérése
     *
     * @return Hírcsatornák listája
     * @see FeedList
     */
    public synchronized FeedList getFeeds() {
        return feeds;
    }

    /**
     * Egy hírcsatorna törlése a listából
     *
     * @param feed az eltávolítandó hírcsatorna
     *             Megjegyzés: Ez törli az összes cikket,ami ehhez a csatornához tartozott
     */
    public synchronized void removeFeed(Feed feed) {
        boolean found = false;
        for (int i = 0; i < feeds.size(); i++) {
            //Csak ha PONTOSAN megegyezik
            if (feeds.get(i) == feed) {
                feeds.remove(i);
                found = true;
                break;
            }
        }
        if (found) {
            List<Article> similar = new ArrayList<Article>(articleList);
            List<Article> different = new ArrayList<Article>();
            different.addAll(articleList);
            different.addAll(feed.getArticleList());
            similar.retainAll(feed.getArticleList());
            different.removeAll(similar);
            articleList = different;
        }
    }

    /**
     * Egy hírcsatorna áthelyezése a listában. Átrendezésre használatos.
     *
     * @param f     az áthelyezendő hírcsatorna
     * @param index a hírcsatorna új helye
     */
    public synchronized void setLocation(Feed f, int index) {
        feeds.remove(f);
        feeds.add(index, f);
    }

    @Override
    public String toString() {
        return "FeedData{" +
                "feeds=" + feeds +
                '}';
    }

    /**
     * Megnézi hogy egy magadott cikk megtalálható-e a cikklistában
     *
     * @param a a keresett cikk
     * @return logikai érték az alapján,hogy a megadott cikk meg lett-e találva
     * Megjegyzés:  Ez egy lineáris keresést hajt végre
     */
    public boolean hasAricle(Article a) {
        for (Article art : articleList) {
            if (art.getURL() != null && art.getURL().equals(a.getURL()))
                return true;
        }
        return false;
    }

    /**
     * A listában található összes hírcsatorna frissítése
     */
    public synchronized void updateAll() throws IOException {
        for (int i = 0; i < feeds.size(); i++) {
            feeds.get(i).updateFeed();
            for (int j = 0; j < feeds.get(i).getArticleCount(); j++) {
                Article a = feeds.get(i).get(j);
                if (!hasAricle(a))
                    articleList.add(feeds.get(i).get(j));
            }
        }
    }

    /**
     * A hírcsatornák listájának kimentése egy XML fájlba.
     * @param filename A fájl ahová menteni szeretné az adatokat
     * Kimentett fájl leírása:
     *
     */
    public synchronized void save(String filename) {
        XStream xStream = initXstream();
        String xml = xStream.toXML(feeds.getList());
        BufferedWriter writer;
        try {
            writer = new BufferedWriter(new FileWriter(filename));
            writer.write(xml);
            writer.flush();
            writer.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * XStream inicializálására használt metódus.
     *
     * @return XStream objektum
     * Megjegyzés:  XML-be írásra van használva
     * @see XStream
     */
    private XStream initXstream() {
        XStream xStream = new XStream();
        xStream.addPermission(NoTypePermission.NONE);
        xStream.addPermission(NullPermission.NULL);
        xStream.addPermission(PrimitiveTypePermission.PRIMITIVES);
        xStream.allowTypeHierarchy(Collection.class);
        xStream.allowTypesByWildcard(new String[]{
                "eu.toldi.rss.**"
        });
        xStream.processAnnotations(Article.class);
        xStream.processAnnotations(Feed.class);
        xStream.processAnnotations(FeedGroup.class);
        xStream.addImplicitArray(FeedList.class, "list", Feed.class);
        return xStream;
    }


}
