/*
Copyright (C) 2004-2008 Christian Wieninger

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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Or, point your browser to http://www.gnu.org/licenses/old-licenses/gpl-2.0.html

The author can be reached at cwieninger@gmx.de

The project's page is at http://winni.vdr-developer.org/epgsearch
*/

#include <sstream>
#include <iomanip>
#include <fstream>

#include "mail.h"
#include "epgsearchcfg.h"
#include "log.h"
#include "epgsearchtools.h"
#include "uservars.h"

using namespace std;

string cMailNotifier::MailCmd = "sendEmail.pl";

// ----------------------
// cMailTimerNotification
string cMailTimerNotification::Format(const string& templ) const
{
    const cEvent* pEvent = GetEvent();
    if (!pEvent) return "";

    int TimerMatch = tmNone;
    cTimer* pTimer = Timers.GetMatch(pEvent, &TimerMatch);
    if (!pTimer) return "";

    string result = templ;
    cVarExpr varExprEvent(result);
    result =  varExprEvent.Evaluate(pEvent);
    cVarExpr varExprTimer(result);
    result =  varExprTimer.Evaluate(pTimer);
    
    if (timerMod == tmStartStop)
      result = ReplaceAll(result, "%timer.modreason%", tr("Start/Stop time has changed"));
    if (timerMod == tmFile)
      result = ReplaceAll(result, "%timer.modreason%", tr("Title/episode has changed"));
    else
      result = ReplaceAll(result, "%timer.modreason%", "");
    return result;
}

const cEvent* cMailTimerNotification::GetEvent() const
{
    cSchedulesLock schedulesLock;
    const cSchedules *schedules = cSchedules::Schedules(schedulesLock);
    if (!schedules) return NULL;
    const cSchedule *schedule = schedules->GetSchedule(channelID);
    if (!schedule) return NULL;
    return schedule->GetEvent(eventID);
}

bool cMailTimerNotification::operator< (const cMailTimerNotification &N) const
{
    const cChannel* channel = Channels.GetByChannelID(channelID,true,true);
    const cChannel* channelOther = Channels.GetByChannelID(N.channelID,true,true);
    if (!channel || !channelOther)
	return false;
    const cEvent* event = GetEvent();
    const cEvent* eventOther = N.GetEvent();
    if (event && eventOther) // sort event by start time and channel
      {
	if (event->StartTime() == eventOther->StartTime())
	    return channel->Number() < channelOther->Number();
	else
	    return event->StartTime() < eventOther->StartTime();
      }
    return false;
}

// -------------------------
// cMailDelTimerNotification
cMailDelTimerNotification::cMailDelTimerNotification(cTimer* pTimer, const cEvent* pEvent, const string& templ)
{
    if (!pTimer || !pTimer->Channel()) return;

    channelID = pTimer->Channel()->GetChannelID();
    start = pTimer->StartTime();

    string result = templ;
    cVarExpr varExprEvent(result);
    result =  varExprEvent.Evaluate(pEvent);
    cVarExpr varExprTimer(result);
    formatted =  varExprTimer.Evaluate(pTimer);
}

bool cMailDelTimerNotification::operator< (const cMailDelTimerNotification &N) const
{
    const cChannel* channel = Channels.GetByChannelID(channelID,true,true);
    const cChannel* channelOther = Channels.GetByChannelID(N.channelID,true,true);
    if (!channel || !channelOther)
	return false;
    if (channel != channelOther)
	return channel->Number() < channelOther->Number();
    else
	return (start < N.start);
}

// -------------
// cMailNotifier
cMailNotifier::cMailNotifier(string Subject, string Body)
: subject(Subject), body(Body) 
{
    SendMail();
}

bool cMailNotifier::SendMailViaSendmail() 
{
    char mailcmd[256];
    const char* mailargs = "%s -i -FVDR -oem  %s";
    const char* mailproc = "/usr/lib/sendmail";
    FILE* mail;
    
    string to = EPGSearchConfig.MailAddressTo; 
    snprintf(mailcmd, sizeof(mailcmd), mailargs, mailproc, to.c_str());
    
    if (!(mail = popen(mailcmd, "w"))) {
	return false;
    }
    
    fprintf(mail, "From: VDR\n");
    fprintf(mail, "To: %s\n", to.c_str());
    fprintf(mail, "Subject: %s\n", subject.c_str());
    if (FindIgnoreCase(body, "<html>") >= 0)
	fprintf(mail, "Content-Type: text/html; charset=ISO-8859-15\n");
    else
	fprintf(mail, "Content-Type: text/plain; charset=ISO-8859-15\n");

    fprintf(mail, "\n");
    
    fprintf(mail, "%s", body.c_str());
    
    pclose(mail);
    
    return true;
}

bool cMailNotifier::SendMailViaScript()
{
    // create a temporary file for the message body
    string filename = *AddDirectory(CONFIGDIR, "epgsearchmail.temp");
    std::ofstream bodyfile(filename.c_str());
    if (!bodyfile)
    {
	LogFile.eSysLog("error opening file %s", filename.c_str());
	return false;
    }
    bodyfile << body;
    bodyfile.close();

    string AuthUser = EPGSearchConfig.MailAuthUser;
    string AuthPass = EPGSearchConfig.MailAuthPass;
    string cmdArgs =  
	string(" -f \"VDR <") + EPGSearchConfig.MailAddress + ">\"" +
	" -t " + EPGSearchConfig.MailAddressTo +
	" -s " + EPGSearchConfig.MailServer +
	" -u \"" + subject + "\""+ 
	(EPGSearchConfig.MailUseAuth?
	 (AuthUser != "" ?(" -xu " + AuthUser):"") +
	 (AuthPass != "" ?(" -xp " + AuthPass):"")
	 :"") +
        " -o message-file=" + filename;

    bool success = ExecuteMailScript(cmdArgs);

    if (remove(filename.c_str()) != 0)
	LogFile.eSysLog("error deleting file %s", filename.c_str());

    return success;
}

bool cMailNotifier::SendMail()
{
    if (!EPGSearchConfig.mailViaScript)
	return SendMailViaSendmail();
    else
	return SendMailViaScript();
}

bool cMailNotifier::ExecuteMailScript(string ScriptArgs)
{
    string mailCmd = MailCmd;
    if (mailCmd == "sendEmail.pl") // beautify output for standard script
	ScriptArgs += " | cut -d\" \" -f 6-";

    cCommand cmd; 
    string fullcmd = "mailcmd: " + mailCmd;
    if (!cmd.Parse(fullcmd.c_str()))
    {
	LogFile.eSysLog("error parsing cmd: %s", MailCmd.c_str());
	return false;
    }
    const char* res = cmd.Execute(ScriptArgs.c_str());
    scriptReply = res?res:"";
    if (scriptReply != "")
	LogFile.iSysLog("mail cmd result: %s", scriptReply.c_str());

    return true;
}

bool cMailNotifier::TestMailAccount(string MailAddressTo, string MailAddress, string MailServer, string AuthUser, string AuthPass)
{
    string cmdArgs =  
	string("-v -f \"VDR <") + MailAddress + ">\"" +
	" -t " + MailAddressTo +
	" -s " + MailServer +
	" -u \"VDR-Testmail\"" + 
	(AuthUser != "" ?(" -xu " + AuthUser):"") +
	(AuthPass != "" ?(" -xp " + AuthPass):"") +
        " -m \"Success! ;-)\"";

    return ExecuteMailScript(cmdArgs);    
}

string cMailNotifier::LoadTemplate(const string& templtype)
{
    string filename = *AddDirectory(CONFIGDIR, templtype.c_str());
    string templ = "";
    if (filename != "" && access(filename.c_str(), F_OK) == 0) 
    {
	LogFile.iSysLog("loading %s", filename.c_str());
	FILE *f = fopen(filename.c_str(), "r");
	if (f)
	{
	    templ = "";
	    char *s;
	    cReadLine ReadLine;
	    while ((s = ReadLine.Read(f)) != NULL)
	    {
		if (strlen(s) > 0 && s[0] == '#')
		    continue;
		templ += string(s) + "\n";
	    }
	    fclose(f);
	}	
    }
    return templ;
}

string cMailNotifier::GetTemplValue(const string& templ, const string& entry)
{
    if (templ == "" || entry == "") return "";
    
    string start = "<" + entry + ">";
    string end = "</" + entry + ">";

    int eBegin = FindIgnoreCase(templ, start);
    int eEnd = FindIgnoreCase(templ, end);
    if (eBegin < 0 || eEnd < 0) return "";
    string value(templ.begin() + eBegin + start.length(), templ.begin() + eEnd);
    return value;
}

// -------------------
// cMailUpdateNotifier
cMailUpdateNotifier::cMailUpdateNotifier()
{
    mailTemplate = LoadTemplate("epgsearchupdmail.templ");
}

void cMailUpdateNotifier::AddNewTimerNotification(tEventID EventID, tChannelID ChannelID)
{
    cMailTimerNotification N(EventID, ChannelID);
    newTimers.insert(N);
}

void cMailUpdateNotifier::AddModTimerNotification(tEventID EventID, tChannelID ChannelID, uint timerMod)
{
  if (timerMod == tmNoChange || timerMod == tmAuxEventID) // if anything but the eventID has changed
    return;
  cMailTimerNotification N(EventID, ChannelID, timerMod);
  modTimers.insert(N);
}

void cMailUpdateNotifier::AddRemoveTimerNotification(cTimer* t, const cEvent* e)
{
    string templTimer = GetTemplValue(mailTemplate, "timer");
    cMailDelTimerNotification N(t, e, templTimer);
    delTimers.insert(N);
}

void cMailUpdateNotifier::SendUpdateNotifications()
{
    if (newTimers.size() == 0 && 
	modTimers.size() == 0 && 
	delTimers.size() == 0)
	return;

    // extract single templates
    if (mailTemplate == "")
    {
	LogFile.eSysLog("error loading %s", *AddDirectory(CONFIGDIR, "epgsearchupdmail.templ"));
	return;
    }
    string templSubject = GetTemplValue(mailTemplate, "subject");
    string templBody = GetTemplValue(mailTemplate, "mailbody");
    string templTimer = GetTemplValue(mailTemplate, "timer");

    // create the timer list for new timers
    string newtimers;
    if (newTimers.size() == 0)
	newtimers = tr("No new timers were added.");
    std::set<cMailTimerNotification>::iterator itnt;
    for (itnt = newTimers.begin(); itnt != newTimers.end(); itnt++) 
    {
	string message = (*itnt).Format(templTimer);
	if (message != "") newtimers += message;
    }

    // create the timer list for modified timers
    string modtimers;
    if (modTimers.size() == 0)
	modtimers = tr("No timers were modified.");
    std::set<cMailTimerNotification>::iterator itmt;
    for (itmt = modTimers.begin(); itmt != modTimers.end(); itmt++) 
    {
	string message = (*itmt).Format(templTimer);
	if (message != "") modtimers += message;
    }

    // create the timer list for removed timers
    string deltimers;
    if (delTimers.size() == 0)
	deltimers = tr("No timers were deleted.");
    std::set<cMailDelTimerNotification>::iterator itdt;
    for (itdt = delTimers.begin(); itdt != delTimers.end(); itdt++) 
    {
	string message = (*itdt).formatted;
	if (message != "") deltimers += message;
    }

    // evaluate variables
    cVarExpr varExprSubj(templSubject);
    subject =  varExprSubj.Evaluate();
    subject = ReplaceAll(subject, "%update.countnewtimers%", NumToString((long)newTimers.size()));
    subject = ReplaceAll(subject, "%update.countmodtimers%", NumToString((long)modTimers.size()));
    subject = ReplaceAll(subject, "%update.countdeltimers%", NumToString((long)delTimers.size()));

    newtimers = ReplaceAll(newtimers, "%update.countnewtimers%", NumToString((long)newTimers.size()));
    modtimers = ReplaceAll(modtimers, "%update.countmodtimers%", NumToString((long)modTimers.size()));
    deltimers = ReplaceAll(deltimers, "%update.countdeltimers%", NumToString((long)delTimers.size()));

    cVarExpr varExprBody(templBody);
    body =  varExprBody.Evaluate();
    body = ReplaceAll(body, "%update.countnewtimers%", NumToString((long)newTimers.size()));
    body = ReplaceAll(body, "%update.countmodtimers%", NumToString((long)modTimers.size()));
    body = ReplaceAll(body, "%update.countdeltimers%", NumToString((long)delTimers.size()));
    body = ReplaceAll(body, "%update.newtimers%", newtimers);
    body = ReplaceAll(body, "%update.modtimers%", modtimers);
    body = ReplaceAll(body, "%update.deltimers%", deltimers);

    SendMail();

    newTimers.clear();
    modTimers.clear();
    delTimers.clear();
}

// ---------------------
// cMailConflictNotifier
void cMailConflictNotifier::SendConflictNotifications(cConflictCheck& conflictCheck)
{
    if (conflictCheck.relevantConflicts == 0)
	return;

    // check if anything has changed since last mail
    ostringstream newMailConflicts;
    cList<cConflictCheckTime>* failedList = conflictCheck.GetFailed();
    if (!failedList) return;

    for(cConflictCheckTime* ct = failedList->First(); ct; ct = failedList->Next(ct))
    {
	if (!ct || ct->ignore) continue;	
	std::set<cConflictCheckTimerObj*,TimerObjSort>::iterator it;
	for (it = ct->failedTimers.begin(); it != ct->failedTimers.end(); it++) 
	    if ((*it) && !(*it)->ignore && (*it)->Event())
	      {
		std::string channelID = *(*it)->Event()->ChannelID().ToString();
		newMailConflicts << (*it)->Event()->EventID() 
				 << "|" 
				 << channelID;
	      }
    }
    string newMailConflictsMD5 = MD5(newMailConflicts.str());
    if (newMailConflictsMD5 == EPGSearchConfig.LastMailConflicts)
    {
	LogFile.Log(3, "conflicts unchanged - no new notification needed.");
	return;
    }


    // open the template
    string templ = LoadTemplate("epgsearchconflmail.templ");
    if (templ == "")
    {
	LogFile.eSysLog("error loading %s", *AddDirectory(CONFIGDIR, "epgsearchconflmail.templ"));
	return;
    }

    // extract single templates
    LogFile.Log(3, "extracting templates");
    string templSubject = GetTemplValue(templ, "subject");
    string templBody = GetTemplValue(templ, "mailbody");
    string templConflictsAt = GetTemplValue(templ, "conflictsat");
    string templConflictTimer = GetTemplValue(templ, "conflicttimer");
    LogFile.Log(3, "extracting templates - done");

    // create the conflict list 
    string conflicts;
    for(cConflictCheckTime* ct = failedList->First(); ct; ct = failedList->Next(ct))
    {
	if (ct->ignore) continue;	
	// format conflict time
	string conflictsAt = templConflictsAt;
	conflictsAt = ReplaceAll(conflictsAt, "%conflict.time%", TIMESTRING(ct->evaltime));
	conflictsAt = ReplaceAll(conflictsAt, "%conflict.date%", DATESTRING(ct->evaltime));

	string conflicttimers;
	std::set<cConflictCheckTimerObj*,TimerObjSort>::iterator it;
	for (it = ct->failedTimers.begin(); it != ct->failedTimers.end(); it++) 
	    if (!(*it)->ignore && (*it)->Event())
	    {
		cMailTimerNotification M((*it)->Event()->EventID(), (*it)->Event()->ChannelID());
		string message = M.Format(templConflictTimer);
		if (message != "")
		{
		    message = ReplaceAll(message, "%device%", NumToString((*it)->device));
		    conflicttimers += message;
		}
	    }
	conflictsAt = ReplaceAll(conflictsAt, "%conflict.confltimers%", conflicttimers);

	conflicts += conflictsAt;	
    }

    // evaluate variables
    cVarExpr varExprSubj(templSubject);
    subject =  varExprSubj.Evaluate();
    subject = ReplaceAll(subject, "%conflict.count%", NumToString(conflictCheck.relevantConflicts));

    conflicts = ReplaceAll(conflicts, "%conflict.count%", NumToString(conflictCheck.relevantConflicts));

    cVarExpr varExprBody(templBody);
    body =  varExprBody.Evaluate();
    body = ReplaceAll(body, "%conflict.count%", NumToString(conflictCheck.relevantConflicts));
    body = ReplaceAll(body, "%conflict.conflicts%", conflicts);

    SendMail();

    // store conflicts
    strcpy(EPGSearchConfig.LastMailConflicts, newMailConflictsMD5.c_str());
    cPluginManager::GetPlugin("epgsearch")->SetupStore("LastMailConflicts",  EPGSearchConfig.LastMailConflicts);
}
