/*
 *   kmoon - a moon phase indicator
 *   $Id: kmoon.cpp,v 1.46 2004/07/10 21:58:47 binner Exp $
 *   Copyright (C) 1998,2000  Stephan Kulow
 *
 *   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.
 *
 */

#include <stdlib.h>
#include <assert.h>
#include <unistd.h>

#include <qbitmap.h>
#include <qtooltip.h>
#include <qpainter.h>
#include <qpopupmenu.h>

#include <dcopclient.h>
#include <kdebug.h>
#include <kapplication.h>
#include <kwin.h>
#include <kstartupinfo.h>
#include <kmessagebox.h>
#include <kaboutdata.h>
#include <klocale.h>
#include <kstandarddirs.h>
#include <kcmdlineargs.h>
#include <kiconloader.h>
#include <kiconeffect.h>
#include <kconfig.h>

#include "version.h"
#include "kmoondlg.h"
#include "kmoon.h"

extern double moonphasebylunation(int lun, int phi);
extern time_t JDtoDate(double jd, struct tm *event_date);

MoonWidget::MoonWidget(QWidget *parent, const char *name)
    : KSystemTray(parent, name)
{
    struct tm * t;
    time_t clock;

    counter = -1;
    KConfig *config = KGlobal::config();
    config->setGroup("General");
    angle = config->readNumEntry("Rotation", 0);
    north = config->readBoolEntry("Northern", true);
    old_a = old_w = old_h = old_counter = -1;
    old_north = false;
    startTimer(1000 * 60 * 20);

    time(&clock);
    t = gmtime(&clock);
    // kdDebug() << "time " << t->tm_isdst << " " << timezone << " " << daylight << endl ;
    calcStatus(mktime(t));
    if (!parent) {
        popup = new QPopupMenu();
        popup->insertItem(kapp->miniIcon(),
                          i18n("&About"), this,
                          SLOT(showAbout()));
        popup->insertItem(SmallIcon("configure"),i18n("&Configure..."), this,
                          SLOT(settings()));
        popup->insertSeparator();
        popup->insertItem(SmallIcon("exit"), i18n("&Quit"),
                          kapp, SLOT(quit()));
    } else
        popup = 0;
}

MoonWidget::~MoonWidget()
{
    delete popup;
}

void MoonWidget::calcStatus( time_t time )
{
    uint lun = 0;
    time_t last_new = 0;
    time_t next_new = 0;

    do {
        double JDE = moonphasebylunation(lun, 0);
        last_new = next_new;
        next_new = JDtoDate(JDE, 0);
        lun++;
    } while (next_new < time);

    lun -= 2;

    QDateTime ln;
    ln.setTime_t( last_new );
    kdDebug() << KGlobal::locale()->formatDateTime( ln ) << endl;

    time_t first_quarter = JDtoDate( moonphasebylunation( lun, 1 ), 0 );
    QDateTime fq;
    fq.setTime_t( first_quarter );
    kdDebug() << KGlobal::locale()->formatDateTime( fq ) << endl;

    time_t full_moon = JDtoDate( moonphasebylunation( lun, 2 ), 0 );
    QDateTime fm;
    fm.setTime_t( full_moon );
    kdDebug() << KGlobal::locale()->formatDateTime( fm ) << endl;

    time_t third_quarter = JDtoDate( moonphasebylunation( lun, 3 ), 0 );
    QDateTime tq;
    tq.setTime_t( third_quarter );
    kdDebug() << KGlobal::locale()->formatDateTime( tq ) << endl;

    QDateTime nn;
    nn.setTime_t( next_new );
    kdDebug() << KGlobal::locale()->formatDateTime( nn ) << endl;

    QDateTime now;
    now.setTime_t( time );
    kdDebug() << KGlobal::locale()->formatDateTime( now ) << endl;

    counter = ln.daysTo( now );
    kdDebug() << "counter " << counter << " " << fm.daysTo( now ) << endl;

    if ( fm.daysTo( now ) == 0 ) {
        counter = 14;
        tooltip = i18n( "Full Moon" );
        return;
    } else if ( counter <= 15 && counter >= 13 ) {
        counter = 14 + fm.daysTo( now );
        kdDebug() << "around full moon " << counter << endl;
    }

    int diff = fq.daysTo( now );
    if ( diff  == 0 )
        counter = 7;
    else if ( counter <= 8 && counter >= 6 ) {
        counter = 7 + diff;
         kdDebug() << "around first quarter " << counter << endl;
    }

    diff = ln.daysTo( now );
    if ( diff == 0 )
        counter = 0;
    else if ( counter <= 1 || counter >= 28 )
    {
        counter = ( 29 + diff ) % 29;
        diff = -nn.daysTo( now );
        if ( diff == 0 )
            counter = 0;
        else if ( diff < 3 )
            counter = 29 - diff;
        kdDebug() << "around new " << counter << " " << diff << endl;
    }

    if ( tq.daysTo( now ) == 0 )
        counter = 21;
    else if ( counter <= 22 && counter >= 20 )
    {
        counter = 21 + tq.daysTo( now );
        kdDebug() << "around third quarter " << counter << endl;
    }

    kdDebug() << "counter " << counter << endl;

    assert (counter >= 0 && counter < 29);

    switch (counter) {
    case 0:
        tooltip = i18n("New Moon");
        return;
    case 1:
    case 2:
    case 3:
    case 4:
    case 5:
    case 6:
        tooltip = i18n("Waxing Crescent (New Moon was yesterday)", "Waxing Crescent (%n days since New Moon)", counter );
        break;
    case 7:
        tooltip = i18n("First Quarter");
        break;
    case 8:
    case 9:
    case 10:
    case 11:
    case 12:
    case 13:
        tooltip = i18n( "Waxing Gibbous (Tomorrow is Full Moon)", "Waxing Gibbous (%n days to Full Moon)", -fm.daysTo( now ) );
        break;
    case 14:
        assert( false );
        break;
    case 15:
    case 16:
    case 17:
    case 18:
    case 19:
    case 20:
        tooltip = i18n("Waning Gibbous (Yesterday was Full Moon)", "Waning Gibbous (%n days since Full Moon)", fm.daysTo( now ) );
        break;
    case 21:
        tooltip = i18n("Last Quarter");
        break;
    case 22:
    case 23:
    case 24:
    case 25:
    case 26:
    case 27:
    case 28:
        kdDebug() << "nn.days " << ln.daysTo( now ) << " " << nn.daysTo( now ) << endl;
        tooltip = i18n("Waning Crescent (Tomorrow is New Moon)", "Waning Crescent (%n days to New Moon)", -nn.daysTo( now ) );
        break;
    default:
        kdFatal() << "coolo can't count\n";
    }

}

QImage MoonWidget::loadMoon(int index)
{
    if (index == 0) // the new moon has the wrong filename
        index = 29;
    QString filename = QString("kmoon/pics/moon%1.png").arg(index);
    QString path = locate("data", filename);
    if (path.isNull())
        kdFatal() << "cound't find " << filename << ". Exiting.\n";
    QImage image(path);
    KIconEffect iconeffect;
    image=iconeffect.apply(image, KIcon::Panel, KIcon::DefaultState);
    return image;
}

const char *description = I18N_NOOP("Moon Phase Indicator for KDE");

void MoonWidget::settings()
{
	KMoonDlg dlg(angle, north, this, "moondlg");
	if (dlg.exec() == KMoonDlg::Accepted) {
		setAngle(dlg.getAngle());
                setNorthHemi(dlg.northHemi());
		KConfig *config = KGlobal::config();
		config->setGroup("General");
		config->writeEntry("Rotation", angle);
                config->writeEntry("Northern", north);
                config->sync();
	}
}

void MoonWidget::showAbout()
{
    KMessageBox::about(0,
                       i18n(description) + QString::fromLatin1("\n\n") +
                       i18n("Written by Stephan Kulow <coolo@kde.org>\n"
                            "\n"
                            "Lunar code by Chris Osburn "
                            "<chris@speakeasy.org>\n"
                            "\n"
                            "Moon graphics by Tim Beauchamp "
                            "<timb@googol.com>"),
                       i18n("About Moon Phase Indicator"));
}

void MoonWidget::timerEvent( QTimerEvent * )
{
    struct tm * t;
    time_t clock;
    time(&clock);
    t = localtime(&clock);
    calcStatus(mktime(t));
    renderGraphic();
    repaint( false );
}

void MoonWidget::setAngle(int value)
{
    angle = value;
    renderGraphic();
    repaint(false);
}

void MoonWidget::setNorthHemi(bool n)
{
    north = n;
    renderGraphic();
    repaint(false);
}

void MoonWidget::paintEvent( QPaintEvent * )
{
    renderGraphic();
    bitBlt(this, 0, 0, &pixmap, 0, 0);
}

void MoonWidget::resizeEvent( QResizeEvent *)
{
    renderGraphic();
    repaint();
}

void MoonWidget::renderGraphic()
{
    if (old_counter == counter && old_w == width() && old_h == height() && old_a == angle && old_north == north)
        return;
    old_counter = counter;
    old_w = width();
    old_h = height();
    old_north = north;
    QImage im = loadMoon(counter);
    assert(!im.isNull());
    im = im.convertDepth(32, 0);
    assert(!im.isNull());

    int mw = QMIN(width(), height());
    QImage dest;
    QBitmap destMask;

    if (QPixmap::defaultDepth() > 8) {

    if (!pixmap.convertFromImage(im.smoothScale(mw * 2, mw * 2), 0)) {
       return;
    }

    //We expand the image 2x before rotating, rotate it, and then average out
    //the pixel for better quality

    QWMatrix m;
    m.rotate(angle);

    //..rotate..
    QPixmap rotated = pixmap.xForm(m);

    //Copy the relevant part back to the pixmap
    QPainter p;
    p.begin(&pixmap);
    p.fillRect(0, 0, pixmap.width(), pixmap.height(), QApplication::palette().active().background());
    p.drawPixmap(0, 0, rotated, (rotated.width() - pixmap.width()) / 2, (rotated.height() - pixmap.height()) / 2,
                 pixmap.width(), pixmap.height());
    p.end();

    //Create a circular mask of the proper size
    destMask.resize(mw, mw);
    QRegion r(QRect(0, 0, mw, mw), QRegion::Ellipse);
    p.begin(&destMask);
    p.fillRect(0, 0, pixmap.width(), pixmap.height(), Qt::color0);
    p.setClipRegion(r);
    p.fillRect(0, 0, pixmap.width(), pixmap.height(), Qt::color1);
    p.end();

    //Shrink down to the proper size, averaging from the source
    im = pixmap.convertToImage();
    dest = im.copy(0, 0, mw, mw);
    for (int y = 0; y < mw; y++) {
        QRgb *destline = (QRgb*)dest.scanLine(y);
        QRgb *sourceline1 = (QRgb*)im.scanLine(2*y);
        QRgb *sourceline2 = (QRgb*)im.scanLine(2*y + 1);
        for (int x = 0; x < mw; x++) {
            int r = qRed(sourceline1[2*x]) + qRed(sourceline1[2*x+1]);
            r = r + qRed(sourceline2[2*x]) + qRed(sourceline2[2*x+1]);
            int g = qGreen(sourceline1[2*x]) + qGreen(sourceline1[2*x+1]);
            g = g + qGreen(sourceline2[2*x]) + qGreen(sourceline2[2*x+1]);
            int b = qBlue(sourceline1[2*x]) + qBlue(sourceline1[2*x+1]);
            b = b + qBlue(sourceline2[2*x]) + qBlue(sourceline2[2*x+1]);
            destline[x] = qRgb(qRound(r / 4), qRound(g / 4),
                               qRound(b / 4));
        }
    }

    } else {
    	dest = im.smoothScale(mw, mw);
    }

    if (!north)
        dest = dest.mirror(true, true);

    if (!pixmap.convertFromImage(dest, 0)) {
        return;
    }

    if (!destMask.isNull())
    {
        //Shape the pixmap and the widget
        pixmap.setMask( destMask );
        setMask( destMask );
    }

    QToolTip::remove(this);

    QToolTip::add(this, tooltip);
}

void MoonWidget::mousePressEvent( QMouseEvent *e)
{
	if (!popup)
		return;

    if (e->button() == RightButton) {
		popup->popup(mapToGlobal(e->pos()));
		popup->exec();
    }
    if (e->button() == LeftButton) {
		showAbout();
    }
}

static KCmdLineOptions options[] =
{
    { "o", 0, 0 },
	{ "offset <days>", I18N_NOOP("Set the moon some days off"), "0" },
	KCmdLineLastOption
};

int main( int argc, char **argv)
{
    KAboutData aboutData( "kmoon", I18N_NOOP("KMoon"),
						  version, description, KAboutData::License_GPL,
						  "(c) 1998, Stephan Kulow");
    aboutData.addAuthor("Stephan Kulow",0, "coolo@kde.org");
    KCmdLineArgs::init( argc, argv, &aboutData );
    KCmdLineArgs::addCmdLineOptions( options );

    KApplication a;
    MoonWidget *moon = new MoonWidget();
    KWin::setSystemTrayWindowFor(moon->winId(),0);
    kapp->setTopWidget(new QWidget());
    a.setMainWidget(moon);
    moon->show();
    KStartupInfo::appStarted();
    KCmdLineArgs *args = KCmdLineArgs::parsedArgs();
    int offset = args->getOption("offset").toInt();
    args->clear();
    if (offset)
        moon->calcStatus(time(0) + offset * 24 * 60 * 60);
    return a.exec();
}

#include "kmoon.moc"
