/***************************************************************************
 *                                                                         *
 *   txtrender.c - 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.                                   *
 *                                                                         *
 *   Changelog:                                                            *
 *     2005-03    initial version (c) Udo Richter                          *
 *                                                                         *
 ***************************************************************************/

#include <strings.h>
#include "txtrender.h"


// Font tables

// teletext uses 7-bit numbers to identify a font set.
// There are three font sets involved:
// Primary G0, Secondary G0, and G2 font set.

// Font tables are organized in blocks of 8 fonts:

enumCharsets FontBlockG0_0000[8] = {
    CHARSET_LATIN_G0_EN,
    CHARSET_LATIN_G0_DE,
    CHARSET_LATIN_G0_SV_FI,
    CHARSET_LATIN_G0_IT,
    CHARSET_LATIN_G0_FR,
    CHARSET_LATIN_G0_PT_ES,
    CHARSET_LATIN_G0_CZ_SK,
    CHARSET_LATIN_G0
};

enumCharsets FontBlockG2Latin[8]={
    CHARSET_LATIN_G2,
    CHARSET_LATIN_G2,
    CHARSET_LATIN_G2,
    CHARSET_LATIN_G2,
    CHARSET_LATIN_G2,
    CHARSET_LATIN_G2,
    CHARSET_LATIN_G2,
    CHARSET_LATIN_G2
};

enumCharsets FontBlockG0_0001[8] = {
    CHARSET_LATIN_G0_PL,
    CHARSET_LATIN_G0_DE,
    CHARSET_LATIN_G0_SV_FI,
    CHARSET_LATIN_G0_IT,
    CHARSET_LATIN_G0_FR,
    CHARSET_LATIN_G0,
    CHARSET_LATIN_G0_CZ_SK,
    CHARSET_LATIN_G0
};

enumCharsets FontBlockG0_0010[8] = {
    CHARSET_LATIN_G0_EN,
    CHARSET_LATIN_G0_DE,
    CHARSET_LATIN_G0_SV_FI,
    CHARSET_LATIN_G0_IT,
    CHARSET_LATIN_G0_FR,
    CHARSET_LATIN_G0_PT_ES,
    CHARSET_LATIN_G0_TR,
    CHARSET_LATIN_G0
};


enumCharsets FontBlockG0_0011[8] = {
    CHARSET_LATIN_G0,
    CHARSET_LATIN_G0,
    CHARSET_LATIN_G0,
    CHARSET_LATIN_G0,
    CHARSET_LATIN_G0,
    CHARSET_LATIN_G0_SR_HR_SL,
    CHARSET_LATIN_G0,
    CHARSET_LATIN_G0_RO
};

enumCharsets FontBlockG0_0100[8] = {
    CHARSET_CYRILLIC_G0_SR_HR,
    CHARSET_LATIN_G0_DE,
    CHARSET_LATIN_G0_EE,
    CHARSET_LATIN_G0_LV_LT,
    CHARSET_CYRILLIC_G0_RU_BG,
    CHARSET_CYRILLIC_G0_UK,
    CHARSET_LATIN_G0_CZ_SK,
    CHARSET_INVALID
};

enumCharsets FontBlockG2_0100[8] = {
    CHARSET_CYRILLIC_G2,
    CHARSET_LATIN_G2,
    CHARSET_LATIN_G2,
    CHARSET_LATIN_G2,
    CHARSET_CYRILLIC_G2,
    CHARSET_CYRILLIC_G2,
    CHARSET_LATIN_G2,
    CHARSET_INVALID
};

enumCharsets FontBlockG0_0110[8] = {
    CHARSET_INVALID,
    CHARSET_INVALID,
    CHARSET_INVALID,
    CHARSET_INVALID,
    CHARSET_INVALID,
    CHARSET_INVALID,
    CHARSET_LATIN_G0_TR,
    CHARSET_GREEK_G0
};

enumCharsets FontBlockG2_0110[8] = {
    CHARSET_INVALID,
    CHARSET_INVALID,
    CHARSET_INVALID,
    CHARSET_INVALID,
    CHARSET_INVALID,
    CHARSET_INVALID,
    CHARSET_LATIN_G2,
    CHARSET_GREEK_G2
};

enumCharsets FontBlockG0_1000[8] = {
    CHARSET_LATIN_G0_EN,
    CHARSET_INVALID,
    CHARSET_INVALID,
    CHARSET_INVALID,
    CHARSET_LATIN_G0_FR,
    CHARSET_INVALID,
    CHARSET_INVALID,
    CHARSET_ARABIC_G0
};

enumCharsets FontBlockG2_1000[8] = {
    CHARSET_ARABIC_G2,
    CHARSET_INVALID,
    CHARSET_INVALID,
    CHARSET_INVALID,
    CHARSET_ARABIC_G2,
    CHARSET_INVALID,
    CHARSET_INVALID,
    CHARSET_ARABIC_G2
};

enumCharsets FontBlockG0_1010[8] = {
    CHARSET_INVALID,
    CHARSET_INVALID,
    CHARSET_INVALID,
    CHARSET_INVALID,
    CHARSET_INVALID,
    CHARSET_HEBREW_G0,
    CHARSET_INVALID,
    CHARSET_ARABIC_G0,
};

enumCharsets FontBlockG2_1010[8] = {
    CHARSET_INVALID,
    CHARSET_INVALID,
    CHARSET_INVALID,
    CHARSET_INVALID,
    CHARSET_INVALID,
    CHARSET_ARABIC_G2,
    CHARSET_INVALID,
    CHARSET_ARABIC_G2,
};

enumCharsets FontBlockInvalid[8] = {
    CHARSET_INVALID,
    CHARSET_INVALID,
    CHARSET_INVALID,
    CHARSET_INVALID,
    CHARSET_INVALID,
    CHARSET_INVALID,
    CHARSET_INVALID,
    CHARSET_INVALID
};



// The actual font table definition:
// Split the 7-bit number into upper 4 and lower 3 bits,
// use upper 4 bits for outer array,
// use lower 3 bits for inner array

struct structFontBlock {
    enumCharsets *G0Block;
    enumCharsets *G2Block;
};
    
structFontBlock FontTable[16] = {
    { FontBlockG0_0000, FontBlockG2Latin }, // 0000 block
    { FontBlockG0_0001, FontBlockG2Latin }, // 0001 block
    { FontBlockG0_0010, FontBlockG2Latin }, // 0010 block
    { FontBlockG0_0011, FontBlockG2Latin }, // 0011 block
    { FontBlockG0_0100, FontBlockG2_0100 }, // 0100 block
    { FontBlockInvalid, FontBlockInvalid }, // 0101 block
    { FontBlockG0_0110, FontBlockG2_0110 }, // 0110 block
    { FontBlockInvalid, FontBlockInvalid }, // 0111 block
    { FontBlockG0_1000, FontBlockG2_1000 }, // 1000 block
    { FontBlockInvalid, FontBlockInvalid }, // 1001 block
    { FontBlockG0_1010, FontBlockG2_1010 }, // 1010 block
    { FontBlockInvalid, FontBlockInvalid }, // 1011 block
    { FontBlockInvalid, FontBlockInvalid }, // 1100 block
    { FontBlockInvalid, FontBlockInvalid }, // 1101 block
    { FontBlockInvalid, FontBlockInvalid }, // 1110 block
    { FontBlockInvalid, FontBlockInvalid }  // 1111 block
};

inline enumCharsets GetG0Charset(int codepage) {
    return FontTable[codepage>>3].G0Block[codepage&7];
}
inline enumCharsets GetG2Charset(int codepage) {
    return FontTable[codepage>>3].G2Block[codepage&7];
}

    
cRenderPage::cRenderPage() {
    Dirty=false;
    DirtyAll=false;
    
    // Todo: make this configurable
    FirstG0CodePage=0;
    SecondG0CodePage=0;
}

enum enumSizeMode {
    // Possible size modifications of characters
    sizeNormal,
    sizeDoubleWidth,
    sizeDoubleHeight,
    sizeDoubleSize
};

/*
// Debug only: List of teletext spacing code short names
const char *(names[0x20])={
    "AlBk","AlRd","AlGr","AlYl","AlBl","AlMg","AlCy","AlWh",
    "Flsh","Stdy","EnBx","StBx","SzNo","SzDh","SzDw","SzDs",
    "MoBk","MoRd","MoGr","MoYl","MoBl","MoMg","MoCy","MoWh",
    "Conc","GrCn","GrSp","ESC", "BkBl","StBk","HoMo","ReMo"};
*/

void cRenderPage::ReadTeletextHeader(unsigned char *Header) {
    // Format of buffer:
    //   0     String "VTXV4"
    //   5     always 0x01
    //   6     magazine number
    //   7     page number
    //   8     flags
    //   9     lang
    //   10    always 0x00
    //   11    always 0x00
    //   12    teletext data, 40x24 bytes
    // Format of flags:
    //   0x80  C4 - Erase page
    //   0x40  C5 - News flash
    //   0x20  C6 - Subtitle
    //   0x10  C7 - Suppress Header
    //   0x08  C8 - Update
    //   0x04  C9 - Interrupt Sequence
    //   0x02  C9 (Bug?)
    //   0x01  C11 - Magazine Serial mode

    Flags=Header[8];
    Lang=Header[9];
}


void cRenderPage::RenderTeletextCode(unsigned char *PageCode) {
    int x,y;
    bool EmptyNextLine=false;
    // Skip one line, in case double height chars were/will be used

    // Get code pages:
    int LocalG0CodePage=(FirstG0CodePage & 0x78) 
            | ((Lang & 0x04)>>2) | (Lang & 0x02) | ((Lang & 0x01)<<2);
        
    enumCharsets FirstG0=GetG0Charset(LocalG0CodePage);
    enumCharsets SecondG0=GetG0Charset(SecondG0CodePage);
    // Reserved for later use:
    // enumCharsets FirstG2=GetG2Charset(LocalG0CodePage);
    
    for (y=0;y<24;(EmptyNextLine?y+=2:y++)) {
        // Start of line: Set start of line defaults
        
        // Hold Mosaics mode: Remember last mosaic char/charset 
        // for next spacing code
        bool HoldMosaics=false;
        unsigned char HoldMosaicChar=' ';
        enumCharsets HoldMosaicCharset=FirstG0;

        enumSizeMode Size=sizeNormal;
        // Font size modification
        bool SecondCharset=false;
        // Use primary or secondary G0 charset
        bool GraphicCharset=false;
        // Graphics charset used?
        bool SeparateGraphics=false;
        // Use separated vs. contiguous graphics charset
        bool NoNextChar=false;
        // Skip display of next char, for double-width
        EmptyNextLine=false;
        // Skip next line, for double-height

        cTeletextChar c;
        // auto.initialized to everything off
        c.SetFGColor(ttcWhite);
        c.SetBGColor(ttcBlack);
        c.SetCharset(FirstG0);
        
        if (y==0 && (Flags&0x10)) {
            c.SetBoxedOut(true);    
        }
        if (Flags&0x60) {
            c.SetBoxedOut(true);    
        }

        // Pre-scan for double-height and double-size codes
        for (x=0;x<40;x++) {
            if (y==0 && x<8) x=8;
            if ((PageCode[x+40*y] & 0x7f)==0x0D || (PageCode[x+40*y] & 0x7f)==0x0F)
                EmptyNextLine=true;
        }

        // Move through line
        for (x=0;x<40;x++) {
            unsigned char ttc=PageCode[x+40*y] & 0x7f;
            // skip parity check

            if (y==0 && x<8) continue;
            // no displayable data here...
            
/*          // Debug only: Output line data and spacing codes
            if (y==6) {
                if (ttc<0x20)
                    printf("%s ",names[ttc]);
                else
                    printf("%02x ",ttc);
                if (x==39) printf("\n");
            }
*/          
            
            // Handle all 'Set-At' spacing codes
            switch (ttc) {
            case 0x09: // Steady
                c.SetBlink(false);
                break;
            case 0x0C: // Normal Size
                if (Size!=sizeNormal) {
                    Size=sizeNormal;
                    HoldMosaicChar=' ';
                    HoldMosaicCharset=FirstG0;
                }                   
                break;
            case 0x18: // Conceal
                c.SetConceal(true);
                break;
            case 0x19: // Contiguous Mosaic Graphics
                SeparateGraphics=false;
                if (GraphicCharset)
                    c.SetCharset(CHARSET_GRAPHICS_G1);
                break;
            case 0x1A: // Separated Mosaic Graphics
                SeparateGraphics=true;
                if (GraphicCharset)
                    c.SetCharset(CHARSET_GRAPHICS_G1_SEP);
                break;
            case 0x1C: // Black Background
                c.SetBGColor(ttcBlack);
                break;
            case 0x1D: // New Background
                c.SetBGColor(c.GetFGColor());
                break;
            case 0x1E: // Hold Mosaic
                HoldMosaics=true;               
                break;
            }

            // temporary copy of character data:
            cTeletextChar c2=c;
            // c2 will be text character or space character or hold mosaic
            // c2 may also have temporary flags or charsets
            
            if (ttc<0x20) {
                // Spacing code, display space or hold mosaic
                if (HoldMosaics) {
                    c2.SetChar(HoldMosaicChar);
                    c2.SetCharset(HoldMosaicCharset);
                } else {
                    c2.SetChar(' ');
                }
            } else {
                // Character code               
                c2.SetChar(ttc);
                if (GraphicCharset) {
                    if (ttc&0x20) {
                        // real graphics code, remember for HoldMosaics
                        HoldMosaicChar=ttc;
                        HoldMosaicCharset=c.GetCharset();
                    } else {
                        // invalid code, pass-through to G0
                        c2.SetCharset(SecondCharset?SecondG0:FirstG0);
                    }   
                }
            }
            
            // Handle double-height and double-width extremes
            if (y>=23) {
                if (Size==sizeDoubleHeight) Size=sizeNormal;
                if (Size==sizeDoubleSize) Size=sizeDoubleWidth;
            }
            if (x>=38) {
                if (Size==sizeDoubleWidth) Size=sizeNormal;
                if (Size==sizeDoubleSize) Size=sizeDoubleHeight;
            }
            
            // Now set character code
            
            if (NoNextChar) {
                // Suppress this char due to double width last char
                NoNextChar=false;
            } else {
                switch (Size) {
                case sizeNormal:
                    // Normal sized
                    SetChar(x,y,c2);
                    if (EmptyNextLine && y<23) {
                        // Clean up next line
                        SetChar(x,y+1,c2.ToChar(' ').ToCharset(FirstG0));
                    }
                    break;
                case sizeDoubleWidth:
                    // Double width
                    SetChar(x,y,c2.ToDblWidth(dblw_Left));
                    SetChar(x+1,y,c2.ToDblWidth(dblw_Right));
                    if (EmptyNextLine && y<23) {
                        // Clean up next line
                        SetChar(x  ,y+1,c2.ToChar(' ').ToCharset(FirstG0));
                        SetChar(x+1,y+1,c2.ToChar(' ').ToCharset(FirstG0));
                    }
                    NoNextChar=true;
                    break;
                case sizeDoubleHeight:
                    // Double height
                    SetChar(x,y,c2.ToDblHeight(dblh_Top));
                    SetChar(x,y+1,c2.ToDblHeight(dblh_Bottom));
                    break;
                case sizeDoubleSize:
                    // Double Size
                    SetChar(x  ,  y,c2.ToDblHeight(dblh_Top   ).ToDblWidth(dblw_Left ));
                    SetChar(x+1,  y,c2.ToDblHeight(dblh_Top   ).ToDblWidth(dblw_Right));
                    SetChar(x  ,y+1,c2.ToDblHeight(dblh_Bottom).ToDblWidth(dblw_Left ));
                    SetChar(x+1,y+1,c2.ToDblHeight(dblh_Bottom).ToDblWidth(dblw_Right));
                    NoNextChar=true;
                    break;
                }
            }
                
            // Handle all 'Set-After' spacing codes
            switch (ttc) {
            case 0x00 ... 0x07: // Set FG color
                if (GraphicCharset) {
                    // Actual switch from graphics charset
                    HoldMosaicChar=' ';
                    HoldMosaicCharset=FirstG0;
                }
                c.SetFGColor((enumTeletextColor)ttc);
                c.SetCharset(SecondCharset?SecondG0:FirstG0);
                GraphicCharset=false;
                c.SetConceal(false);
                break;
            case 0x08: // Flash
                c.SetBlink(true);
                break;
            case 0x0A: // End Box
                c.SetBoxedOut(true);
                break;
            case 0x0B: // Start Box
                c.SetBoxedOut(false);
                break;
            case 0x0D: // Double Height
                if (Size!=sizeDoubleHeight) {
                    Size=sizeDoubleHeight;
                    HoldMosaicChar=' ';
                    HoldMosaicCharset=FirstG0;
                }                   
                break;
            case 0x0E: // Double Width
                if (Size!=sizeDoubleWidth) {
                    Size=sizeDoubleWidth;
                    HoldMosaicChar=' ';
                    HoldMosaicCharset=FirstG0;
                }                   
                break;
            case 0x0F: // Double Size
                if (Size!=sizeDoubleSize) {
                    Size=sizeDoubleSize;
                    HoldMosaicChar=' ';
                    HoldMosaicCharset=FirstG0;
                }                   
                break;
            case 0x10 ... 0x17: // Mosaic FG Color
                if (!GraphicCharset) {
                    // Actual switch to graphics charset
                    HoldMosaicChar=' ';
                    HoldMosaicCharset=FirstG0;
                }
                c.SetFGColor((enumTeletextColor)(ttc-0x10));
                c.SetCharset(SeparateGraphics?CHARSET_GRAPHICS_G1_SEP:CHARSET_GRAPHICS_G1);
                GraphicCharset=true;
                c.SetConceal(false);
                break;
            case 0x1B: // ESC Switch
                SecondCharset=!SecondCharset;
                if (!GraphicCharset) c.SetCharset(SecondCharset?SecondG0:FirstG0);
                break;
            case 0x1F: // Release Mosaic
                HoldMosaics=false;
                break;
            }
        } // end for x
    } // end for y
    
    for (x=0;x<40;x++) {
        // Clean out last line
        cTeletextChar c;
        c.SetFGColor(ttcWhite);
        c.SetBGColor(ttcBlack);
        c.SetCharset(FirstG0);
        c.SetChar(' ');
        if (Flags&0x60) {
            c.SetBoxedOut(true);    
        }
        SetChar(x,24,c);
    }       
}


