/*************************************************************** -*- c++ -*-
 *                                                                         *
 *   displaybase.c - Base class for rendering a teletext cRenderPage to    *
 *                   an actual VDR OSD.                                    *
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 *   Changelog:                                                            *
 *     2005-03    initial version (c) Udo Richter                          *
 *                                                                         *
 ***************************************************************************/

#include <strings.h>
#include <time.h>
#include <vdr/tools.h>
#include "displaybase.h"
#include "txtfont.h"
#include <iostream>

std::string cDisplay::GFXFontFootprint = "";
std::string cDisplay::TXTFontFootprint = "";
int cDisplay::realFontWidths[8] = {0};

cDisplay::cDisplay(int width, int height)
    : Zoom(Zoom_Off), Concealed(false), Blinked(false), FlushLock(0),
      Boxed(false), Width(width), Height(height), Background(clrGray50),
      osd(NULL), outputWidth(0), outputScaleX(1.0),
      outputHeight(0), outputScaleY(1.0),
      ScaleX(1), ScaleY(1), OffsetX(0), OffsetY(0),
      MessageFont(cFont::GetFont(fontSml)), MessageX(0), MessageY(0),
      MessageW(0), MessageH(0),
      GFXFont(0), GFXDblWFont(0), GFXDblHFont(0), GFXDblHWFont(0),
      TXTFont(0), TXTDblWFont(0), TXTDblHFont(0), TXTDblHWFont(0)
{
}

cDisplay::~cDisplay() {
    DELETENULL(osd);
    DELETENULL(GFXFont);
    DELETENULL(GFXDblWFont);
    DELETENULL(GFXDblHFont);
    DELETENULL(GFXDblHWFont);
    DELETENULL(TXTFont);
    DELETENULL(TXTDblWFont);
    DELETENULL(TXTDblHFont);
    DELETENULL(TXTDblHWFont);
}

// This is an ugly hack, any ideas on how to get font size with characters (glyphs) of specified width/height?
cFont *cDisplay::GetFont(const char *name, int fontIndex, int height, int width) {
    cFont *font = cFont::CreateFont(name, height, width);
    if (font != NULL) {
        int realWidth = font->Width(' ');
        for (int i = width * width / realWidth; i < width * 4; i++) {
            DELETENULL(font);
            font = cFont::CreateFont(name, height, i);
            if (font != NULL) {
                realWidth = font->Width(' ');
                if (realWidth > width) {
                    DELETENULL(font);
                    width = i - 1;
                    font = cFont::CreateFont(name, height, width);
                    realFontWidths[fontIndex] = width;
                    break;
                }
            }
        }
    }
    return font;
}

std::string cDisplay::GetFontFootprint(const char *name) {
    return std::string(cString::sprintf("%s_%d_%d_%d", name, fontWidth, fontHeight, Zoom));
}

void cDisplay::InitScaler() {
    // Set up the scaling factors. Also do zoom mode by
    // scaling differently.

    outputScaleX = (double)outputWidth/480.0;
    outputScaleY = (double)outputHeight/250.0;

    int height=Height-6;
    int width=Width-6;
    OffsetX=3;
    OffsetY=3;

    switch (Zoom) {
    case Zoom_Upper:
        height=height*2;
        break;
    case Zoom_Lower:
        OffsetY=OffsetY-height;
        height=height*2;
        break;
    default:;
    }

    ScaleX=(480<<16)/width;
    ScaleY=(250<<16)/height;

    fontWidth = outputWidth * 2 / 40;
    if (Zoom == Zoom_Off) {
        fontHeight = outputHeight * 2 / 25;
    } else {
        fontHeight = outputHeight * 2 / 13;
    }

    int gfxFontWidth = fontWidth;
    int gfxFontHeight = fontHeight;
    const char *gfxFontName = "teletext2:Medium";
    std::string footprint = GetFontFootprint(gfxFontName);

    if (footprint.compare(GFXFontFootprint) == 0) {
        GFXFont      = cFont::CreateFont(gfxFontName, gfxFontHeight / 2, realFontWidths[0]);
        GFXDblWFont  = cFont::CreateFont(gfxFontName, gfxFontHeight / 2, realFontWidths[1]);
        GFXDblHFont  = cFont::CreateFont(gfxFontName, gfxFontHeight , realFontWidths[2]);
        GFXDblHWFont = cFont::CreateFont(gfxFontName, gfxFontHeight, realFontWidths[3]);
    } else {
        GFXFontFootprint = footprint;
        GFXFont      = GetFont(gfxFontName, 0, gfxFontHeight / 2, gfxFontWidth / 2);
        GFXDblWFont  = GetFont(gfxFontName, 1, gfxFontHeight / 2, gfxFontWidth);
        GFXDblHFont  = GetFont(gfxFontName, 2, gfxFontHeight , gfxFontWidth / 2);
        GFXDblHWFont = GetFont(gfxFontName, 3, gfxFontHeight, gfxFontWidth);
    }


    int txtFontWidth = fontWidth;
    int txtFontHeight = fontHeight;
    const char *txtFontName = ttSetup.txtFontName;
    footprint = GetFontFootprint(txtFontName);

    if (footprint.compare(TXTFontFootprint) == 0) {
        TXTFont      = cFont::CreateFont(txtFontName, txtFontHeight / 2, realFontWidths[4]);
        TXTDblWFont  = cFont::CreateFont(txtFontName, txtFontHeight / 2, realFontWidths[5]);
        TXTDblHFont  = cFont::CreateFont(txtFontName, txtFontHeight, realFontWidths[6]);
        TXTDblHWFont = cFont::CreateFont(txtFontName, txtFontHeight, realFontWidths[7]);
    } else {
        TXTFontFootprint = footprint;
        TXTFont      = GetFont(txtFontName, 4, txtFontHeight / 2, txtFontWidth / 2);
        TXTDblWFont  = GetFont(txtFontName, 5, txtFontHeight / 2, txtFontWidth);
        TXTDblHFont  = GetFont(txtFontName, 6, txtFontHeight, txtFontWidth / 2);
        TXTDblHWFont = GetFont(txtFontName, 7, txtFontHeight, txtFontWidth);
    }
}

bool cDisplay::SetBlink(bool blink) {
    int x,y;
    bool Change=false;

    if (blink==Blinked) return false;

    // touch all blinking chars
    for (y=0;y<25;y++) {
        for (x=0;x<40;x++) {
            if (Page[x][y].GetBlink())  {
                Page[x][y].SetDirty(true);
                Change=true;
            }
        }
    }
    Blinked=blink;
    if (Change) Dirty=true;

    Flush();

    return Change;
}

bool cDisplay::SetConceal(bool conceal) {
    int x,y;
    bool Change=false;

    if (conceal==Concealed) return false;

    // touch all concealed chars
    for (y=0;y<25;y++) {
        for (x=0;x<40;x++) {
            if (Page[x][y].GetConceal()) {
                Page[x][y].SetDirty(true);
                Change=true;
            }
        }
    }
    Concealed=conceal;
    if (Change) Dirty=true;

    Flush();

    return Change;
}

void cDisplay::SetZoom(enumZoom zoom) {

    if (!osd) return;
    if (Zoom==zoom) return;
    Zoom=zoom;

    // Re-initialize scaler to let zoom take effect
    InitScaler();

    // Clear screen - mainly clear border
    CleanDisplay();

    Flush();
}

void cDisplay::SetBackgroundColor(tColor c) {
    Background=c;
    CleanDisplay();
    Flush();
}

void cDisplay::CleanDisplay() {
    enumTeletextColor bgc=(Boxed)?(ttcTransparent):(ttcBlack);
    if (!osd) return;

    osd->DrawRectangle(0, 0, Width, Height, GetColorRGB(bgc,0));

    // repaint all
    Dirty=true;
    DirtyAll=true;
}


tColor cDisplay::GetColorRGB(enumTeletextColor ttc, int Area) {
    switch (ttc) {
    case ttcBlack:       return Background;
    case ttcRed:         return clrRed;
    case ttcGreen:       return clrGreen;
    case ttcYellow:      return clrYellow;
    case ttcBlue:        return clrBlue;
    case ttcMagenta:     return clrMagenta;
    case ttcCyan:        return clrCyan;
    case ttcWhite:       return clrWhite;
    case ttcTransparent: return clrTransparent;
    default:             return Background;
    }
}

tColor cDisplay::GetColorRGBAlternate(enumTeletextColor ttc, int Area) {
    return GetColorRGB(ttc,Area);
}

void cDisplay::RenderTeletextCode(unsigned char *PageCode) {
    // Interprete teletext code referenced by PageCode
    // and draw the whole page content into OSD.
    // PageCode must be a 40*24+12 bytes buffer

    HoldFlush();

    cRenderPage::ReadTeletextHeader(PageCode);

    if (!Boxed && (Flags&0x60)!=0) {
        Boxed=true;
        CleanDisplay();
    } else if (Boxed && (Flags&0x60)==0) {
        Boxed=false;
        CleanDisplay();
    }

    cRenderPage::RenderTeletextCode(PageCode+12);

    ReleaseFlush();
}



void cDisplay::DrawDisplay() {
    int x,y;
    int cnt=0;

    if (!IsDirty()) return;
    // nothing to do

    for (y=0;y<25;y++) {
        for (x=0;x<40;x++) {
            if (IsDirty(x,y)) {
                // Need to draw char to osd
                cnt++;
                cTeletextChar c=Page[x][y];
                c.SetDirty(false);
                if ((Blinked && c.GetBlink()) || (Concealed && c.GetConceal())) {
                    c.SetChar(0x20);
                    c.SetCharset(CHARSET_LATIN_G0_DE);
                }
                DrawChar(x,y,c);
                Page[x][y]=c;
            }
        }
    }

    Dirty=false;
    DirtyAll=false;
}


inline bool IsPureChar(unsigned int *bitmap) {
    // Check if character is pure foreground or
    // pure background color
    int i;
    if (bitmap[0]==0x0000) {
        for (i=1;i<10;i++) {
            if (bitmap[i]!=0x0000) return false;
        }
    } else if (bitmap[0]==0xfff0) {
        for (i=1;i<10;i++) {
            if (bitmap[i]!=0xfff0) return false;
        }
    } else {
        return false;
    }
    return true;
}



void cDisplay::DrawChar(int x, int y, cTeletextChar c) {
    // Get colors
    enumTeletextColor ttfg=c.GetFGColor();
    enumTeletextColor ttbg=c.GetBGColor();

    if (c.GetBoxedOut()) {
        ttbg=ttcTransparent;
        ttfg=ttcTransparent;
    }

    if (!osd) return;

    tColor fg=GetColorRGB(ttfg, 0);
    tColor bg=GetColorRGB(ttbg, 0);

    char buf[5];
    uint t = GetVTXChar(c);
    int tl = Utf8CharSet(t, buf);
    buf[tl] = 0;

    const cFont *font;
    int charset = c.GetCharset();
    int fontType = 0;
    int w = fontWidth / 2;
    int h = fontHeight / 2;
    if (c.GetDblWidth() != dblw_Normal) {
        fontType |= 1;
        w = fontWidth;
    }

    if (c.GetDblHeight() != dblh_Normal) {
        fontType |= 2;
        h = fontHeight;
    }

    if (charset == CHARSET_GRAPHICS_G1 || charset == CHARSET_GRAPHICS_G1_SEP) {
        switch(fontType) {
            case 0:
                font = GFXFont;
                break;
            case 1:
                font = GFXDblWFont;
                break;
            case 2:
                font = GFXDblHFont;
                break;
            case 3:
                font = GFXDblHWFont;
                break;
        }
    } else {
        switch(fontType) {
            case 0:
                font = TXTFont;
                break;
            case 1:
                font = TXTDblWFont;
                break;
            case 2:
                font = TXTDblHFont;
                break;
            case 3:
                font = TXTDblHWFont;
                break;
        }
    }

    if (Zoom == Zoom_Lower) {
        y -= 11;
    }

    int vx = x * fontWidth / 2;
    int vy = y * fontHeight / 2;

    bool drawChar = true;
    if (c.GetDblWidth() == dblw_Right) {
        drawChar = false;
    }
    if (c.GetDblHeight() == dblh_Bottom) {
        drawChar = false;
    }

    if (drawChar) {
        osd->DrawRectangle(vx, vy, vx + w, vy + h, bg);
        osd->DrawText(vx, vy, buf, fg, bg, font);
    }

}

void cDisplay::DrawText(int x, int y, const char *text, int len) {
    // Copy text to teletext page

    cTeletextChar c;
    c.SetFGColor(ttcWhite);
    c.SetBGColor(ttcBlack);
    c.SetCharset(CHARSET_LATIN_G0);

    // Copy chars up to len or null char
    while (len>0 && *text!=0x00) {
        c.SetChar(*text);
        SetChar(x,y,c);
        text++;
        x++;
        len--;
    }

    // Fill remaining chars with spaces
    c.SetChar(' ');
    while (len>0) {
        SetChar(x,y,c);
        x++;
        len--;
    }
    // .. and display
    Flush();
}

void cDisplay::DrawClock() {
    char text[9];
    time_t t=time(0);
    struct tm loct;

    localtime_r(&t, &loct);
    sprintf(text, "%02d:%02d:%02d", loct.tm_hour, loct.tm_min, loct.tm_sec);

    DrawText(32,0,text,8);
}

void cDisplay::DrawMessage(const char *txt) {
    const int border=5;

    if (!osd) return;

    HoldFlush();
    // Hold flush until done

    ClearMessage();
    // Make sure old message is gone

    if (IsDirty()) DrawDisplay();
    // Make sure all characters are out, so we can draw on top

    int w=MessageFont->Width(txt)+4*border;
    int h=MessageFont->Height(txt)+4*border;
    int x=(outputWidth-w)/2;
    int y=(outputHeight-h)/2;

    // Get local color mapping
    tColor fg=GetColorRGB(ttcWhite,0);
    tColor bg=GetColorRGB(ttcBlack,0);
    if (fg==bg) bg=GetColorRGBAlternate(ttcBlack,0);

    // Draw framed box
    osd->DrawRectangle(x         ,y         ,x+w-1       ,y+border-1  ,fg);
    osd->DrawRectangle(x         ,y+h-border,x+w-1       ,y+h-1       ,fg);
    osd->DrawRectangle(x         ,y         ,x+border-1  ,y+h-1       ,fg);
    osd->DrawRectangle(x+w-border,y         ,x+w-1       ,y+h-1       ,fg);
    osd->DrawRectangle(x+border  ,y+border  ,x+w-border-1,y+h-border-1,bg);

    // Draw text
    osd->DrawText(x+2*border,y+2*border,txt, fg, bg, MessageFont);


    // Remember box
    MessageW=w;
    MessageH=h;
    MessageX=x;
    MessageY=y;

    // And flush all changes
    ReleaseFlush();
}

void cDisplay::ClearMessage() {
    if (!osd) return;
    if (MessageW==0 || MessageH==0) return;

    // map OSD pixel to virtual coordinate, use center of pixel
    int x0=(MessageX-OffsetX)*ScaleX+ScaleX/2;
    int y0=(MessageY-OffsetY)*ScaleY+ScaleY/2;
    int x1=(MessageX+MessageW-1-OffsetX)*ScaleX+ScaleX/2;
    int y1=(MessageY+MessageH-1-OffsetY)*ScaleY+ScaleY/2;

    // map to character
    x0=x0/(12<<16);
    y0=y0/(10<<16);
    x1=(x1+(12<<16)-1)/(12<<16);
    y1=(y1+(10<<16)-1)/(10<<16);

    for (int x=x0;x<=x1;x++) {
        for (int y=y0;y<=y1;y++) {
            MakeDirty(x,y);
        }
    }

    MessageW=0;
    MessageH=0;

    Flush();
}
