/***************************************************************************
 *   Copyright (C) 2005 by Thierry CHARLES   *
 *   thierry@les-charles.net   *
 *                                                                         *
 *   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 "tclock.h"

#include <wx/dcclient.h>
#include <wx/settings.h>
#include <wx/pen.h>
#include <wx/brush.h>
#include <wx/colour.h>
#include <wx/dcmemory.h>

#include <string>
#include <iostream>

#include <time.h>
#include <math.h>
#include <stdio.h>

#include "lib/commons.h"
#include "components/stdgui/tbitmap.h"

#define SECS_PER_DAY 86400
#define BG_SMOOTH_MULT 7
#define MINUTE_SMOOTH_MULT 3

IMPLEMENT_DYNAMIC_CLASS(TClock,wxControl);

BEGIN_EVENT_TABLE(TClock, wxControl)
        EVT_PAINT(TClock::onPaint)
        EVT_SIZE(TClock::onSize)
        EVT_TIMER(_STDGUI_TCLOCK_PAINT_TIMER_,TClock::onPaintTimer)
END_EVENT_TABLE()

TClock::TClock(wxWindow * parent)
    : wxControl(parent, -1, wxDefaultPosition, wxDefaultSize, wxNO_BORDER),
        clockType(Analogical), iClockSize(50), bHasNumbers(true),
        bMightHaveSubNumbers(true), bHasDecorations(true), bMightHaveSubDecorations(true),
        bSeconds(false), bDate(false), iLastMinute(0), minuteBitmap(NULL), bg(NULL)
{
    this->paintTimer.SetOwner(this,_STDGUI_TCLOCK_PAINT_TIMER_);
}

TClock::~TClock()
{
    this->flushCache();
}

void TClock::flushCache()
{
    if(this->minuteBitmap)
    {
        delete this->minuteBitmap;
        this->minuteBitmap = NULL;
    }
    if(this->bg)
    {
        delete this->bg;
        this->bg = NULL;
    }
}

/** défini la taille du cadran ou de la police suivant le type d'horloge */
void TClock::setClockSize( const int iSize )
{
     this->iClockSize = iSize;
     this->flushCache();
}

/** dessine le composant */
void TClock::onPaint(wxPaintEvent& event)
{
    wxPaintDC dc(this);

    dc.SetTextForeground ( this->GetForegroundColour() );
    dc.SetTextBackground ( this->GetBackgroundColour() );
    dc.SetFont(this->GetFont());


    int iClockWidth = 0, iClockHeight = 0;
    int iTimeWidth = this->iClockSize, iTimeHeight = this->iClockSize;
    wxString sTimeString;
    int iDateWidth = 0, iDateHeight = 0;
    wxString sDateString;
    int iContentWidth = 0, iContentHeight = 0;

    this->GetClientSize( &iClockWidth, &iClockHeight );

    time_t iTime = time(NULL);
    tm timeStruct = *localtime(&iTime);

    if(this->bDate)
    {
        char szDate[11];
        sprintf(szDate,"%02d/%02d/%04d",timeStruct.tm_mday,timeStruct.tm_mon+1,timeStruct.tm_year + 1900);
        sDateString = ISO2WX(szDate);

        dc.SetFont(this->GetFont());
        dc.GetTextExtent(sDateString, &iDateWidth, &iDateHeight);

        iContentWidth = iDateWidth;
        iContentHeight = 2 + iDateHeight;
    }

    if(this->clockType == Digital)
    {
        char szTime[9];
        sprintf(szTime,"%02d:%02d:%02d",timeStruct.tm_hour,timeStruct.tm_min,timeStruct.tm_sec);
        if(!this->bSeconds)
            szTime[5] = 0;
        sTimeString = ISO2WX(szTime);

        wxFont font(this->GetFont());
        font.SetPointSize(this->iClockSize / ( this->bSeconds ? 6 : 4));
        dc.SetFont(font);
        dc.GetTextExtent(sTimeString, &iTimeWidth, &iTimeHeight);
    }

    iContentWidth = (iTimeWidth > iDateWidth) ? iTimeWidth : iDateWidth;
    iContentHeight += iTimeHeight;

    int iTopMargin = ((iClockHeight - iContentHeight) / 2);

    // dessin du composant
    wxPen pen(this->GetForegroundColour());
    dc.SetPen(pen);

    wxBrush brush(this->GetBackgroundColour());
    dc.SetBrush(brush);

    // dessin de l'heure
    int iCornerTop = iTopMargin;
    int iCornerLeft = ((iClockWidth - iTimeWidth) / 2);
    if(this->clockType == Digital)
    {
        wxFont font(this->GetFont());
        font.SetPointSize(this->iClockSize / ( this->bSeconds ? 6 : 4));
        dc.SetFont(font);
        dc.DrawText(sTimeString, iCornerLeft, iCornerTop);
    }
    else
    {
        if(this->iLastMinute != timeStruct.tm_min)
        {
            if(this->minuteBitmap)
            {
                delete this->minuteBitmap;
                this->minuteBitmap = NULL;
            }
        }
        this->prepareMinuteBitmap(timeStruct);

        int iVirtualClockSize = this->iClockSize;

        iCornerTop = 0;
        iCornerLeft = 0;

        wxColour dark(0,0,0);
        wxColour shadow(192,192,192);
        wxColour face(255,255,240);
        wxColour highlight(255,255,255);
        wxColour halfshadow(shadow.Red()/2 + highlight.Red()/2,
                            shadow.Green()/2 + highlight.Green()/2,
                            shadow.Blue()/2 + highlight.Blue()/2);

        int iClockCenterY = iVirtualClockSize / 2 - 1;
        int iClockCenterX = iVirtualClockSize / 2 - 1;

        int iBorderSize = iVirtualClockSize / 20;
        int iClockRadius = iVirtualClockSize / 2 - 3;
        int iInteriorMargin = iBorderSize + iBorderSize/2;
        int iInteriorRadius = iClockRadius - iInteriorMargin;
        int iTickSize = iBorderSize / 5;

        TBitmap bmp(iVirtualClockSize,iVirtualClockSize,24);
        { // bloc permettant la fermeture du memoryDC avant de peindre le bmp (pour windows)
            wxMemoryDC bmpDC;
            bmpDC.SelectObject(bmp);

            bmpDC.DrawBitmap(*this->minuteBitmap,0,0,true);

            bmpDC.SetBrush(*wxTRANSPARENT_BRUSH);

            // dessin des aiguilles
            pen.SetColour(dark);
            bmpDC.SetPen(pen);
            if(this->bSeconds)
            {
                double dAngleSeconds = -PI/2 + (PI * timeStruct.tm_sec / 30.0);
                for(int i = -iTickSize/2 ; i <= iTickSize/2 ; i++)
                {
                    int x,y,u=0,v=0;
                    x = static_cast<int>(cos(dAngleSeconds) * (iInteriorRadius-iBorderSize));
                    y = static_cast<int>(sin(dAngleSeconds) * (iInteriorRadius-iBorderSize));

                    // gestion des modificateurs de dï¿½lacement
                    if(i < 0) u = i;
                    else v = i;

                    if(x < 0) u *= -1;
                    if(y < 0) v *= -1;

                    bmpDC.DrawLine(iClockCenterX + x + u, iClockCenterY + y - v, iClockCenterX + u/2, iClockCenterY - v/2);
                }
            }

            pen.SetColour(shadow);
            bmpDC.SetPen(pen);
            brush.SetColour(halfshadow);
            bmpDC.SetBrush(brush);
            bmpDC.DrawCircle(iClockCenterX, iClockCenterY, iBorderSize/2);
        }
        dc.DrawBitmap(bmp,(iClockWidth-iTimeWidth)/2,iTopMargin,true);
    }

    // dessin de la date
    if(this->bDate)
    {
        brush.SetColour(this->GetBackgroundColour());
        dc.SetBrush(brush);
        pen.SetColour(this->GetForegroundColour());
        dc.SetPen(pen);
        iCornerTop = iTopMargin + 2 + iTimeHeight;
        iCornerLeft = ((iClockWidth - iDateWidth) / 2);
        dc.SetFont(this->GetFont());
        dc.DrawText(sDateString, iCornerLeft, iCornerTop);
    }
}

void TClock::prepareMinuteBitmap(const tm & timeStruct)
{
    this->prepareBG();
    if(this->minuteBitmap)
        return;

    this->iLastMinute = timeStruct.tm_min;

    int iVirtualClockSize = this->iClockSize * MINUTE_SMOOTH_MULT;

    wxColour dark(0,0,0);
    wxColour shadow(192,192,192);
    wxColour face(255,255,240);
    wxColour highlight(255,255,255);
    wxColour halfshadow(shadow.Red()/2 + highlight.Red()/2,
                        shadow.Green()/2 + highlight.Green()/2,
                        shadow.Blue()/2 + highlight.Blue()/2);

    int iClockCenterY = iVirtualClockSize / 2 - 1;
    int iClockCenterX = iVirtualClockSize / 2 - 1;

    int iBorderSize = iVirtualClockSize / 20;
    int iClockRadius = iVirtualClockSize / 2 - 3;
    int iInteriorMargin = iBorderSize + iBorderSize/2;
    int iInteriorRadius = iClockRadius - iInteriorMargin;
    int iTickSize = iBorderSize / 5;

    TBitmap bmp(iVirtualClockSize,iVirtualClockSize,24);
    wxMemoryDC bmpDC;
    bmpDC.SelectObject(bmp);

    wxBrush brush(this->GetBackgroundColour());
    bmpDC.SetBackground(brush);
    bmpDC.Clear();

    bmpDC.DrawBitmap(*this->bg,0,0,true);

    bmpDC.SetBrush(*wxTRANSPARENT_BRUSH);

    // dessin des aiguilles
    wxPen pen;
    pen.SetColour(dark);
    bmpDC.SetPen(pen);

    double dAngleMinutes = -PI/2 + (PI * timeStruct.tm_min / 30.0);
    for(int i = -iTickSize ; i <= iTickSize ; i++)
    {
        int x,y,u=0,v=0;
        x = static_cast<int>(cos(dAngleMinutes) * (iInteriorRadius-iBorderSize*2));
        y = static_cast<int>(sin(dAngleMinutes) * (iInteriorRadius-iBorderSize*2));

            // gestion des modificateurs de dï¿½lacement
        if(i < 0) u = i;
        else v = i;

        if(x < 0) u *= -1;
        if(y < 0) v *= -1;

        bmpDC.DrawLine(iClockCenterX + x + u, iClockCenterY + y - v, iClockCenterX + u/2, iClockCenterY - v/2);
    }

    double dAngleHour = -PI/2 + (PI * (timeStruct.tm_hour%12) / 6.0) + (PI * timeStruct.tm_min / 360.0);
    int x = static_cast<int>(cos(dAngleHour) * iInteriorRadius/2);
    int y = static_cast<int>(sin(dAngleHour) * iInteriorRadius/2);
    for(int i = 0 ; i <= iTickSize*3 ; i++)
    {
        if(i > (iTickSize*2))
            pen.SetColour(shadow);
        else
            pen.SetColour(dark);
        bmpDC.SetPen(pen);

        int u,v;
        v = i;
        u=0;
        if(y < 0) v *= -1;

        bmpDC.DrawLine(iClockCenterX + x + u, iClockCenterY + y - v, iClockCenterX + u/2, iClockCenterY - v/2);

        u = -i;
        v=0;
        if(x < 0) u *= -1;

        bmpDC.DrawLine(iClockCenterX + x + u, iClockCenterY + y - v, iClockCenterX + u/2, iClockCenterY - v/2);
    }

    this->minuteBitmap = bmp.getResampledCopy( this->iClockSize, this->iClockSize );
}

void TClock::prepareBG()
{
    if(this->bg)
        return;

    bool bHasSubNumbers = this->iClockSize/10 > 8;

    int iVirtualClockSize = this->iClockSize * BG_SMOOTH_MULT;

    wxColour dark(0,0,0);
    wxColour shadow(192,192,192);
    wxColour face(255,255,240);
    wxColour highlight(255,255,255);
    wxColour halfshadow(shadow.Red()/2 + highlight.Red()/2,
                        shadow.Green()/2 + highlight.Green()/2,
                        shadow.Blue()/2 + highlight.Blue()/2);

    int iClockCenterY = iVirtualClockSize / 2 - 1;
    int iClockCenterX = iVirtualClockSize / 2 - 1;

    int iBorderSize = iVirtualClockSize / 20;
    int iClockRadius = iVirtualClockSize / 2 - 3;

    TBitmap bmp(iVirtualClockSize, iVirtualClockSize, 24);
    wxMemoryDC bmpDC;
    bmpDC.SelectObject(bmp);

    wxBrush brush(this->GetBackgroundColour());
    bmpDC.SetBackground(brush);
    bmpDC.Clear();

    wxPen pen;

    // contour
    bmpDC.SetBrush(*wxTRANSPARENT_BRUSH);
    for(int i = 0 ; i < iVirtualClockSize ; i++)
    {
        wxColour colour;
        if(i < iClockRadius)
        {
            int j = iClockRadius - i;
            colour.Set(halfshadow.Red()*i/iClockRadius + highlight.Red()*j/iClockRadius,
                       halfshadow.Green()*i/iClockRadius + highlight.Green()*j/iClockRadius,
                       halfshadow.Blue()*i/iClockRadius + highlight.Blue()*j/iClockRadius);
        }
        else
            colour = halfshadow;

        pen.SetColour(colour);
        bmpDC.SetPen(pen);

        int iLineWidth = static_cast<int>(sqrt((iClockRadius * iClockRadius) - ((iClockRadius - i) * (iClockRadius - i))));

        bmpDC.DrawLine(iClockCenterX - iLineWidth, i, iClockCenterX + iLineWidth, i);
    }

    pen.SetColour(dark);
    bmpDC.SetPen(pen);
    bmpDC.DrawCircle(iClockCenterX, iClockCenterY, iClockRadius);
    bmpDC.DrawCircle(iClockCenterX, iClockCenterY, iClockRadius-1);
    bmpDC.DrawCircle(iClockCenterX, iClockCenterY, iClockRadius-2);

    brush.SetColour(face);
    bmpDC.SetBrush(brush);
    pen.SetColour(dark);
    bmpDC.SetPen(pen);
    bmpDC.DrawCircle(iClockCenterX, iClockCenterY, iClockRadius - iBorderSize);

    // décorations intérieures
    int iInteriorMargin = iBorderSize + iBorderSize/2;
    int iInteriorRadius = iClockRadius - iInteriorMargin;
    int iTickSize = iBorderSize / 5;
    if(this->bHasDecorations)
    {
        int x = static_cast<int>(0.866025404 * iInteriorRadius);  // 0,866025404 == cos(30)
        int y = static_cast<int>(0.5 * iInteriorRadius);  // 0.5 == sin(30)
        for(int i = -iTickSize ; i <= iTickSize ; i++)
        {
            bmpDC.DrawLine(iClockCenterX+i,iInteriorMargin, iClockCenterX+i, iVirtualClockSize - iInteriorMargin);
            bmpDC.DrawLine(iInteriorMargin,iClockCenterY + i, iVirtualClockSize - iInteriorMargin, iClockCenterY+i);

            if(this->bMightHaveSubDecorations)
            {
                int u=0,v=0;
                // gestion des modificateurs de déplacement
                if(i < 0) u = i;
                else v = i;

                bmpDC.DrawLine(iClockCenterX + x + u, iClockCenterY + y - v, iClockCenterX - x - u, iClockCenterY - y + v);
                bmpDC.DrawLine(iClockCenterX + x + u, iClockCenterY - y + v, iClockCenterX - x - u, iClockCenterY + y - v);
                bmpDC.DrawLine(iClockCenterX + y + u, iClockCenterY + x - v, iClockCenterX - y - u, iClockCenterY - x + v);
                bmpDC.DrawLine(iClockCenterX + y + u, iClockCenterY - x + v, iClockCenterX - y - u, iClockCenterY + x - v);
            }
        }

        pen.SetColour(face);
        bmpDC.SetPen(pen);
        bmpDC.DrawCircle(iClockCenterX, iClockCenterY, iClockRadius - iInteriorMargin - iBorderSize);
    }

    if(this->bHasNumbers)
    {
        // dessin des chiffres
        wxFont font(this->GetFont());
        font.SetPointSize(static_cast<int>(iBorderSize * 2 * this->GetFont().GetPointSize() / 10));
        if(!bHasSubNumbers)
            font.SetPointSize(static_cast<int>(iBorderSize * 3.5 * this->GetFont().GetPointSize() / 10));
        bmpDC.SetFont(font);
        bmpDC.SetTextForeground ( dark );
        bmpDC.SetTextBackground ( face );
        int iChiffreWidth, iChiffreHeight;
        int iChiffreRadius = iInteriorRadius-static_cast<int>(iBorderSize*1.1);

        wxString sChiffre(wxT("12"));
        bmpDC.GetTextExtent(sChiffre, &iChiffreWidth, &iChiffreHeight);
        bmpDC.DrawText(sChiffre, iClockCenterX - iChiffreWidth/2, iClockCenterY - iChiffreRadius);

        sChiffre = wxT("3");
        bmpDC.GetTextExtent(sChiffre, &iChiffreWidth, &iChiffreHeight);
        bmpDC.DrawText(sChiffre, iClockCenterX + iChiffreRadius - iChiffreWidth, iClockCenterY - iChiffreHeight/2);

        sChiffre = wxT("6");
        bmpDC.GetTextExtent(sChiffre, &iChiffreWidth, &iChiffreHeight);
        bmpDC.DrawText(sChiffre, iClockCenterX - iChiffreWidth/2, iClockCenterY + iChiffreRadius - iChiffreHeight);

        sChiffre = wxT("9");
        bmpDC.GetTextExtent(sChiffre, &iChiffreWidth, &iChiffreHeight);
        bmpDC.DrawText(sChiffre, iClockCenterX - iChiffreRadius, iClockCenterY - iChiffreHeight/2);

        if(this->bMightHaveSubNumbers && bHasSubNumbers)
        {
            font.SetPointSize(static_cast<int>(iBorderSize*1.5 * this->GetFont().GetPointSize() / 10));
            bmpDC.SetFont(font);
            int x,y;
            x = static_cast<int>(0.5 * iChiffreRadius);  // 0.5 == sin(30)
            y = static_cast<int>(0.866025404 * iChiffreRadius);  // 0,866025404 == cos(30)

            sChiffre = wxT("1");
            bmpDC.GetTextExtent(sChiffre, &iChiffreWidth, &iChiffreHeight);
            bmpDC.DrawText(sChiffre, iClockCenterX + x - iChiffreWidth, iClockCenterY - y);

            sChiffre = wxT("2");
            bmpDC.GetTextExtent(sChiffre, &iChiffreWidth, &iChiffreHeight);
            bmpDC.DrawText(sChiffre, iClockCenterX + y - iChiffreWidth, iClockCenterY - x - iChiffreHeight/3);

            sChiffre = wxT("4");
            bmpDC.GetTextExtent(sChiffre, &iChiffreWidth, &iChiffreHeight);
            bmpDC.DrawText(sChiffre, iClockCenterX + y - iChiffreWidth, iClockCenterY + x - iChiffreHeight*2/3);

            sChiffre = wxT("5");
            bmpDC.GetTextExtent(sChiffre, &iChiffreWidth, &iChiffreHeight);
            bmpDC.DrawText(sChiffre, iClockCenterX + x - iChiffreWidth, iClockCenterY + y - iChiffreHeight);

            sChiffre = wxT("7");
            bmpDC.GetTextExtent(sChiffre, &iChiffreWidth, &iChiffreHeight);
            bmpDC.DrawText(sChiffre, iClockCenterX - x, iClockCenterY + y - iChiffreHeight);

            sChiffre = wxT("8");
            bmpDC.GetTextExtent(sChiffre, &iChiffreWidth, &iChiffreHeight);
            bmpDC.DrawText(sChiffre, iClockCenterX - y, iClockCenterY + x - iChiffreHeight*2/3);

            sChiffre = wxT("10");
            bmpDC.GetTextExtent(sChiffre, &iChiffreWidth, &iChiffreHeight);
            bmpDC.DrawText(sChiffre, iClockCenterX - y, iClockCenterY - x - iChiffreHeight/3);

            sChiffre = wxT("11");
            bmpDC.GetTextExtent(sChiffre, &iChiffreWidth, &iChiffreHeight);
            bmpDC.DrawText(sChiffre, iClockCenterX - x - (iChiffreWidth/4), iClockCenterY - y);
        }
    }

    this->bg = bmp.getResampledCopy(this->iClockSize * MINUTE_SMOOTH_MULT, this->iClockSize * MINUTE_SMOOTH_MULT);
}



/** calcule la taille optimale de l'horloge */
wxSize TClock::DoGetBestSize() const
{
    int iTimeWidth = this->iClockSize, iTimeHeight = this->iClockSize;
    int iDateWidth = 0, iDateHeight = 0;
    if(this->clockType == Digital || this->bDate)
    {
        wxPaintDC dc(const_cast<TClock*>(this));

        if(this->clockType == Digital)
        {
            wxFont font(this->GetFont());
            font.SetPointSize(this->iClockSize / ( this->bSeconds ? 6 : 4));
            dc.SetFont(font);
            wxString sEvalString(wxT("88:88"));
            if(this->bSeconds)
                sEvalString += wxT(":88");
            dc.GetTextExtent(sEvalString, &iTimeWidth, &iTimeHeight);
        }

        if(this->bDate)
        {
            dc.SetFont(this->GetFont());
            wxString sEvalString(wxT("88/88/8888"));
            dc.GetTextExtent(sEvalString, &iDateWidth, &iDateHeight);
            iDateHeight += 2;
        }

    }
    wxSize bestSize(((iTimeWidth > iDateWidth) ? iTimeWidth : iDateWidth) + 4, iTimeHeight + iDateHeight + 4);

    return bestSize;
}

/** affiche ou cache le composant */
bool TClock::Show(bool bShow)
{
    if(!bShow)
    {
        this->paintTimer.Stop();
        this->flushCache();
    }
    bool bRet = wxControl::Show(bShow);
    if(/*bRet && */bShow)
    {
        int iRefreshRate = 10000; // rafraichissement toutes les 10 secondes
        if(this->bSeconds)
            iRefreshRate = 1000; // rafraichissement a la seconde
        this->paintTimer.Start(iRefreshRate,wxTIMER_CONTINUOUS);
    }
    return bRet;
}

void TClock::setDisplaySeconds( bool b )
{
    this->bSeconds = b;
    if(this->paintTimer.IsRunning())
    {
        int iRefreshRate = 10000; // rafraichissement toutes les 10 secondes
        if(this->bSeconds)
            iRefreshRate = 1000; // rafraichissement a la seconde
        this->paintTimer.Start(iRefreshRate,wxTIMER_CONTINUOUS);
    }
}

/** défini comment sont affichés les nombres des heures */
void TClock::setNumbersProperties(bool bHasNumbers, bool bMightHaveSubNumbers)
{
    this->bHasNumbers = bHasNumbers;
    this->bMightHaveSubNumbers = bMightHaveSubNumbers;
    this->flushCache();
}

/** défini comment sont affichés les repères des heures */
void TClock::setDecorationsProperties(bool bHasDecorations, bool bMightHaveSubDecorations)
{
    this->bHasDecorations = bHasDecorations;
    this->bMightHaveSubDecorations = bMightHaveSubDecorations;
    this->flushCache();
}

/** resize ==> dessine le composant */
void TClock::onSize(wxSizeEvent& event)
{
    this->Refresh();
}

