/*
 * TV-Browser
 * Copyright (C) 04-2003 Martin Oberhauser (martin@tvbrowser.org)
 *
 * 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 2
 * 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * CVS information:
 *     $Date: 2012-12-22 10:51:30 +0100 (Sa, 22 Dez 2012) $
 *   $Author: ds10 $
 * $Revision: 7751 $
 */
package tvbrowser.extras.favoritesplugin.dlgs;

import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map.Entry;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JOptionPane;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;

import org.apache.commons.lang3.StringUtils;

import tvbrowser.extras.common.ReminderConfiguration;
import tvbrowser.extras.favoritesplugin.FavoritesPlugin;
import tvbrowser.extras.favoritesplugin.FavoritesPluginProxy;
import tvbrowser.extras.favoritesplugin.core.Favorite;
import tvbrowser.extras.reminderplugin.ReminderPlugin;
import tvbrowser.ui.mainframe.MainFrame;
import util.ui.Localizer;
import util.ui.TVBrowserIcons;
import util.ui.UiUtilities;
import devplugin.Channel;
import devplugin.ContextMenuIf;
import devplugin.Date;
import devplugin.NodeFormatter;
import devplugin.PluginTreeNode;
import devplugin.Program;
import devplugin.ProgramFieldType;
import devplugin.ProgramItem;
import devplugin.ProgramReceiveTarget;

/**
 * The model for the favorite tree.
 *
 * @author René Mach
 * @since 2.6
 */
public class FavoriteTreeModel extends DefaultTreeModel {
  private static final Localizer mLocalizer = Localizer.getLocalizerFor(FavoriteTreeModel.class);

  private static FavoriteTreeModel mInstance;

  /**
   * holds all programs which are contained in multiple favorites<br>
   * initialized lazyly
   */
  private ArrayList<Program> mMultiples = null;

  /**
   * Creates an instance of this class.
   *
   * @param root The root node for this model.
   */
  private FavoriteTreeModel(TreeNode root) {
    super(root, true);
  }

  public static FavoriteTreeModel initInstance(Favorite[] favoriteArr) {
    FavoriteNode rootNode = new FavoriteNode("");
    fixRootNode(rootNode);
    for(Favorite fav : favoriteArr) {
      rootNode.add(fav);
    }

    mInstance = new FavoriteTreeModel(rootNode);
    return mInstance;
  }

  public static FavoriteTreeModel initInstance(ObjectInputStream in, int version) throws IOException, ClassNotFoundException {
    FavoriteNode rootNode = new FavoriteNode(in, version);
    fixRootNode(rootNode);
    mInstance = new FavoriteTreeModel(rootNode);
    return mInstance;
  }

  /**
   * change the label of the root node after it has been red from disk
   * @param rootNode
   */
  private static void fixRootNode(final FavoriteNode rootNode) {
    String rootLabel = mLocalizer.msg("rootLabel", "All favorites");
    if (StringUtils.isEmpty(rootLabel)) {
      rootLabel = "FAVORITES_ROOT";
    }
    rootNode.setUserObject(rootLabel);
  }

  public static FavoriteTreeModel getInstance() {
    if (mInstance == null) {
      mInstance = initInstance(new Favorite[0]);
    }

    return mInstance;
  }

  public void reload(TreeNode node) {
    super.reload(node);
    @SuppressWarnings("unchecked")
    Enumeration<FavoriteNode> e = node.children();

    while(e.hasMoreElements()) {
      FavoriteNode child = e.nextElement();

      if(child.isDirectoryNode()) {
        reload(child);
      }
    }
  }

  public void reload(FavoriteTree tree, TreeNode node) {
    super.reload(node);
    @SuppressWarnings("unchecked")
    Enumeration<FavoriteNode> e = node.children();

    while(e.hasMoreElements()) {
      FavoriteNode child = e.nextElement();

      if(child.isDirectoryNode()) {
        reload(tree, child);
      }
    }

    FavoriteNode parent = (FavoriteNode)node;

    if(parent.wasExpanded()) {
      tree.expandPath(new TreePath((tree.getModel()).getPathToRoot(node)));
    } else {
      tree.collapsePath(new TreePath((tree.getModel()).getPathToRoot(node)));
    }
  }


  public void reload() {
    reload(root);
  }

  public boolean isLeaf(Object nodeObject) {
    if (nodeObject instanceof FavoriteNode) {
      FavoriteNode node = (FavoriteNode) nodeObject;
      return node.getChildCount() == 0;
    }
    return super.isLeaf(nodeObject);
  }

  /**
   * Gets all favorites in an array.
   *
   * @return All favorites in an array.
   */
  public Favorite[] getFavoriteArr() {
    ArrayList<Favorite> favoriteList = new ArrayList<Favorite>();

    fillFavoriteList((FavoriteNode) getRoot(), favoriteList);

    return favoriteList.toArray(new Favorite[favoriteList.size()]);
  }

  private void fillFavoriteList(FavoriteNode node, ArrayList<Favorite> favoriteList) {
    if(node.isDirectoryNode()) {
      @SuppressWarnings("unchecked")
      Enumeration<FavoriteNode> e = node.children();

      while(e.hasMoreElements()) {
        FavoriteNode child = e.nextElement();

        if(child.isDirectoryNode()) {
          fillFavoriteList(child, favoriteList);
        } else if(child.containsFavorite()) {
          favoriteList.add(child.getFavorite());
        }
      }
    }
  }

  /**
   * Deletes a favorite.
   *
   * @param favorite The favorite to delete.
   */
  public void deleteFavorite(Favorite favorite) {
    Program[] delFavPrograms = favorite.getPrograms();
    for (Program program : delFavPrograms) {
      program.unmark(FavoritesPluginProxy.getInstance());
    }
    
    deleteFavorite((FavoriteNode) getRoot(), favorite);

    String[] reminderServices = favorite.getReminderConfiguration().getReminderServices();

    for (String reminderService : reminderServices) {
      if (ReminderConfiguration.REMINDER_DEFAULT.equals(reminderService)) {
        ReminderPlugin.getInstance().removePrograms(favorite.getPrograms());
      }
    }

    FavoritesPlugin.getInstance().updateRootNode(true);
  }

  /**
   * Check if a program is marked by other Favorites to.
   *
   * @param favorite The Favorite that wants to check this.
   * @param p The program to check.
   * @return True if the program was found in other Favorites than the given one.
   */
  public boolean isContainedByOtherFavorites(Favorite favorite, Program p) {
    return isContainedByOtherFavorites((FavoriteNode) getRoot(),favorite,p);
  }

  private boolean isContainedByOtherFavorites(FavoriteNode node,Favorite favorite, Program p) {
    boolean value = false;

    if(node.isDirectoryNode()) {
      @SuppressWarnings("unchecked")
      Enumeration<FavoriteNode> e = node.children();

      while(e.hasMoreElements()) {
        FavoriteNode child = e.nextElement();

        if(child.isDirectoryNode()) {
          value = value || isContainedByOtherFavorites(child, favorite, p);
        } else if(child.containsFavorite()) {
          if(!child.equals(favorite)) {
            value = value || child.getFavorite().contains(p);
          }
        }
      }
    }

    return value;
  }

  private void deleteFavorite(FavoriteNode node, Favorite fav) {
    if(node.isDirectoryNode()) {
      @SuppressWarnings("unchecked")
      Enumeration<FavoriteNode> e = node.children();

      while(e.hasMoreElements()) {
        FavoriteNode child = e.nextElement();

        if(child.isDirectoryNode()) {
          deleteFavorite(child, fav);
        } else if(child.containsFavorite()) {
          if(child.equals(fav)) {
            node.remove(child);
            
            Program[] progs = fav.getPrograms();
            
            for(Program prog : progs) {
              prog.validateMarking();
            }
          }
          else {
            child.getFavorite().handleContainingPrograms(fav.getPrograms());
          }
        }
      }
    }

  }

  /**
   * Adds a favorite to this tree at the root node.
   *
   * @param fav The favorite to add.
   */
  public void addFavorite(Favorite fav) {
    addFavorite(fav, (FavoriteNode) getRoot());
  }

  /**
   * Adds a favorite to this tree at the given target node.
   *
   * @param fav
   *          The favorite to add.
   * @param parent
   *          The parent node to add the favorite to or <code>null</code> if the
   *          root node should be used.
   * @return the newly created node for the favorite
   */
  public FavoriteNode addFavorite(Favorite fav, FavoriteNode parent) {
    if (parent == null) {
      parent = (FavoriteNode) getRoot();
    }
    FavoriteNode newNode = parent.add(fav);
    reload(parent);
    FavoritesPlugin.getInstance().updateRootNode(true);
    
    Program[] progs = newNode.getAllPrograms(false);
    
    for(Program prog : progs) {
      prog.validateMarking();
    }
    
    return newNode;
  }

  public static String getFavoriteLabel(Favorite favorite, Program program) {
    return getFavoriteLabel(favorite, program, null);
  }

  public static String getFavoriteLabel(Favorite favorite, Program p, Channel currentChannel) {
    Date d = p.getDate();
    String progdate;

    Date currentDate = Date.getCurrentDate();

    if (d.equals(currentDate.addDays(-1))) {
      progdate = Localizer.getLocalization(Localizer.I18N_YESTERDAY);
    } else if (d.equals(currentDate)) {
      progdate = Localizer.getLocalization(Localizer.I18N_TODAY);
    } else if (d.equals(currentDate.addDays(1))) {
      progdate = Localizer.getLocalization(Localizer.I18N_TOMORROW);
    } else {
      progdate = p.getDateString();
    }

    String description = progdate + "  " + p.getTimeString();
    if(favorite.getName().compareTo(p.getTitle()) != 0) {
      description = description + "  " + p.getTitle();
    }
    String episode = p.getTextField(ProgramFieldType.EPISODE_TYPE);
    if (StringUtils.isNotBlank(episode)) {
      if (episode.length()<=3) {
        episode = ProgramFieldType.EPISODE_TYPE.getLocalizedName() + " " + episode;
      }
      description = description + ": " + episode ;
    }
    if (null == currentChannel || currentChannel != p.getChannel()) {
      description = description + "  (" + p.getChannel() + ")";
    }
    return description;
  }

  /**
   * Saves the data of this tree into the given stream.
   *
   * @param out The stream to write the data to.
   * @throws IOException Thrown if something went wrong
   */
  public void storeData(ObjectOutputStream out) throws IOException {
    ((FavoriteNode)getRoot()).store(out);
  }

  public void updatePluginTree(final PluginTreeNode node, final ArrayList<Program> allPrograms, FavoriteNode parentFavorite) {
    if(parentFavorite == null) {
      parentFavorite = (FavoriteNode) getRoot();
    }

    if(parentFavorite.isDirectoryNode()) {
      @SuppressWarnings("unchecked")
      Enumeration<FavoriteNode> e = parentFavorite.children();

      while(e.hasMoreElements()) {
        final FavoriteNode child = e.nextElement();

        if(child.isDirectoryNode()) {
          PluginTreeNode newNode = new PluginTreeNode(child.toString());
          newNode.setGroupingByWeekEnabled(true);

          updatePluginTree(newNode, allPrograms, child);
          if (!newNode.isEmpty()) {
            node.add(newNode);
          }
        } else {
          Program[] progArr = child.getFavorite().getWhiteListPrograms();
          if (progArr.length > 0) {
            PluginTreeNode newNode = new PluginTreeNode(child.toString());
            newNode.setGroupingByWeekEnabled(true);
            newNode.getMutableTreeNode().setIcon(FavoritesPlugin.getFavoritesIcon(16));
            node.add(newNode);
            Action editFavorite = new AbstractAction() {
              public void actionPerformed(ActionEvent e) {
                FavoritesPlugin.getInstance().editFavorite(child.getFavorite());
              }
            };
            editFavorite.putValue(Action.NAME, mLocalizer.ellipsisMsg("editTree","Edit"));
            editFavorite.putValue(Action.SMALL_ICON, TVBrowserIcons.edit(TVBrowserIcons.SIZE_SMALL));

            Action deleteFavorite = new AbstractAction() {
              public void actionPerformed(ActionEvent e) {
                FavoritesPlugin.getInstance().askAndDeleteFavorite(child.getFavorite());
              }
            };
            deleteFavorite.putValue(Action.NAME, mLocalizer.ellipsisMsg("deleteTree","Delete"));
            deleteFavorite.putValue(Action.SMALL_ICON, TVBrowserIcons.delete(TVBrowserIcons.SIZE_SMALL));
            deleteFavorite.putValue(ContextMenuIf.ACTIONKEY_KEYBOARD_EVENT,
                KeyEvent.VK_DELETE);

            newNode.addAction(editFavorite);
            newNode.addAction(deleteFavorite);


            if(progArr.length <= 10) {
              newNode.setGroupingByDateEnabled(false);
            }
            boolean episodeOnly = progArr.length > 1;
            for (Program program : progArr) {
              String episode = program.getTextField(ProgramFieldType.EPISODE_TYPE);
              if (StringUtils.isBlank(episode)) {
                episodeOnly = false;
                break;
              }
            }

            for (Program program : progArr) {
              PluginTreeNode pNode = newNode.addProgramWithoutCheck(program);
              allPrograms.add(program);
              if (episodeOnly || progArr.length <= 10) {
                pNode.setNodeFormatter(new NodeFormatter() {
                  public String format(ProgramItem pitem) {
                    Program p = pitem.getProgram();
                    return FavoriteTreeModel.getFavoriteLabel(child.getFavorite(), p);
                  }
                });
              }
            }
          }
        }
      }
    }
  }

   /** Calculates the number of programs contained in the children
   *
   * @param node
   *          use this Node
   * @return Number of Child-Nodes
   */
  public static int[] getProgramsCount(FavoriteNode node) {
    int[] count = new int[2];

    Date currentDate = Date.getCurrentDate();
    if(node.containsFavorite()) {
      Program[] whiteListPrograms = node.getFavorite().getWhiteListPrograms();
      count[0] = whiteListPrograms.length;
      for(Program p : whiteListPrograms) {
        if(p.getDate().equals(currentDate) && !p.isExpired()) {
          count[1]++;
        }
      }
    }

    for (int i = 0; i < node.getChildCount(); i++) {
      FavoriteNode child = (FavoriteNode)node.getChildAt(i);
      if (child.containsFavorite()) {
        Program[] whiteListPrograms = child.getFavorite().getWhiteListPrograms();
        count[0] += whiteListPrograms.length;

        for(Program p : whiteListPrograms) {
          if(p.getDate().equals(currentDate) && !p.isExpired()) {
            count[1]++;
          }
        }
      } else {
        int[] countReturned = getProgramsCount(child);
        count[0] += countReturned[0];
        count[1] += countReturned[1];
      }
    }
    return count;
  }

  /**
   * Sorts the path from the given node to all leafs alphabetically.
   *
   * @param node The node to sort from.
   * @param comp Comparator for sorting
   * @param title Title of confirmation message dialog
   */
  public void sort(FavoriteNode node, Comparator<FavoriteNode> comp, String title) {
    String msg = mLocalizer.msg("reallySort",
        "Do you really want to sort '{0}'?\n\nThe current order will get lost.", node.toString());
    int result = JOptionPane.showConfirmDialog(UiUtilities
        .getLastModalChildOf(MainFrame.getInstance()), msg, title,
        JOptionPane.YES_NO_OPTION);
    if (result == JOptionPane.YES_OPTION) {
      sortNodeInternal(node, comp);
    }

    FavoritesPlugin.getInstance().favoriteSelectionChanged();
  }

  /**
   * sort favorite nodes (dialog handling must be done by caller)
   * @param node
   * @param comp
   */
  private void sortNodeInternal(FavoriteNode node,
      Comparator<FavoriteNode> comp) {
    ArrayList<FavoriteNode> childNodes = Collections.list(node.children());
    Collections.sort(childNodes, comp);

    node.removeAllChildren();

    for(FavoriteNode child : childNodes) {
      node.add(child);
      if(child.isDirectoryNode()) {
        sortNodeInternal(child, comp);
      }
    }
  }

  /**
   * Gets the Favorites containing the given receive target in an array.
   *
   * @param target The target to check.
   * @return The Favorites that contains the given receive target in an array.
   */
  public Favorite[] getFavoritesContainingReceiveTarget(ProgramReceiveTarget target) {
    Favorite[] favorites = getFavoriteArr();

    ProgramReceiveTarget[] defaultTargets = FavoritesPlugin.getInstance().getDefaultClientPluginsTargets();

    for(ProgramReceiveTarget defaultTarget : defaultTargets) {
      if(defaultTarget.equals(target)) {
        return favorites;
      }
    }

    ArrayList<Favorite> receiveFavorites = new ArrayList<Favorite>();

    for(Favorite fav : favorites) {
      if(fav.containsReceiveTarget(target)) {
        receiveFavorites.add(fav);
      }
    }

    return receiveFavorites.toArray(new Favorite[receiveFavorites.size()]);
  }

  public void updatePluginTree(final PluginTreeNode topicNode, final ArrayList<Program> allPrograms) {
    updatePluginTree(topicNode, allPrograms, null);
  }

  /**
   * get an array of all favorites containing the given program
   * @param program program to search for
   * @return array of favorites
   * @since 2.7
   */
  public Favorite[] getFavoritesContainingProgram(Program program) {
    ArrayList<Favorite> containing = new ArrayList<Favorite>();

    for (Favorite favorite : getFavoriteArr()) {
      for (Program favProgram : favorite.getWhiteListPrograms()) {
        if (favProgram.equals(program)) {
          containing.add(favorite);
          break;
        }
      }
    }
    return containing.toArray(new Favorite[containing.size()]);
  }

  public boolean isInMultipleFavorites(final Program program) {
    if (mMultiples == null) {
      HashMap<Program, Integer> map = new HashMap<Program, Integer>(2000);
      for (Favorite favorite : getFavoriteArr()) {
        for (Program favProgram : favorite.getPrograms()) {
          Integer count = map.get(favProgram);
          if (count == null) {
            count = 0;
          }
          count++;
          map.put(favProgram, count);
        }
      }
      mMultiples = new ArrayList<Program>();
      for (Entry<Program, Integer> entry : map.entrySet()) {
        if (entry.getValue().intValue() > 1) {
          mMultiples.add(entry.getKey());
        }
      }
    }
    for (Program dupProgram : mMultiples) {
      if (dupProgram.equals(program)) {
        return true;
      }
    }
    return false;
  }

  public void resetMultiplesCounter() {
    mMultiples = null;
  }
  
  public void reValidateChannelLimitations() {
    
  }
}
