/* base64.cpp */

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdio.h>
//#include <conio.h>
#include <malloc.h>
#include "base64.h"


// US-ASCII テーブル
static char base64_table[] = {
    'A','B','C','D','E','F','G','H',
    'I','J','K','L','M','N','O','P',
    'Q','R','S','T','U','V','W','X',
    'Y','Z','a','b','c','d','e','f',
    'g','h','i','j','k','l','m','n',
    'o','p','q','r','s','t','u','v',
    'w','x','y','z','0','1','2','3',
    '4','5','6','7','8','9','+','/'
//  '='
};


#define IsBit(c)    ((unsigned char)c) ? 0x01 : 0x00


inline
int gettableint(unsigned char str) {
    for(int i = 0; i < 64; i++) {
        if(str == base64_table[i]) break;
    }
    return i;
}


DWORD encode_base64(unsigned char* data, DWORD datalen, unsigned char* buffer) {
    DWORD byte = 0;
    unsigned char bit = 0x80;
    unsigned char tmp;
    DWORD size = 0, line = 0;
    int i;

    while(1) {
        if(byte >= datalen) break;
        *buffer = '\0';
        for(i = 5; i >= 0; i--) {
            tmp = IsBit(data[byte] & bit);
            *buffer |= tmp ? (tmp << i) : *buffer;
            bit >>= 1;
            if(bit == 0x00) {
                byte++;
                if(byte >= datalen) break;
                bit = 0x80;
            }
        }
        *buffer = base64_table[*buffer];
        buffer++;
        size++;
        line++;
        // 改行
        if(line >= 76) {
            buffer[0] = '\r';
            buffer[1] = '\n';
//          buffer[2] = '\0';
            buffer += 2;
            size += 2;
            line = 0;
        }
    }

    tmp = (unsigned char)((size / 4 * 4 + 4) - size);
    while(tmp--) {
        *buffer = '=';
        buffer++;
        size++;
        // 改行
        if(line >= 76) {
            buffer[0] = '\r';
            buffer[1] = '\n';
            buffer += 2;
            size += 2;
            line = 0;
        }
    }
    *buffer = '\0';
    return size;
}


DWORD decode_base64(unsigned char* data, DWORD datalen, unsigned char* buffer) {
    int bit = 7;
    unsigned char tmp;
    unsigned char i, t;
    DWORD size = 0;
    DWORD byte=0;

    *buffer = '\0';
    while(1) {
        if( (data[byte] == '=') || (data[byte] == '\0') || (byte > datalen) )
            break;
        if( (data[byte] == '\n') || (data[byte] == '\r') ) {
            byte++;
            continue;
        }
        tmp = gettableint(data[byte]);
        if(tmp > 63) return 0;
        for(i = 0x20; i != 0; i >>= 1) {
            t = IsBit(tmp & i);
            *buffer |= t ? t << bit : *buffer;
            bit--;
            if(bit < 0) {
                buffer++;
                *buffer = '\0';
                size++;
                bit = 7;
            }
        }
        byte++;
    }

    return size;
}



void out_usage(void) {
    printf(
        "MIME base64 encode/decode\n"
        "usage:\n"
        "   base64 [-e|d] inputfile outputfile\n"
    );
}


int main(int argv, char* argc[]) {
    FILE* finput;
    FILE* foutput;
    unsigned char* data;
    unsigned char* buf;
    DWORD datasize;
    DWORD bufsize;
    BOOL flag = TRUE;

    if(argv < 4) {
        out_usage();
        return 0;
    }

    if(*argc[1] == '-') {
        if(argc[1][1] == 'e')
            flag = TRUE;
        else if(argc[1][1] == 'd')
            flag = FALSE;
        else {
            out_usage();
            return 0;
        }
    }

    finput = fopen(argc[2], "rb");
    if(finput == NULL) {
        printf("入力ファイル %s が開けません\n", argc[2]);
        return 0;
    }

    fseek(finput, 0, SEEK_SET);
    fseek(finput, 0, SEEK_END);
    datasize = ftell(finput);
    fseek(finput, 0, SEEK_SET);
    data = (unsigned char*)malloc(datasize+1);
    fread(data, 1, datasize, finput);
    fclose(finput);

    bufsize = datasize * 2 + 1024;
    buf = (unsigned char*)malloc(bufsize);

    if(flag)
        bufsize = encode_base64(data, datasize, buf);
    else
        bufsize = decode_base64(data, datasize-1, buf);

    free(data);

    if(bufsize <= 0) {
        free(buf);
        printf("失敗しました\n");
        return 0;
    }

    foutput = fopen(argc[3], "wb");
    if(foutput == NULL) {
        free(buf);
        printf("出力ファイル %s が開けません\n", argc[3]);
        return 0;
    }

    fwrite(buf, 1, bufsize, foutput);

    free(buf);
    return 0;
}