/*
GanttProject is an opensource project management tool. License: GPL3
Copyright (C) 2011 Dmitry Barashev

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 3
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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package net.sourceforge.ganttproject.client;

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;

import net.sourceforge.ganttproject.GPVersion;
import net.sourceforge.ganttproject.gui.NotificationChannel;
import net.sourceforge.ganttproject.gui.NotificationItem;
import net.sourceforge.ganttproject.gui.NotificationManager;
import net.sourceforge.ganttproject.gui.UIFacade;
import net.sourceforge.ganttproject.language.GanttLanguage;

import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;

import biz.ganttproject.core.option.ChangeValueEvent;
import biz.ganttproject.core.option.ChangeValueListener;
import biz.ganttproject.core.option.DateOption;
import biz.ganttproject.core.option.DefaultBooleanOption;
import biz.ganttproject.core.option.DefaultDateOption;
import biz.ganttproject.core.option.DefaultEnumerationOption;
import biz.ganttproject.core.option.GPOptionGroup;
import biz.ganttproject.core.time.impl.GPTimeUnitStack;

/**
 * Checks GanttProject RSS news feeds once per day
 *
 * @author dbarashev (Dmitry Barashev)
 */
public class RssFeedChecker {
  private static enum CheckOption {
    YES, NO, UNDEFINED
  }

  private final UIFacade myUiFacade;
  private final DefaultEnumerationOption<CheckOption> myCheckRssOption = new DefaultEnumerationOption<CheckOption>(
      "check", CheckOption.values()) {
    @Override
    protected String objectToString(CheckOption obj) {
      return obj.toString();
    }

    @Override
    protected CheckOption stringToObject(String value) {
      return CheckOption.valueOf(value);
    }
  };
  private final DefaultBooleanOption myBooleanCheckRssOption = new DefaultBooleanOption("rss.checkUpdates");
  private final DateOption myLastCheckOption = new DefaultDateOption("lastCheck", null);
  private final GPOptionGroup myOptionGroup = new GPOptionGroup("updateRss",
      myCheckRssOption, myLastCheckOption);
  private final GPOptionGroup myUiOptionGroup = new GPOptionGroup("rss", myBooleanCheckRssOption);
  private GPTimeUnitStack myTimeUnitStack;
  private static final String RSS_URL = "http://www.ganttproject.biz/my/feed";
  protected static final int MAX_ATTEMPTS = 10;
  private final RssParser parser = new RssParser();
  private final NotificationItem myRssProposalNotification = new NotificationItem("",
      GanttLanguage.getInstance().formatText("updateRss.question.template",
          GanttLanguage.getInstance().getText("updateRss.question.0"),
          GanttLanguage.getInstance().getText("updateRss.question.1"),
          GanttLanguage.getInstance().getText("updateRss.question.2")),
          NotificationManager.DEFAULT_HYPERLINK_LISTENER);
  private String myOptionsVersion;

  public RssFeedChecker(GPTimeUnitStack timeUnitStack, UIFacade uiFacade) {
    myCheckRssOption.setValue(CheckOption.UNDEFINED.toString());
    myUiFacade = uiFacade;
    myTimeUnitStack = timeUnitStack;
    myBooleanCheckRssOption.setValue(CheckOption.YES.equals(myCheckRssOption.getSelectedValue()));
    myBooleanCheckRssOption.addChangeValueListener(new ChangeValueListener() {
      @Override
      public void changeValue(ChangeValueEvent event) {
        if (event.getTriggerID() != RssFeedChecker.this) {
          if (myBooleanCheckRssOption.isChecked()) {
            myCheckRssOption.setValue(CheckOption.YES.name(), RssFeedChecker.this);
          } else {
            myCheckRssOption.setValue(CheckOption.NO.name(), RssFeedChecker.this);
          }
        }
      }
    });
    myCheckRssOption.addChangeValueListener(new ChangeValueListener() {
      @Override
      public void changeValue(ChangeValueEvent event) {
        if (event.getID() != RssFeedChecker.this && CheckOption.UNDEFINED != myCheckRssOption.getSelectedValue()) {
          myBooleanCheckRssOption.setValue(CheckOption.YES == myCheckRssOption.getSelectedValue(), RssFeedChecker.this);
        }
      }
    });
  }

  private NotificationManager getNotificationManager() {
    return myUiFacade.getNotificationManager();
  }

  public GPOptionGroup getOptions() {
    return myOptionGroup;
  }

  public GPOptionGroup getUiOptions() {
    return myUiOptionGroup;
  }

  public void run() {
    Runnable command = null;
    CheckOption checkOption = CheckOption.valueOf(myCheckRssOption.getValue());
    if (CheckOption.NO == checkOption) {
      if (myOptionsVersion == null) {
        // We used opt-in before GP 2.7; now we use opt-out, and we suggest to
        // subscribe once again to those who previously chosen not to.
        checkOption = CheckOption.UNDEFINED;
        myCheckRssOption.setSelectedValue(checkOption);
        markLastCheck();
      } else {
        NotificationChannel.RSS.setDefaultNotification(myRssProposalNotification);
      }
      return;
    }
    Date lastCheck = myLastCheckOption.getValue();
    if (lastCheck == null) {
      // It is the first time we run, just mark it. We want to suggest
      // subscribing to updates only to
      // those who runs GP at least twice.
      markLastCheck();
    } else if (wasToday(lastCheck)) {
      // It is not the first run of GP but it was last run today and RSS
      // proposal has not been shown yet.
      // Add it to RSS button but don't promote it, wait until tomorrow.
      if (CheckOption.UNDEFINED == checkOption) {
        NotificationChannel.RSS.setDefaultNotification(myRssProposalNotification);
      }
    } else {
      // So it is not the first time and even not the first day we start GP.
      // If no decision about subscribing, let's proactively suggest it,
      // otherwise
      // run check RSS.
      if (CheckOption.UNDEFINED == checkOption) {
        command = createRssProposalCommand();
      } else {
        command = createRssReadCommand();
      }
    }
    if (command == null) {
      return;
    }
    new Thread(command).start();
  }

  private Runnable createRssReadCommand() {
    return new Runnable() {
      @Override
      public void run() {
        HttpClient httpClient = new DefaultHttpClient();
        String url = RSS_URL;
        try {
          for (int i = 0; i < MAX_ATTEMPTS; i++) {
            HttpGet getRssUrl = new HttpGet(url);
            getRssUrl.addHeader("User-Agent", "GanttProject " + GPVersion.CURRENT);
            HttpResponse result = httpClient.execute(getRssUrl);

            switch (result.getStatusLine().getStatusCode()) {
            case HttpStatus.SC_OK:
              processResponse(result.getEntity().getContent());
              return;
            }
          }
        } catch (MalformedURLException e) {
          e.printStackTrace();
        } catch (IOException e) {
          e.printStackTrace();
        } finally {
          httpClient.getConnectionManager().shutdown();
        }
      }

      private void processResponse(InputStream responseStream) {
        RssFeed feed = parser.parse(responseStream, myLastCheckOption.getValue());
        List<NotificationItem> items = new ArrayList<NotificationItem>();
        for (RssFeed.Item item : feed.getItems()) {
          items.add(new NotificationItem(item.title, item.body, NotificationManager.DEFAULT_HYPERLINK_LISTENER));
        }
        Collections.reverse(items);
        if (!items.isEmpty()) {
          getNotificationManager().addNotifications(NotificationChannel.RSS, items);
        }
        markLastCheck();
      }
    };
  }

  private Runnable createRssProposalCommand() {
    return new Runnable() {
      @Override
      public void run() {
        onYes();
        getNotificationManager().addNotifications(NotificationChannel.RSS,
            Collections.singletonList(myRssProposalNotification));
      }
    };
  }

  private boolean wasToday(Date date) {
    return myTimeUnitStack.createDuration(GPTimeUnitStack.DAY, date, GPTimeUnitStack.DAY.adjustLeft(new Date())).getLength() == 0;
  }

  private void onYes() {
    myCheckRssOption.setValue(CheckOption.YES.toString());
  }

  private void markLastCheck() {
    myLastCheckOption.setValue(new Date());
  }

  public void setOptionsVersion(String version) {
    myOptionsVersion = version;
  }
}
