/*
 *      fhist - file history and comparison tools
 *      Copyright (C) 2000, 2002, 2008, 2010 Peter Miller
 *
 *      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 3 of the License, 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.
 *
 *      You should have received a copy of the GNU General Public License
 *      along with this program. If not, see
 *      <http://www.gnu.org/licenses/>.
 */

#include <common/output/private.h>
#include <common/output/quotprindeco.h>


typedef struct output_quoted_printable_decode_ty
    output_quoted_printable_decode_ty;
struct output_quoted_printable_decode_ty
{
        output_ty       inherited;
        output_ty       *deeper;
        int             delete_on_delete;
        int             state;
        int             c1;
        int             n1;
};


static void
destructor(output_ty *fp)
{
        output_quoted_printable_decode_ty *this;

        this = (output_quoted_printable_decode_ty *)fp;
        switch (this->state)
        {
        case 1:
                output_fputc(this->deeper, '=');
                break;

        case 2:
                output_fputc(this->deeper, '=');
                output_fputc(this->deeper, this->c1);
                break;
        }
        this->state = 0;
        output_flush(this->deeper);
        if (this->delete_on_delete)
            output_delete(this->deeper);
        this->deeper = 0;
}


static const char *
filename(output_ty *fp)
{
        output_quoted_printable_decode_ty *this;

        this = (output_quoted_printable_decode_ty *)fp;
        return output_filename(this->deeper);
}


static long
otell(output_ty *fp)
{
        output_quoted_printable_decode_ty *this;

        this = (output_quoted_printable_decode_ty *)fp;
        return output_ftell(this->deeper);
}


static int
unhex(int c)
{
        switch (c)
        {
        case '0': case '1': case '2': case '3': case '4':
        case '5': case '6': case '7': case '8': case '9':
                return (c - '0');

        case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
                return (c - 'a' + 10);

        case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
                return (c - 'A' + 10);
        }
        return -1;
}


static void
oputc(output_ty *fp, int c)
{
        output_quoted_printable_decode_ty *this;
        int             n;

        this = (output_quoted_printable_decode_ty *)fp;
        switch (this->state)
        {
        case 0:
                state0:
                if (c == '=')
                {
                        this->state = 1;
                        break;
                }
                output_fputc(this->deeper, c);
                break;

        case 1:
                if (c == '\n')
                {
                        this->state = 0;
                        break;
                }
                n = unhex(c);
                if (n >= 0)
                {
                        this->n1 = n;
                        this->c1 = c;
                        this->state = 2;
                        break;
                }
                output_fputc(this->deeper, '=');
                this->state = 0;
                goto state0;

        case 2:
                this->state = 0;
                n = unhex(c);
                if (n >= 0)
                {
                        output_fputc(this->deeper, (this->n1 << 4) + n);
                        break;
                }
                output_fputc(this->deeper, '=');
                output_fputc(this->deeper, this->c1);
                goto state0;
        }
}


static void
oflush(output_ty *fp)
{
        output_quoted_printable_decode_ty *this;

        this = (output_quoted_printable_decode_ty *)fp;
        output_flush(this->deeper);
}


static output_vtbl_ty vtbl =
{
        sizeof(output_quoted_printable_decode_ty),
        destructor,
        filename,
        otell,
        oputc,
        output_generic_fputs,
        output_generic_write,
        oflush,
        "quoted_printable_decode",
};


output_ty *
output_quoted_printable_decode(output_ty *deeper, int dod)
{
        output_ty       *result;
        output_quoted_printable_decode_ty *this;

        result = output_new(&vtbl);
        this = (output_quoted_printable_decode_ty *)result;
        this->deeper = deeper;
        this->delete_on_delete = dod;
        this->state = 0;
        return result;
}
