//
//  VT52.mm
//  2Term
//
//  Created by Kelvin Sherlock on 7/7/2010.
//  Copyright 2010 __MyCompanyName__. All rights reserved.
//

#include <sys/ttydefaults.h>
#include <cctype>
#include <cstdio>
#include <numeric>

#import "VT52.h"
#include "OutputChannel.h"
#include "Screen.h"

enum {
    StateText,
    StateDCAY,
    StateDCAX
};




/*
 * TODO -- the VT50x are 12 rows but double spaced.
 *
 *
 */

// VT52 is 24 x 80 upper/lowercase.
// 50/50H is 12 * 80, uppercase only.  H has a keypad.
// The 50s only display/transmit uppercase characters and lack `~ {} characters on the keypad.
// VT55 is a VT52 with extra graphic display capabilites.
enum {
    ModelVT50,
    ModelVT50H,
    ModelVT52,
    ModelVT55
};




@implementation VT50

+(void)load {
    [EmulatorManager registerClass: self];
}

+(NSString *)name {
    return @"VT50";
}
-(NSString *)name {
    return @"VT50";
}

-(const char *)termName {
    return "vt50";
}

-(struct winsize)displaySize
{
    struct winsize ws = { 24, 80, 0, 0 };
    
    return ws;
}


-(id)init {
    if ((self = [super init])) {
        _model = ModelVT50;
        [self reset];

    }
    return self;
}

@end


@implementation VT50H

+(void)load {
    [EmulatorManager registerClass: self];
}


+(NSString *)name {
    return @"VT50H";
}

-(NSString *)name {
    return @"VT50H";
}

-(const char *)termName {
    return "vt50h";
}


-(id)init {
    if ((self = [super init])) {
        _model = ModelVT50H;
        [self reset];

    }
    return self;
}

-(struct winsize)displaySize
{
    struct winsize ws = { 24, 80, 0, 0 };
    
    return ws;
}

@end

@implementation VT52

+(void)load {
    [EmulatorManager registerClass: self];
}


+(NSString *)name {
    return @"VT52";
}

-(NSString *)name {
    return @"VT52";
}

-(const char *)termName {
    return "vt52";
}

-(id)init {
    if ((self = [super init])) {
        _model = ModelVT52;
        [self reset];
    }
    return self;
}
@end

@implementation VT55

+(void)load {
    //[EmulatorManager registerClass: self];
}

+(NSString *)name {
    return @"VT55";
}

-(NSString *)name {
    return @"VT55";
}

-(const char *)termName {
    return "vt55";
}

-(id)init {
    if ((self = [super init])) {
        _model = ModelVT55;
        [self reset];

    }
    return self;
}
@end




#define ESC "\x1b"

@implementation VT5x




+(NSString *)name {
    return @"";
}

-(NSString *)name {
    return @"";
}

-(const char *)termName {
    return "";
}
    

-(void)keyDown: (NSEvent *)event screen: (Screen *)screen output: (OutputChannel *)output
{
    NSEventModifierFlags flags = [event modifierFlags];
    NSString *chars = [event charactersIgnoringModifiers];
    NSUInteger length = [chars length];
        
    for (unsigned i = 0; i < length; ++i)
    {
        unichar uc = [chars characterAtIndex: i];
        uint8_t c;
        
        
        if (flags & NSNumericPadKeyMask)
        {
            if (_altKeyPad)
            {
                const char *str = NULL;
                switch (uc)
                {
                    case '0':
                        str = ESC "?p";
                        break;
                    case '1':
                        str = ESC "?q";
                        break;
                    case '2':
                        str = ESC "?r";
                        break;
                    case '3':
                        str = ESC "?s";
                        break;
                    case '4':
                        str = ESC "?t";
                        break;
                    case '5':
                        str = ESC "?u";
                        break;
                    case '6':
                        str = ESC "?v";
                        break;
                    case '7':
                        str = ESC "?w";
                        break;
                    case '8':
                        str = ESC "?x";
                        break;
                    case '9':
                        str = ESC "?y";
                        break;
                    case '.':
                        str = ESC "?n";
                        break;
                    case NSNewlineCharacter: //?
                    case NSEnterCharacter:
                        str = ESC "?M";
                        break;
                        
                }
                if (str)
                {
                    output->write(str);
                    break;
                }
            }
        }
        
        
        switch (uc)
        {
            case NSEnterCharacter:
                output->write('\r');
                break;
            case NSDeleteCharacter:
                output->write(0x7f);
                break;
            case NSUpArrowFunctionKey:
                output->write(ESC "A");
                break;
            case NSDownArrowFunctionKey:
                output->write(ESC "B");
                break;
            case NSRightArrowFunctionKey:
                output->write(ESC "C");
                break;
            case NSLeftArrowFunctionKey:
                output->write(ESC "D");
                break;
                
            // 3 function keys. (VT50H / VT52)
            case NSF1FunctionKey:
                output->write(ESC "P");
                break;
            
            case NSF2FunctionKey:
                output->write(ESC "Q");
                break;

            case NSF3FunctionKey:
                output->write(ESC "R");
                break;                
                
                
            default:
                if (uc > 0x7f) break;
                c = uc;
                
                if (flags & (NSShiftKeyMask | NSAlphaShiftKeyMask))
                {
                    c = toupper(c);
                }
                
                if (flags & NSControlKeyMask)
                {
                    c = CTRL(c);
                }
                output->write(c);
                break;
        }
    }
}

-(void)reset
{
    cs = StateText;
    _escape = false;
    _altKeyPad = false;
    _graphics = false;
    _context.cursor = iPoint(0,0);
    _context.window = iRect(0, 0, 80, 24);
    if (_model <= ModelVT50H) _context.window = iRect(0, 0, 80, 12);
    _context.flags = 0;
}



static void advance(context &ctx, Screen *screen) {
    if (ctx.cursor.x < ctx.window.maxX()-1) ctx.cursor.x++;
}

-(void)processData: (uint8_t *)data length: (size_t)length screen: (Screen *)screen output: (OutputChannel *)output
{
    
    
    cs = std::accumulate(data, data + length, cs, [&](unsigned state, uint8_t c) -> unsigned {

        auto &cursor = _context.cursor;
        auto &window = _context.window;

        c &= 0x7f;
        if (c == 0x7f) return state; // pad character
        if (c < 32) {
            // control characters are always control characters
            switch(c) {
                // bell
                case 007: NSBeep(); break;
                // backspace
                case 010: if (cursor.x) cursor.x--; break;
                //tab
                case 011:
                    if (cursor.x < 72) cursor.x = (cursor.x + 8) & ~7;
                    else if (cursor.x < window.maxX() -1) cursor.x++;
                    break;
                // linefeed
                case 012:
                    if (cursor.y < window.maxY() -1) cursor.y++;
                    else {
                        if (_model <= ModelVT50H) screen->scrollUp();
                        screen->scrollUp();
                    }
                    break;
                // cr
                case 015: cursor.x = 0; break;
                // vt05-compatible dca
                case 016: 
                    if (_model == ModelVT50) return StateDCAY;
                    break;

                case 0x1b: // escape.
                    if (_model >= ModelVT52) _escape = true;
                    else _escape = !_escape;
                    break;
                
            }            
            return state;
        }

        if (state == StateDCAY) {
            c -= 32;
            //if (_model <= ModelVT50H) c *= 2; // double it up.

            if (c >= window.maxY()) {
                if (_model <= ModelVT50H) cursor.y = window.maxY() -1;
                else c = cursor.y;
            }
            cursor.y = c;
            return StateDCAX;
        }
        if (state == StateDCAX) {
            c -= 32;
            if (c >= window.maxX()) c = window.maxX() -1;
            cursor.x = c;
            return StateText;
        }
        
        if (_escape) {
            _escape = false;
            switch(c) {
                case 'A': if (cursor.y) cursor.y--; break;
                case 'B':
                    if (_model >= ModelVT50H && cursor.y < window.maxY() -1)
                        cursor.y++;
                    break;
                case 'C':
                    if (cursor.x < window.maxX() -1) cursor.x++;
                    break;
                case 'D':
                    if (_model >= ModelVT50H && cursor.x) cursor.x--;
                    break;

                // alt graphics
                case 'F':
                    if (_model >= ModelVT52) _graphics = true;
                    break;
                case 'G':
                    if (_model >= ModelVT52) _graphics = false;
                    break;


                // cursor home
                case 'H': cursor = iPoint(0,0); break;

                // reverse line feed
                case 'I':
                    // this is documented as being vt52++
                    // however the 2BSD termcap entry (dating to 1980)
                    // and every termcap since claims 50h supports it...
                    if (_model >= ModelVT50H) {
                        if (cursor.y) cursor.y--;
                        else {
                            screen->scrollDown();
                            if (_model <= ModelVT50H) screen->scrollDown();
                        }
                    }
                    break;

                case 'J': { 
                    // erase to end-of screen

                    iRect tmp;

                    if (_model <= ModelVT50H) {
                        cursor.y *= 2;
                        window.size.height *= 2;
                    }
                    
                    tmp.origin = cursor;
                    tmp.size = iSize( window.maxX() - cursor.x, 1);
                    screen->eraseRect(tmp);
                    
                    tmp.origin = iPoint(0, cursor.y+1);
                    tmp.size = iSize(window.maxX(), window.maxY() - cursor.y - 1);
                    screen->eraseRect(tmp);

                    if (_model <= ModelVT50H) {
                        cursor.y /= 2;
                        window.size.height /= 2;
                    }
                    
                    
                    break;
                }

                // erase to eol
                case 'K': {
                    iRect tmp;

                    if (_model <= ModelVT50H) {
                        cursor.y *= 2;
                        window.size.height *= 2;
                    }
                    
                    tmp.origin = cursor;
                    tmp.size = iSize( window.maxX() - cursor.x, 1);
                    screen->eraseRect(tmp);

                    if (_model <= ModelVT50H) {
                        cursor.y /= 2;
                        window.size.height /= 2;
                    }
                    
                    break;
                }



                // direct cursor addressing
                case 'Y':
                    if (_model >= ModelVT50H) {
                        _escape = false;
                        return StateDCAY;
                    }
                    break;

                case 'Z':
                    // identify terminal.
                    // NB -- these indicate no copier.
                    switch(_model) {
                        case ModelVT50:
                            output->write(ESC "/A");
                            break;
                        case ModelVT50H:
                            output->write(ESC "/H");
                            break;
                        case ModelVT52:
                            output->write(ESC "/K");
                            break;
                        case ModelVT55:
                            output->write(ESC "/C");
                            break;
                    }
                    break;
                // alternate keypad
                case '=':
                    if (_model >= ModelVT52) _altKeyPad = true;
                    break;
                case '>':
                    if (_model >= ModelVT52) _altKeyPad = false;
                    break;

                    
            }
            return state;
        }
        // normal text!

        
        if (c >= 0140 && (_model <= ModelVT50H))
            c -= 040;

        if (_model <= ModelVT50H) {
            auto tmp = cursor; tmp.y *= 2;
            screen->putc(c, tmp, _context.flags);
        } else screen->putc(c, cursor, _context.flags);
        
        advance(_context, screen);
        return state;
        
        
    });

    if (_model <= ModelVT50H) {
        auto tmp = _context.cursor; tmp.y *= 2;
        screen->setCursor(tmp);
    } else screen->setCursor(_context.cursor);
  
}

-(BOOL)resizable
{
    return NO;

    switch (_model)
    {
        case ModelVT52:
        case ModelVT55:
            return YES;
        default:
        return NO;
    }
}

-(struct winsize)defaultSize
{
    struct winsize ws = { 0, 0, 0, 0};
    
    // TODO -- although VT50x have 12 rows, they are double spaced.
    
    switch (_model)
    {
        case ModelVT52:
        case ModelVT55:
            ws.ws_row = 24;
            ws.ws_col = 80;
            break;
            
        default:
            ws.ws_row = 12;
            ws.ws_col = 80;
            break;
    }
    
    return ws;
    
}


@end