/*************************************************************** -*- c++ -*-
 *       Copyright (c) 2005      by Udo Richter                            *
 *       Copyright (c) 2021      by Peter Bieringer (extenions)            *
 *                                                                         *
 *   txtrender.h - Teletext display abstraction and teletext code          *
 *                 renderer                                                *
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

#ifndef OSDTELETEXT_TXTRENDER_H_
#define OSDTELETEXT_TXTRENDER_H_

#include <stdio.h>
#include <vdr/tools.h>


// Teletext character sets
enum enumCharsets {
    CHARSET_LATIN_G0          = 0x0000, // native latin (partially todo)
    CHARSET_LATIN_G0_CZ_SK    = 0x0100, // Czech/Slovak (todo)
    CHARSET_LATIN_G0_EN       = 0x0200, // English
    CHARSET_LATIN_G0_EE       = 0x0300, // Estonian (todo)
    CHARSET_LATIN_G0_FR       = 0x0400, // French
    CHARSET_LATIN_G0_DE       = 0x0500, // German
    CHARSET_LATIN_G0_IT       = 0x0600, // Italian
    CHARSET_LATIN_G0_LV_LT    = 0x0700, // Lettish/Lithuanian (todo)
    CHARSET_LATIN_G0_PL       = 0x0800, // Polish (todo)
    CHARSET_LATIN_G0_PT_ES    = 0x0900, // Portuguese/Spanish
    CHARSET_LATIN_G0_RO       = 0x0A00, // Romanian (todo)
    CHARSET_LATIN_G0_SR_HR_SL = 0x0B00, // Serbian/Croatian/Slovenian (todo)
    CHARSET_LATIN_G0_SV_FI    = 0x0C00, // Swedish/Finnish
    CHARSET_LATIN_G0_TR       = 0x0D00, // Turkish (todo)
    CHARSET_LATIN_G2          = 0x0E00, // Latin G2 supplementary set (todo)
    CHARSET_CYRILLIC_G0_SR_HR = 0x0F00, // Serbian/Croatian (todo)
    CHARSET_CYRILLIC_G0_RU_BG = 0x1000, // Russian/Bulgarian (todo)
    CHARSET_CYRILLIC_G0_UK    = 0x1100, // Ukrainian (todo)
    CHARSET_CYRILLIC_G2       = 0x1200, // Cyrillic G2 Supplementary (todo)
    CHARSET_GREEK_G0          = 0x1300, // Greek G0 (todo)
    CHARSET_GREEK_G2          = 0x1400, // Greeek G2 (todo)
    CHARSET_ARABIC_G0         = 0x1500, // Arabic G0 (todo)
    CHARSET_ARABIC_G2         = 0x1600, // Arabic G2 (todo)
    CHARSET_HEBREW_G0         = 0x1700, // Hebrew G0 (todo)
    CHARSET_GRAPHICS_G1       = 0x1800, // G1 graphics set
    CHARSET_GRAPHICS_G1_SEP   = 0x1900, // G1 graphics set, separated
    CHARSET_GRAPHICS_G3       = 0x1A00, // G3 graphics set (todo)
    CHARSET_INVALID           = 0x1F00  // no charset defined
};

// Macro to get the lowest non-0 bit position from a bit mask
// Should evaluate to const on a const mask
#define LowestSet2Bit(mask) ((mask)&0x0001?0:1)
#define LowestSet4Bit(mask) ((mask)&0x0003?LowestSet2Bit(mask):LowestSet2Bit((mask)>>2)+2)
#define LowestSet8Bit(mask) ((mask)&0x000f?LowestSet4Bit(mask):LowestSet4Bit((mask)>>4)+4)
#define LowestSet16Bit(mask) ((mask)&0x00ff?LowestSet8Bit(mask):LowestSet8Bit((mask)>>8)+8)
#define LowestSet32Bit(mask) ((mask)&0xffff?LowestSet16Bit(mask):LowestSet16Bit((mask)>>16)+16)


// Character modifcation double height:
enum enumDblHeight {
    dblh_Normal=0x00000000, // normal height
    dblh_Top   =0x04000000, // upper half character
    dblh_Bottom=0x08000000  // lower half character
};
// Character modifcation double width:
enum enumDblWidth {
    dblw_Normal=0x00000000, // normal width
    dblw_Left  =0x10000000, // left half character
    dblw_Right =0x20000000  // right half character
};

// Teletext colors
enum enumTeletextColor {
    // level 1:
    ttcBlack=0,
    ttcRed=1,
    ttcGreen=2,
    ttcYellow=3,
    ttcBlue=4,
    ttcMagenta=5,
    ttcCyan=6,
    ttcWhite=7,
    // level 2.5:
    ttcTransparent=8,
    ttcHalfRed=9,
    ttcHalfGreen=10,
    ttcHalfYellow=11,
    ttcHalfBlue=12,
    ttcHalfMagenta=13,
    ttcHalfCyan=14,
    ttcGrey=15,
    // unnamed, level 2.5:
    ttcColor16=16, ttcColor17=17, ttcColor18=18, ttcColor19=19,
    ttcColor20=20, ttcColor21=21, ttcColor22=22, ttcColor23=23,
    ttcColor24=24, ttcColor25=25, ttcColor26=26, ttcColor27=27,
    ttcColor28=28, ttcColor29=29, ttcColor30=30, ttcColor31=31,
    
    ttcFirst=0, ttcLast=31
};
inline enumTeletextColor& operator++(enumTeletextColor& c) { return c=enumTeletextColor(int(c)+1); }
inline enumTeletextColor operator++(enumTeletextColor& c, int) { enumTeletextColor tmp(c); ++c; return tmp; }
    
class cTeletextChar {
    // Wrapper class that represents a teletext character,
    // including colors and effects. Should optimize back
    // to 4 byte unsigned int on compile.
    
protected:
    unsigned int c;

    static const unsigned int CHAR             = 0x000000FF;
    // character code
    static const unsigned int CHARSET          = 0x00001F00;
    // character set code, see below
    static const unsigned int BOXOUT           = 0x00004000;
    // 'boxed' mode hidden area
    static const unsigned int DIRTY            = 0x00008000;
    // 'dirty' bit - internal marker only
    static const unsigned int FGCOLOR          = 0x001F0000;
    // 5-bit foreground color code, 3 bit used for now
    static const unsigned int BGCOLOR          = 0x03E00000;
    // 5-bit background color code, 3 bit used for now
    static const unsigned int DBLHEIGHT        = 0x0C000000;
    // show double height
    static const unsigned int DBLWIDTH         = 0x30000000;
    // show double width (todo)
    static const unsigned int CONCEAL          = 0x40000000;
    // character concealed
    static const unsigned int BLINK            = 0x80000000;
    // blinking character

    cTeletextChar(unsigned int cc) { c=cc; }

public:
    cTeletextChar() { c=0; }
    
    // inline helper functions:
    // For each parameter encoded into the 32-bit int, there is
    // a Get...() to read, a Set...() to write, and a To...() to
    // return a modified copy
    inline unsigned int GetC() { return c;};
    inline unsigned char GetChar() 
        { return c&CHAR; }
    inline void SetChar(unsigned char chr)
        { c=(c&~CHAR)|chr; }
    inline cTeletextChar ToChar(unsigned char chr)
        { return cTeletextChar((c&~CHAR)|chr); }
        
    inline enumCharsets GetCharset() 
        { return (enumCharsets)(c&CHARSET); }
    inline void SetCharset(enumCharsets charset) 
        { c=(c&~CHARSET)|charset; }
    inline cTeletextChar ToCharset(enumCharsets charset) 
        { return cTeletextChar((c&~CHARSET)|charset); }
    
    inline enumTeletextColor GetFGColor() 
        { return (enumTeletextColor)((c&FGCOLOR) >> LowestSet32Bit(FGCOLOR)); }
    inline void SetFGColor(enumTeletextColor fgc) 
        { c=(c&~FGCOLOR) | (fgc << LowestSet32Bit(FGCOLOR)); }
    inline cTeletextChar ToFGColor(enumTeletextColor fgc) 
        { return cTeletextChar((c&~FGCOLOR) | (fgc << LowestSet32Bit(FGCOLOR))); }
    
    inline enumTeletextColor GetBGColor() 
        { return (enumTeletextColor)((c&BGCOLOR) >> LowestSet32Bit(BGCOLOR)); }
    inline void SetBGColor(enumTeletextColor bgc) 
        { c=(c&~BGCOLOR) | (bgc << LowestSet32Bit(BGCOLOR)); }
    inline cTeletextChar ToBGColor(enumTeletextColor bgc) 
        { return cTeletextChar((c&~BGCOLOR) | (bgc << LowestSet32Bit(BGCOLOR))); }
    
    inline bool GetBoxedOut() 
        { return c&BOXOUT; }
    inline void SetBoxedOut(bool BoxedOut) 
        { c=(BoxedOut)?(c|BOXOUT):(c&~BOXOUT); }
    inline cTeletextChar ToBoxedOut(bool BoxedOut) 
        { return cTeletextChar((BoxedOut)?(c|BOXOUT):(c&~BOXOUT)); }
    
    inline bool GetDirty() 
        { return c&DIRTY; }
    inline void SetDirty(bool Dirty) 
        { c=(Dirty)?(c|DIRTY):(c&~DIRTY); }
    inline cTeletextChar ToDirty(bool Dirty) 
        { return cTeletextChar((Dirty)?(c|DIRTY):(c&~DIRTY)); }
    
    inline enumDblHeight GetDblHeight() 
        { return (enumDblHeight)(c&DBLHEIGHT); }
    inline void SetDblHeight(enumDblHeight dh) 
        { c=(c&~(DBLHEIGHT)) | dh; }
    inline cTeletextChar ToDblHeight(enumDblHeight dh) 
        { return cTeletextChar((c&~(DBLHEIGHT)) | dh); }
    
    inline enumDblWidth GetDblWidth() 
        { return (enumDblWidth)(c&DBLWIDTH); }
    inline void SetDblWidth(enumDblWidth dw) 
        { c=(c&~(DBLWIDTH)) | dw; }
    inline cTeletextChar ToDblWidth(enumDblWidth dw) 
        { return cTeletextChar((c&~(DBLWIDTH)) | dw); }
    
    inline bool GetConceal() 
        { return c&CONCEAL; }
    inline void SetConceal(bool Conceal) 
        { c=(Conceal)?(c|CONCEAL):(c&~CONCEAL); }
    inline cTeletextChar ToConceal(bool Conceal) 
        { return cTeletextChar((Conceal)?(c|CONCEAL):(c&~CONCEAL)); }
    
    inline bool GetBlink() 
        { return c&BLINK; }
    inline void SetBlink(bool Blink) 
        { c=(Blink)?(c|BLINK):(c&~BLINK); }
    inline cTeletextChar ToBlink(bool Blink) 
        { return cTeletextChar((Blink)?(c|BLINK):(c&~BLINK)); }
        
    bool operator==(cTeletextChar &chr) { return c==chr.c; }
    bool operator!=(cTeletextChar &chr) { return c!=chr.c; }
};


class cRenderPage {
    // Abstraction of a 40x25 teletext character page
    // with all special attributes and colors
    // Additionally tracks changes by maintaining a 
    // 'dirty' flag on each character
    
protected:
    cTeletextChar Page[40][27];

    int Flags;
    //   0x80  C4 - Erase page
    //   0x40  C5 - News flash
    //   0x20  C6 - Subtitle
    //   0x10  C7 - Suppress Header
    //   0x08  C8 - Update
    //   0x04  C9 - Interrupt Sequence
    //   0x02  C10 - Inhibit Display
    //   0x01  C11 - Magazine Serial mode
    
    int Lang;
    // 3-bit language number from header
    
    bool Dirty;    // At least one character is dirty
    bool DirtyAll; // Consider all characters dirty, regardless of flag

    // Font Code pages
    int FirstG0CodePage;  // 7-bit number, lower 3 bits ignored
    int SecondG0CodePage; // 7-bit number

public: 
    cRenderPage();

#define CHECK_XY_LIMIT(x,y)	((x < 0) || (x >= 40) || (y < 0) || (y >= 27))

    cTeletextChar GetChar(int x, int y) {
        // Read character content from page
        if (CHECK_XY_LIMIT(x,y)) {
            esyslog("osdteletext: WARN: out of bounds access to teletext page (GetChar x=%d y=%d)", x, y);
            return cTeletextChar();
        }
        return Page[x][y].ToDirty(false);
    }

    bool IsDirty() {
        // global dirty status
        return Dirty;   
    }
    
    bool IsDirty(int x, int y) {
        // local dirty status
        if (CHECK_XY_LIMIT(x,y)) {
            esyslog("osdteletext: WARN: out of bounds access to teletext page (IsDirty x=%d y=%d)", x, y);
            return false;
        }
        return Page[x][y].GetDirty() | DirtyAll;    
    }

    void MakeDirty(int x, int y) {
        // force one character dirty
        if (CHECK_XY_LIMIT(x,y)) {
            esyslog("osdteletext: WARN: out of bounds access to teletext page (MakeDirty x=%d y=%d)", x, y);
            return;
        }
        Page[x][y].SetDirty(true);
        Dirty=true;
    }
    
    void SetChar(int x, int y, cTeletextChar c) {
        // Set character at given location
        
        if (CHECK_XY_LIMIT(x,y)) {
            esyslog("osdteletext: WARN: out of bounds access to teletext page (SetChar x=%d y=%d c=%x)", x, y, c.GetC());
            return;
        }
        if (GetChar(x,y) != c) {
            Page[x][y]=c.ToDirty(true);
            Dirty=true; 
        }           
    }

    void ReadTeletextHeader(unsigned char *Header);
    // Read header from teletext page
    // Header must be a 12 bytes buffer

    void RenderTeletextCode(unsigned char *PageCode);
    // Interprete teletext code referenced by PageCode
    // and draw the whole page content into this object
    // PageCode must be a buffer containing TelePageData structure (see storage.h)
};



#endif
