/*
 * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores
 * CA 94065 USA or visit www.oracle.com if you need additional information or
 * have any questions.
 */

package com.codename1.components;

import com.codename1.io.ConnectionRequest;
import com.codename1.ui.Button;
import com.codename1.ui.Command;
import com.codename1.ui.Component;
import com.codename1.ui.Container;
import com.codename1.ui.Display;
import com.codename1.ui.Form;
import com.codename1.ui.Label;
import com.codename1.ui.List;
import com.codename1.ui.layouts.BorderLayout;
import com.codename1.ui.events.ActionEvent;
import com.codename1.ui.events.ActionListener;
import com.codename1.io.NetworkEvent;
import com.codename1.io.NetworkManager;
import com.codename1.io.services.ImageDownloadService;
import com.codename1.io.services.RSSService;
import com.codename1.ui.Image;
import com.codename1.ui.layouts.BoxLayout;
import com.codename1.ui.list.DefaultListModel;
import com.codename1.ui.list.GenericListCellRenderer;
import com.codename1.ui.TextArea;
import java.util.Hashtable;
import java.util.Vector;

/**
 * A List implementing an RSS reader that automatically populates itself with content
 * from the RSS chanel. When clicking an article it displays the HTML content of said
 * article in a new Form.
 *
 * @author Shai Almog
 */
public class RSSReader extends List {
    private Vector existingData;
    private String url = "http://codenameone.blogspot.com/feeds/posts/default?alt=rss";
    private RSSService service;
    private int limit = 20;
    private boolean waitingForResponseLock;
    private boolean blockList;
    private String progressTitle = "Fetching RSS";
    private boolean displayProgressPercentage = true;
    private Image iconPlaceholder;
    private static final Hashtable MORE = new Hashtable();
    static {
        MORE.put("title", "More");
        MORE.put("details", "Fetch More Elements");
    }

    private boolean designMode;

    /**
     * Sets the form/container to which the RSS will navigate when clicking a RSS
     * entry
     */
    private Container targetContainer;

    /**
     * Indicates whether a back command should be added implicitly to the target container
     */
    private boolean addBackToTaget = true;

    /**
     * Creates an rss reader instance
     */
    public RSSReader() {
        setUIID("RSSReader");
        setRenderer(new GenericListCellRenderer(createRendererContainer(), createRendererContainer()));
        addActionListener(new EventHandler());
    }


    /**
     * Set the description for the "more" request
     * 
     * @param d the description
     */
    public static void setMoreDescription(String d) {
        MORE.put("description", d);
    }

    /**
     * Set the title for the "more" request
     * 
     * @param t new title
     */
    public static void setMoreTitle(String t) {
        MORE.put("title", t);
    }

    private Container createRendererContainer() {
        Container entries = new Container(new BoxLayout(BoxLayout.Y_AXIS));
        entries.setUIID("RSSEntry");
        Label title = new Label();
        title.setName("title");
        title.setUIID("RSSTitle");
        entries.addComponent(title);
        TextArea description = new TextArea(2, 30);
        description.setGrowByContent(false);
        description.setName("details");
        description.setUIID("RSSDescription");
        description.setScrollVisible(false);
        entries.addComponent(description);
        if(iconPlaceholder != null) {
            Container wrap = new Container(new BorderLayout());
            wrap.addComponent(BorderLayout.CENTER, entries);
            Label icon = new Label();
            icon.setIcon(iconPlaceholder);
            icon.setUIID("RSSIcon");
            icon.setName("icon");
            wrap.addComponent(BorderLayout.WEST, icon);
            entries = wrap;
        }
        return entries;
    }

    /**
     * The URL of the RSS stream
     *
     * @param url The URL of the RSS stream
     */
    public void setURL(String url) {
        this.url = url;
    }

    /**
     * Send the request to the server, will only work once. This is called implicitly
     * when the list is initialized
     */
    public void sendRequest() {
        if(service == null) {
            service = new RSSService(url, limit);
            if(iconPlaceholder != null) {
                service.setIconPlaceholder(iconPlaceholder);
            }
            service.addResponseListener(new EventHandler());
            if(blockList) {
                Progress p = new Progress(progressTitle, service, displayProgressPercentage);
                p.setAutoShow(true);
                p.setDisposeOnCompletion(true);
            }
            setHint(progressTitle);
            NetworkManager.getInstance().addToQueue(service);
        }
    }

    /**
     * @inheritDoc
     */
    protected void initComponent() {
        super.initComponent();
        if(designMode) {
            setHint("RSS Data Will Show Here");
        } else {
            sendRequest();
        }
    }

    /**
     * The URL of the RSS stream
     *
     * @return The URL of the RSS stream 
     */
    public String getURL() {
        return url;
    }

    /**
     * @return the iconPlaceholder
     */
    public Image getIconPlaceholder() {
        return iconPlaceholder;
    }

    /**
     * @param iconPlaceholder the iconPlaceholder to set
     */
    public void setIconPlaceholder(Image iconPlaceholder) {
        this.iconPlaceholder = iconPlaceholder;
        if(service != null) {
            service.setIconPlaceholder(iconPlaceholder);
        }
        setRenderer(new GenericListCellRenderer(createRendererContainer(), createRendererContainer()));
    }

    class Listener implements ActionListener {
        private String url;
        public Listener(String url) {
            this.url = url;
        }
        public void actionPerformed(ActionEvent evt) {
            Display.getInstance().execute(url);
        }
    }
    
    void updateComponentValues(Container root, Hashtable h) {
        int c = root.getComponentCount();
        for(int iter = 0 ; iter < c ; iter++) {
            Component current = root.getComponentAt(iter);
            
            // the comparison assumes that other container subclases are really just
            // custom components e.g. tree, table and HTMLComponent as well as third party
            // subclasses
            if(current.getClass() == com.codename1.ui.Container.class ||
                    current.getClass() == com.codename1.ui.Tabs.class) {
                updateComponentValues((Container)current, h);
                continue;
            }
            String n = current.getName();
            if(n != null) {
                String val = (String)h.get(n);
                if(val != null) {
                    if(current instanceof Button) {
                        final String url = (String)val;
                        ((Button)current).addActionListener(new Listener(url));
                        continue;
                    }
                    if(current instanceof Label) {
                        ((Label)current).setText(val);
                        continue;
                    }
                    if(current instanceof TextArea) {
                        ((TextArea)current).setText(val);
                        continue;
                    }
                    if(current instanceof WebBrowser) {
                        ((WebBrowser)current).setPage(val, null);
                        continue;
                    }
                }
            }
        }
    }

    class BackCommand extends Command {
        private Form sourceForm;
        public BackCommand(Form sourceForm) {
            super("Back");
            this.sourceForm = sourceForm;
        }
        public void actionPerformed(ActionEvent ev) {
            sourceForm.showBack();
        }        
    }
    
    /**
     * Shows a form containing the RSS entry
     *
     * @param h the parsed entry
     */
    protected void showRSSEntry(Hashtable h) {
        Form newForm = null;
        if(targetContainer != null) {
            if(targetContainer instanceof Form) {
                newForm = (Form)targetContainer;
            } else {
                newForm = new Form((String)h.get("title"));
                newForm.setLayout(new BorderLayout());
                newForm.addComponent(BorderLayout.CENTER, targetContainer);
            }
            updateComponentValues(newForm, h);
        } else {
            newForm = new Form((String)h.get("title"));
            newForm.setScrollable(false);
            WebBrowser c = new WebBrowser();
            String s = (String)h.get("description");
            s = "<html><body>" + s + "</body></html>";
            c.setPage(s, null);
            newForm.setLayout(new BorderLayout());
            newForm.addComponent(BorderLayout.CENTER, c);
        }
        if(addBackToTaget) {
            final Form sourceForm = Display.getInstance().getCurrent();
            Command back = new BackCommand(sourceForm);
            newForm.addCommand(back);
            newForm.setBackCommand(back);
        }
        newForm.show();
    }

    /**
     * Places a limit on the number of RSS entries requested from the server
     * @return the limit
     */
    public int getLimit() {
        return limit;
    }

    /**
     * Places a limit on the number of RSS entries requested from the server
     * 
     * @param limit the limit to set
     */
    public void setLimit(int limit) {
        this.limit = limit;
    }

    /**
     * @inheritDoc
     */
    public String[] getPropertyNames() {
        return new String[] {"limit", "url", "blockList", "progressTitle", "displayProgressPercentage", "target"};
    }

    /**
     * @inheritDoc
     */
    public Class[] getPropertyTypes() {
       return new Class[] {Integer.class, String.class, Boolean.class, String.class, Boolean.class, Container.class};
    }

    /**
     * @inheritDoc
     */
    public Object getPropertyValue(String name) {
        if(name.equals("limit")) {
            return new Integer(limit);
        }
        if(name.equals("url")) {
            return url;
        }
        if(name.equals("blockList")) {
            if(blockList) {
                return Boolean.TRUE;
            }
            return Boolean.FALSE;
        }
        if(name.equals("progressTitle")) {
            return progressTitle;
        }
        if(name.equals("displayProgressPercentage")) {
            if(displayProgressPercentage) {
                return Boolean.TRUE;
            }
            return Boolean.FALSE;
        }
        if(name.equals("target")) {
            return targetContainer;
        }
        if(name.equals("$designMode")) {
            if(designMode) {
                return Boolean.TRUE;
            }
            return Boolean.FALSE;
        }
        
        return null;
    }

    /**
     * @inheritDoc
     */
    public String setPropertyValue(String name, Object value) {
        if(name.equals("limit")) {
            limit = ((Integer)value).intValue();
            return null;
        }
        if(name.equals("url")) {
            url = (String)value;
            return null;
        }
        if(name.equals("blockList")) {
            blockList = ((Boolean)value).booleanValue();
            return null;
        }
        if(name.equals("progressTitle")) {
            progressTitle = (String)value;
            return null;
        }
        if(name.equals("displayProgressPercentage")) {
            displayProgressPercentage = ((Boolean)value).booleanValue();
            return null;
        }
        if(name.equals("target")) {
            targetContainer = (Container)value;
            return null;
        }
        if(name.equals("$designMode")) {
            designMode = ((Boolean)value).booleanValue();
            return null;
        }
        return super.setPropertyValue(name, value);
    }

    /**
     * @return the blockList
     */
    public boolean isBlockList() {
        return blockList;
    }

    /**
     * @param blockList the blockList to set
     */
    public void setBlockList(boolean blockList) {
        this.blockList = blockList;
    }

    /**
     * @return the progressTitle
     */
    public String getProgressTitle() {
        return progressTitle;
    }

    /**
     * @param progressTitle the progressTitle to set
     */
    public void setProgressTitle(String progressTitle) {
        this.progressTitle = progressTitle;
    }

    /**
     * The form/container to which the RSS will navigate when clicking a RSS
     * entry
     * @return the targetContainer
     */
    public Container getTargetContainer() {
        return targetContainer;
    }

    /**
     * The form/container to which the RSS will navigate when clicking a RSS
     * entry
     * @param targetContainer the targetContainer to set
     */
    public void setTargetContainer(Container targetContainer) {
        this.targetContainer = targetContainer;
    }

    /**
     * Indicates whether a back command should be added implicitly to the target container
     * @return the addBackToTaget
     */
    public boolean isAddBackToTaget() {
        return addBackToTaget;
    }

    /**
     * Indicates whether a back command should be added implicitly to the target container
     * @param addBackToTaget the addBackToTaget to set
     */
    public void setAddBackToTaget(boolean addBackToTaget) {
        this.addBackToTaget = addBackToTaget;
    }


    class EventHandler implements ActionListener {
        private void downloadImage(Hashtable h, int offset) {
            if(iconPlaceholder != null) { 
                String url = (String)h.get("thumb");
                if(url != null) {
                    ImageDownloadService.createImageToStorage(url, RSSReader.this, getModel(), offset, "icon", url.replace('/', '_').replace(':', '_'), iconPlaceholder, ConnectionRequest.PRIORITY_REDUNDANT);
                }
            }
        }
        public void actionPerformed(ActionEvent evt) {
            if(evt instanceof NetworkEvent) {
                waitingForResponseLock = false;
                NetworkEvent e = (NetworkEvent)evt;
                Vector v = (Vector)e.getMetaData();
                RSSService s = (RSSService)e.getConnectionRequest();

                if(existingData != null) {
                    existingData.removeElement(MORE);
                    for(int iter = 0 ; iter < v.size() ; iter++) {
                        Hashtable h = (Hashtable)v.elementAt(iter);
                        existingData.addElement(h);
                    }
                } else {
                    existingData = v;
                }

                if(s.hasMore()) {
                    v.addElement(MORE);
                }

                setModel(new DefaultListModel(existingData));

                for(int iter = 0 ; iter < existingData.size() ; iter++) {
                    Hashtable h = (Hashtable)existingData.elementAt(iter);
                    Object icn = h.get("icon");
                    if(icn != null && icn == iconPlaceholder) {
                        downloadImage(h, iter);
                    }
                }
                return;
            }

            Hashtable sel = (Hashtable)getSelectedItem();
            if(sel == MORE) {
                if(waitingForResponseLock) {
                    return;
                }
                waitingForResponseLock = true;
                service = new RSSService(url, limit, existingData.size() - 1);
                if(iconPlaceholder != null) {
                    service.setIconPlaceholder(iconPlaceholder);
                }
                service.addResponseListener(new EventHandler());
                Progress p = new Progress("Fetching RSS", service, displayProgressPercentage);
                p.setAutoShow(true);
                p.setDisposeOnCompletion(true);
                NetworkManager.getInstance().addToQueue(service);
            } else {
                showRSSEntry(sel);
            }
        }
    }
}
