/* ============================================================
 *
 * This file is a part of digiKam
 *
 * Date        : 2017-08-08
 * Description : Base functions for dnn module, can be used for face recognition, 
 *               all codes are ported from dlib library (http://dlib.net/)
 *
 * Copyright (C) 2006-2016 by Davis E. King <davis at dlib dot net>
 * Copyright (C) 2017      by Yingjie Liu <yingjiewudi at gmail dot com>
 * Copyright (C) 2017-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
 *
 * 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, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * ============================================================ */

#ifndef DLIB_BASE64_KERNEL_1_CPp_
#define DLIB_BASE64_KERNEL_1_CPp_

#include "base64_kernel_1.h"
#include <iostream>
#include <sstream>
#include <climits>



// ----------------------------------------------------------------------------------------

base64::line_ending_type base64::
line_ending (
) const
{
    return eol_style;
}

// ----------------------------------------------------------------------------------------

void base64::
set_line_ending (
    line_ending_type eol_style_
)
{
    eol_style = eol_style_;
}

// ----------------------------------------------------------------------------------------

base64::
base64 (
) : 
    encode_table(0),
    decode_table(0),
    bad_value(100),
    eol_style(LF)
{
    try
    {
        encode_table = new char[64];
        decode_table = new unsigned char[UCHAR_MAX];
    }
    catch (...)
    {
        if (encode_table) delete [] encode_table;
        if (decode_table) delete [] decode_table;
        throw;
    }

    // now set up the tables with the right stuff
    encode_table[0] = 'A';
    encode_table[17] = 'R';
    encode_table[34] = 'i';
    encode_table[51] = 'z';

    encode_table[1] = 'B';
    encode_table[18] = 'S';
    encode_table[35] = 'j';
    encode_table[52] = '0';

    encode_table[2] = 'C';
    encode_table[19] = 'T';
    encode_table[36] = 'k';
    encode_table[53] = '1';

    encode_table[3] = 'D';
    encode_table[20] = 'U';
    encode_table[37] = 'l';
    encode_table[54] = '2';

    encode_table[4] = 'E';
    encode_table[21] = 'V';
    encode_table[38] = 'm';
    encode_table[55] = '3';

    encode_table[5] = 'F';
    encode_table[22] = 'W';
    encode_table[39] = 'n';
    encode_table[56] = '4';

    encode_table[6] = 'G';
    encode_table[23] = 'X';
    encode_table[40] = 'o';
    encode_table[57] = '5';

    encode_table[7] = 'H';
    encode_table[24] = 'Y';
    encode_table[41] = 'p';
    encode_table[58] = '6';

    encode_table[8] = 'I';
    encode_table[25] = 'Z';
    encode_table[42] = 'q';
    encode_table[59] = '7';

    encode_table[9] = 'J';
    encode_table[26] = 'a';
    encode_table[43] = 'r';
    encode_table[60] = '8';

    encode_table[10] = 'K';
    encode_table[27] = 'b';
    encode_table[44] = 's';
    encode_table[61] = '9';

    encode_table[11] = 'L';
    encode_table[28] = 'c';
    encode_table[45] = 't';
    encode_table[62] = '+';

    encode_table[12] = 'M';
    encode_table[29] = 'd';
    encode_table[46] = 'u';
    encode_table[63] = '/';

    encode_table[13] = 'N';
    encode_table[30] = 'e';
    encode_table[47] = 'v';

    encode_table[14] = 'O';
    encode_table[31] = 'f';
    encode_table[48] = 'w';

    encode_table[15] = 'P';
    encode_table[32] = 'g';
    encode_table[49] = 'x';

    encode_table[16] = 'Q';
    encode_table[33] = 'h';
    encode_table[50] = 'y';



    // we can now fill out the decode_table by using the encode_table
    for (int i = 0; i < UCHAR_MAX; ++i)
    {
        decode_table[i] = bad_value;
    }
    for (unsigned char i = 0; i < 64; ++i)
    {
        decode_table[(unsigned char)encode_table[i]] = i;
    }
}

// ----------------------------------------------------------------------------------------

base64::
~base64 (
)
{
    delete [] encode_table;
    delete [] decode_table;
}

// ----------------------------------------------------------------------------------------

void base64::
encode (
    std::istream& in_,
    std::ostream& out_
) const
{
    using namespace std;
    streambuf& in = *in_.rdbuf();
    streambuf& out = *out_.rdbuf();

    unsigned char inbuf[3];
    unsigned char outbuf[4];
    streamsize status = in.sgetn(reinterpret_cast<char*>(&inbuf),3);

    unsigned char c1, c2, c3, c4, c5, c6;

    int counter = 19;

    // while we haven't hit the end of the input stream
    while (status != 0)
    {
        if (counter == 0)
        {
            counter = 19;
            // write a newline
            char ch;
            switch (eol_style)
            {
                case CR:
                    ch = '\r';
                    if (out.sputn(&ch,1)!=1)
                        throw std::ios_base::failure("error occured in the base64 object");
                    break;
                case LF:
                    ch = '\n';
                    if (out.sputn(&ch,1)!=1)
                        throw std::ios_base::failure("error occured in the base64 object");
                    break;
                case CRLF:
                    ch = '\r';
                    if (out.sputn(&ch,1)!=1)
                        throw std::ios_base::failure("error occured in the base64 object");
                    ch = '\n';
                    if (out.sputn(&ch,1)!=1)
                        throw std::ios_base::failure("error occured in the base64 object");
                    break;
                default:
                    DLIB_CASSERT(false,"this should never happen");
            }
        }
        --counter;

        if (status == 3)
        {
            // encode the bytes in inbuf to base64 and write them to the output stream
            c1 = inbuf[0]&0xfc;
            c2 = inbuf[0]&0x03;
            c3 = inbuf[1]&0xf0;
            c4 = inbuf[1]&0x0f;
            c5 = inbuf[2]&0xc0;
            c6 = inbuf[2]&0x3f;

            outbuf[0] = c1>>2;
            outbuf[1] = (c2<<4)|(c3>>4);
            outbuf[2] = (c4<<2)|(c5>>6);
            outbuf[3] = c6;


            outbuf[0] = encode_table[outbuf[0]];
            outbuf[1] = encode_table[outbuf[1]];
            outbuf[2] = encode_table[outbuf[2]];
            outbuf[3] = encode_table[outbuf[3]];

            // write the encoded bytes to the output stream
            if (out.sputn(reinterpret_cast<char*>(&outbuf),4)!=4)
            {
                throw std::ios_base::failure("error occured in the base64 object");
            }

            // get 3 more input bytes
            status = in.sgetn(reinterpret_cast<char*>(&inbuf),3);
            continue;
        }
        else if (status == 2)
        {
            // we are at the end of the input stream and need to add some padding

            // encode the bytes in inbuf to base64 and write them to the output stream
            c1 = inbuf[0]&0xfc;
            c2 = inbuf[0]&0x03;
            c3 = inbuf[1]&0xf0;
            c4 = inbuf[1]&0x0f;
            c5 = 0;

            outbuf[0] = c1>>2;
            outbuf[1] = (c2<<4)|(c3>>4);
            outbuf[2] = (c4<<2)|(c5>>6);
            outbuf[3] = '=';

            outbuf[0] = encode_table[outbuf[0]];
            outbuf[1] = encode_table[outbuf[1]];
            outbuf[2] = encode_table[outbuf[2]];

            // write the encoded bytes to the output stream
            if (out.sputn(reinterpret_cast<char*>(&outbuf),4)!=4)
            {
                throw std::ios_base::failure("error occured in the base64 object");
            }


            break;
        }
        else // in this case status must be 1 
        {
            // we are at the end of the input stream and need to add some padding

            // encode the bytes in inbuf to base64 and write them to the output stream
            c1 = inbuf[0]&0xfc;
            c2 = inbuf[0]&0x03;
            c3 = 0;

            outbuf[0] = c1>>2;
            outbuf[1] = (c2<<4)|(c3>>4);
            outbuf[2] = '=';
            outbuf[3] = '=';

            outbuf[0] = encode_table[outbuf[0]];
            outbuf[1] = encode_table[outbuf[1]];


            // write the encoded bytes to the output stream
            if (out.sputn(reinterpret_cast<char*>(&outbuf),4)!=4)
            {
                throw std::ios_base::failure("error occured in the base64 object");
            }

            break;
        }
    } // while (status != 0)
    

    // make sure the stream buffer flushes to its I/O channel
    out.pubsync();
}

// ----------------------------------------------------------------------------------------

void base64::
decode (
    std::istream& in_,
    std::ostream& out_
) const
{
    using namespace std;
    streambuf& in = *in_.rdbuf();
    streambuf& out = *out_.rdbuf();

    unsigned char inbuf[4];
    unsigned char outbuf[3];
    int inbuf_pos = 0;
    streamsize status = in.sgetn(reinterpret_cast<char*>(inbuf),1);

    // only count this character if it isn't some kind of filler
    if (status == 1 && decode_table[inbuf[0]] != bad_value )
        ++inbuf_pos;

    unsigned char c1, c2, c3, c4, c5, c6;
    streamsize outsize;

    // while we haven't hit the end of the input stream
    while (status != 0)
    {
        // if we have 4 valid characters
        if (inbuf_pos == 4)
        {
            inbuf_pos = 0;

            // this might be the end of the encoded data so we need to figure out if 
            // there was any padding applied.
            outsize = 3;
            if (inbuf[3] == '=')
            {
                if (inbuf[2] == '=')
                    outsize = 1;
                else
                    outsize = 2;
            }

            // decode the incoming characters
            inbuf[0] = decode_table[inbuf[0]];
            inbuf[1] = decode_table[inbuf[1]];
            inbuf[2] = decode_table[inbuf[2]];
            inbuf[3] = decode_table[inbuf[3]];


            // now pack these guys into bytes rather than 6 bit chunks
            c1 = inbuf[0]<<2;
            c2 = inbuf[1]>>4;
            c3 = inbuf[1]<<4;
            c4 = inbuf[2]>>2;
            c5 = inbuf[2]<<6;
            c6 = inbuf[3];

            outbuf[0] = c1|c2;
            outbuf[1] = c3|c4;
            outbuf[2] = c5|c6;


            // write the encoded bytes to the output stream
            if (out.sputn(reinterpret_cast<char*>(&outbuf),outsize)!=outsize)
            {
                throw std::ios_base::failure("error occured in the base64 object");
            }
        }

        // get more input characters 
        status = in.sgetn(reinterpret_cast<char*>(inbuf + inbuf_pos),1);
        // only count this character if it isn't some kind of filler 
        if ((decode_table[inbuf[inbuf_pos]] != bad_value || inbuf[inbuf_pos] == '=') && 
            status != 0)
            ++inbuf_pos;
    } // while (status != 0)
    
    if (inbuf_pos != 0)
    {
        ostringstream sout;
        sout << inbuf_pos << " extra characters were found at the end of the encoded data."
            << "  This may indicate that the data stream has been truncated.";
        // this happens if we hit EOF in the middle of decoding a 24bit block.
        throw decode_error(sout.str());
    }

    // make sure the stream buffer flushes to its I/O channel
    out.pubsync();
}

// ----------------------------------------------------------------------------------------


#endif // DLIB_BASE64_KERNEL_1_CPp_

