#ifndef _MSC_VER
#include "StdAfx.h" //C++Builder(プレイヤー本体用)
#pragma hdrstop
#else
#include <windows.h>
#include <stdlib.h>
#endif

#include "base64.h"

//参考： https://github.com/ehobabel/base64

#ifdef USE_CRYPT32
#include <wincrypt.h>
#pragma comment(lib, "Crypt32")
#endif

#define TBL_SIZE 64             //        10        20        30        40        50        60
                                //1234567890123456789012345678901234567890123456789012345678901234
static const wchar_t cnvTbl[] = L"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

///////////////////////////////////////////////////////////////////////////////
int __fastcall b64GetEncodedSize(int srcLen)
{
    int encLen;

    encLen = ((srcLen * 8) + 5) / 6;
    encLen = (encLen + 3) / 4;
    encLen *= 4;
    return encLen;
}
///////////////////////////////////////////////////////////////////////////////
int __fastcall b64Encode(const BYTE *cpSrc, DWORD dwSize, wchar_t **ppszDest)
{
    int enc_size = b64GetEncodedSize(dwSize);
    wchar_t *pszDst = (wchar_t*)malloc((enc_size+1) * sizeof(wchar_t));
    pszDst[enc_size] = 0;
    int remain;
    int encLen;
    int i;
    BYTE tmp[4];

    remain = dwSize;
    encLen = 0;
    i = 0;
    while (0 < remain){
        if (3 <= remain)
        {
            tmp[0] = ((cpSrc[3*i + 0] >> 2) & 0x3F);
            tmp[1] = (((cpSrc[3*i + 0] << 4) & 0x30) | ((cpSrc[3*i + 1] >> 4) & 0xF));
            tmp[2] = (((cpSrc[3*i + 1] << 2) & 0x3C) | ((cpSrc[3*i + 2] >> 6) & 0x3));
            tmp[3] = (cpSrc[3*i + 2] & 0x3F);
            pszDst[4*i + 0] = cnvTbl[tmp[0]];
            pszDst[4*i + 1] = cnvTbl[tmp[1]];
            pszDst[4*i + 2] = cnvTbl[tmp[2]];
            pszDst[4*i + 3] = cnvTbl[tmp[3]];
            remain -= 3;
        }
        else if (2 == remain)
        {
            tmp[0] = ((cpSrc[3*i + 0] >> 2) & 0x3F);
            tmp[1] = (((cpSrc[3*i + 0] << 4) & 0x30) | ((cpSrc[3*i + 1] >> 4) & 0xF));
            tmp[2] = ((cpSrc[3*i + 1] << 2) & 0x3C);
            pszDst[4*i + 0] = cnvTbl[tmp[0]];
            pszDst[4*i + 1] = cnvTbl[tmp[1]];
            pszDst[4*i + 2] = cnvTbl[tmp[2]];
            pszDst[4*i + 3] = L'=';
            remain -= 2;
        }
        else    // 1 == remain
        {
            tmp[0] = ((cpSrc[3*i + 0] >> 2) & 0x3F);
            tmp[1] = ((cpSrc[3*i + 0] << 4) & 0x30);
            pszDst[4*i + 0] = cnvTbl[tmp[0]];
            pszDst[4*i + 1] = cnvTbl[tmp[1]];
            pszDst[4*i + 2] = L'=';
            pszDst[4*i + 3] = L'=';
            remain -= 1;
        }

        encLen += 4;
        i++;
    }
    *ppszDest = pszDst;
    return encLen;
}
///////////////////////////////////////////////////////////////////////////////
int __fastcall b64Decode(const wchar_t *cszSrc, BYTE **ppDest, DWORD *pdwSize)
{
    *ppDest = NULL;
    *pdwSize = 0;
    int srcLen = wcslen(cszSrc);

    int remain;
    int decLen;
    int i, j;
    BYTE tmp[4];

    if (0 != (srcLen%4))
    {
        return -2;
    }
    BYTE *pRet = (BYTE*)malloc(srcLen);
    BYTE *pDst = pRet;
    remain = srcLen;
    decLen = 0;

    while (0 < remain)
    {
        for (i=0; i<4; i++)
        {
            tmp[i] = 0;

            if (L'=' != *cszSrc)
            {
                for (j=0; j<TBL_SIZE; j++)
                {
                    if (cnvTbl[j] == *cszSrc)
                    {
                        tmp[i] = j;
                        break;
                    }
                }
            }
            else
            {
                if (2 < remain)
                {
                    return -3;
                }

                remain = 0;
                break;
            }

            cszSrc++;
            remain--;
        }

        switch (i)
        {
        case 4:
            *pDst = ((tmp[0] << 2) & 0xFC) | ((tmp[1] >> 4) & 0x3);
            pDst++;
            *pDst = ((tmp[1] << 4) & 0xF0) | ((tmp[2] >> 2) & 0xF);
            pDst++;
            *pDst = ((tmp[2] << 6) & 0xC0) | (tmp[3] & 0x3F);
            pDst++;
            decLen += 3;
            break;
        case 3:
            *pDst = ((tmp[0] << 2) & 0xFC) | ((tmp[1] >> 4) & 0x3);
            pDst++;
            *pDst = ((tmp[1] << 4) & 0xF0) | ((tmp[2] >> 2) & 0xF);
            pDst++;
            decLen += 2;
            break;
        case 2:
            *pDst = ((tmp[0] << 2) & 0xFC) | ((tmp[1] >> 4) & 0x3);
            pDst++;
            decLen += 1;
            break;
        default:
            free(pRet);
            return -4;
        }
    }
    *ppDest = pRet;
    *pdwSize = decLen;
    return decLen;
}
///////////////////////////////////////////////////////////////////////////////
#ifdef USE_CRYPT32
///////////////////////////////////////////////////////////////////////////////
int __fastcall b64Encode_crypt32(const BYTE *cpSrc, DWORD dwSize, wchar_t **ppszDest)
{
    *ppszDest = NULL;
    DWORD dwEncodedSize = 0;
    BOOL b = CryptBinaryToString(cpSrc, dwSize, CRYPT_STRING_BASE64, NULL, &dwEncodedSize);
    if(!b){
        return 0;
    }
    wchar_t *pszEncoded = (wchar_t*)malloc((dwEncodedSize+1)*sizeof(wchar_t));
    CryptBinaryToString(cpSrc, dwSize, CRYPT_STRING_BASE64, pszEncoded, &dwEncodedSize);
    pszEncoded[dwEncodedSize] = 0;//念の為
    //\r\n を除去(CRYPT_STRING_NOCRLF が XP で使えないようなので…)
    wchar_t *p = pszEncoded;
    int i = dwEncodedSize - 2;
    while(i >= 0){
        if(p[i] == L'\r' && p[i+1] == L'\n'){
            memmove(&p[i], &p[i+2], (dwEncodedSize - i - 2 + 1)*sizeof(wchar_t));
            dwEncodedSize -= 2;
        }
        i--;
    }
    *ppszDest = pszEncoded;
    return dwEncodedSize;
}
///////////////////////////////////////////////////////////////////////////////
int __fastcall b64Decode_crypt32(const wchar_t *cszSrc, BYTE **ppDest, DWORD *pdwSize)
{
    *ppDest = NULL;
    *pdwSize = 0;
    DWORD dwSrcLen = wcslen(cszSrc);
    DWORD dwDecodedSize = 0;
    BOOL b = CryptStringToBinary(cszSrc, dwSrcLen, CRYPT_STRING_BASE64,
                                 NULL, &dwDecodedSize, NULL, NULL);
    if(!b){
        return 0;
    }
    BYTE *pData = (BYTE*)malloc(dwDecodedSize);
    b = CryptStringToBinary(cszSrc, dwSrcLen, CRYPT_STRING_BASE64,
                            pData, &dwDecodedSize, NULL, NULL);
    if(!b){
        free(pData);
        return 0;
    }
    *ppDest = pData;
    *pdwSize = dwDecodedSize;
    return dwDecodedSize;
}
///////////////////////////////////////////////////////////////////////////////
#endif

