/*
 * Copyright 2013 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "SkPdfConfig.h"
#include "SkPdfDiffEncoder.h"
#include "SkPdfNativeObject.h"
#include "SkPdfNativeTokenizer.h"
#include "SkPdfUtils.h"

// TODO(edisonn): mac builder does not find the header ... but from headers is ok
//#include "SkPdfStreamCommonDictionary_autogen.h"
//#include "SkPdfImageDictionary_autogen.h"
#include "SkPdfHeaders_autogen.h"


// TODO(edisonn): Perf, Make this function run faster.
// There could be 0s between start and end.
// needle will not contain 0s.
static char* strrstrk(char* hayStart, char* hayEnd, const char* needle) {
    int needleLen = strlen(needle);
    if ((isPdfWhiteSpaceOrPdfDelimiter(*(hayStart+needleLen)) || (hayStart+needleLen == hayEnd)) &&
            strncmp(hayStart, needle, needleLen) == 0) {
        return hayStart;
    }

    hayStart++;

    while (hayStart < hayEnd) {
        if (isPdfWhiteSpaceOrPdfDelimiter(*(hayStart-1)) &&
                (isPdfWhiteSpaceOrPdfDelimiter(*(hayStart+needleLen)) ||
                      (hayStart+needleLen == hayEnd)) &&
                strncmp(hayStart, needle, needleLen) == 0) {
            return hayStart;
        }
        hayStart++;
    }
    return NULL;
}

const unsigned char* skipPdfWhiteSpaces(const unsigned char* start, const unsigned char* end) {
    while (start < end && (isPdfWhiteSpace(*start) || *start == kComment_PdfDelimiter)) {
        TRACE_COMMENT(*start);
        if (*start == kComment_PdfDelimiter) {
            // skip the comment until end of line
            while (start < end && !isPdfEOL(*start)) {
                start++;
                TRACE_COMMENT(*start);
            }
        } else {
            start++;
        }
    }
    return start;
}

const unsigned char* endOfPdfToken(const unsigned char* start, const unsigned char* end) {
    SkASSERT(!isPdfWhiteSpace(*start));

    if (start < end && isPdfDelimiter(*start)) {
        TRACE_TK(*start);
        start++;
        return start;
    }

    while (start < end && !isPdfWhiteSpaceOrPdfDelimiter(*start)) {
        TRACE_TK(*start);
        start++;
    }
    return start;
}

// The parsing should end with a ].
static const unsigned char* readArray(const unsigned char* start, const unsigned char* end,
                                      SkPdfNativeObject* array,
                                      SkPdfAllocator* allocator, SkPdfNativeDoc* doc) {
    SkPdfNativeObject::makeEmptyArray(array);
    // PUT_TRACK_STREAM(array, start, start)

    if (allocator == NULL) {
        // TODO(edisonn): report/warning error/assert
        return end;
    }

    while (start < end) {
        // skip white spaces
        start = skipPdfWhiteSpaces(start, end);

        const unsigned char* endOfToken = endOfPdfToken(start, end);

        if (endOfToken == start) {
            // TODO(edisonn): report error in pdf file (end of stream with ] for end of aray
            return start;
        }

        if (endOfToken == start + 1 && *start == kClosedSquareBracket_PdfDelimiter) {
            return endOfToken;
        }

        SkPdfNativeObject* newObj = allocator->allocObject();
        start = nextObject(start, end, newObj, allocator, doc);
        // TODO(edisonn): perf/memory: put the variables on the stack, and flush them on the array
        // only when we are sure they are not references!
        if (newObj->isKeywordReference() && array->size() >= 2 &&
                array->objAtAIndex(array->size() - 1)->isInteger() &&
                array->objAtAIndex(array->size() - 2)->isInteger()) {
            SkPdfNativeObject* gen = array->removeLastInArray();
            SkPdfNativeObject* id = array->removeLastInArray();

            SkPdfNativeObject::resetAndMakeReference((unsigned int)id->intValue(),
                                                     (unsigned int)gen->intValue(), newObj);
            // newObj  PUT_TRACK_PARAMETERS_OBJ2(id, newObj) - store end, as now
        }
        array->appendInArray(newObj);
    }
    // TODO(edisonn): report not reached, we should never get here
    // TODO(edisonn): there might be a bug here, enable an assert and run it on files
    // or it might be that the files were actually corrupted
    return start;
}

static const unsigned char* readString(const unsigned char* start, const unsigned char* end,
                                       unsigned char* out) {
    const unsigned char* in = start;
    bool hasOut = (out != NULL);

    int openRoundBrackets = 1;
    while (in < end) {
        openRoundBrackets += ((*in) == kOpenedRoundBracket_PdfDelimiter);
        openRoundBrackets -= ((*in) == kClosedRoundBracket_PdfDelimiter);
        if (openRoundBrackets == 0) {
            in++;   // consumed )
            break;
        }

        if (*in == kEscape_PdfSpecial) {
            if (in + 1 < end) {
                switch (in[1]) {
                    case 'n':
                        if (hasOut) { *out = kLF_PdfWhiteSpace; }
                        out++;
                        in += 2;
                        break;

                    case 'r':
                        if (hasOut) { *out = kCR_PdfWhiteSpace; }
                        out++;
                        in += 2;
                        break;

                    case 't':
                        if (hasOut) { *out = kHT_PdfWhiteSpace; }
                        out++;
                        in += 2;
                        break;

                    case 'b':
                        // TODO(edisonn): any special meaning to backspace?
                        if (hasOut) { *out = kBackspace_PdfSpecial; }
                        out++;
                        in += 2;
                        break;

                    case 'f':
                        if (hasOut) { *out = kFF_PdfWhiteSpace; }
                        out++;
                        in += 2;
                        break;

                    case kOpenedRoundBracket_PdfDelimiter:
                        if (hasOut) { *out = kOpenedRoundBracket_PdfDelimiter; }
                        out++;
                        in += 2;
                        break;

                    case kClosedRoundBracket_PdfDelimiter:
                        if (hasOut) { *out = kClosedRoundBracket_PdfDelimiter; }
                        out++;
                        in += 2;
                        break;

                    case kEscape_PdfSpecial:
                        if (hasOut) { *out = kEscape_PdfSpecial; }
                        out++;
                        in += 2;
                        break;

                    case '0':
                    case '1':
                    case '2':
                    case '3':
                    case '4':
                    case '5':
                    case '6':
                    case '7': {
                            //read octals
                            in++;   // consume backslash

                            int code = 0;
                            int i = 0;
                            while (in < end && *in >= '0' && *in < '8') {
                                code = (code << 3) + ((*in) - '0');  // code * 8 + d
                                i++;
                                in++;
                                if (i == 3) {
                                    if (hasOut) { *out = code & 0xff; }
                                    out++;
                                    i = 0;
                                }
                            }
                            if (i > 0) {
                                if (hasOut) { *out = code & 0xff; }
                                out++;
                            }
                        }
                        break;

                    default:
                        // Per spec, backslash is ignored if escaped ch is unknown
                        in++;
                        break;
                }
            } else {
                in++;
            }
        } else {
            if (hasOut) { *out = *in; }
            in++;
            out++;
        }
    }

    if (hasOut) {
        return in;  // consumed already ) at the end of the string
    } else {
        // return where the string would end if we reuse the string
        return start + (out - (const unsigned char*)NULL);
    }
}

static int readStringLength(const unsigned char* start, const unsigned char* end) {
    return readString(start, end, NULL) - start;
}

static const unsigned char* readString(const unsigned char* start, const unsigned char* end,
                                       SkPdfNativeObject* str, SkPdfAllocator* allocator) {
    if (!allocator) {
        // TODO(edisonn): report error/warn/assert
        return end;
    }

    int outLength = readStringLength(start, end);
    unsigned char* out = (unsigned char*)allocator->alloc(outLength);
    const unsigned char* now = readString(start, end, out);
    SkPdfNativeObject::makeString(out, out + outLength, str);
    //  PUT_TRACK_STREAM(str, start, now)
    TRACE_STRING(out, out + outLength);
    return now;  // consumed already ) at the end of the string
}

static const unsigned char* readHexString(const unsigned char* start, const unsigned char* end,
                                          unsigned char* out) {
    bool hasOut = (out != NULL);
    const unsigned char* in = start;

    unsigned char code = 0;

    while (in < end) {
        while (in < end && isPdfWhiteSpace(*in)) {
            in++;
        }

        if (*in == kClosedInequityBracket_PdfDelimiter) {
            in++;  // consume >
            // normal exit
            break;
        }

        if (in >= end) {
            // end too soon
            break;
        }

        switch (*in) {
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
                code = (*in - '0') << 4;
                break;

            case 'a':
            case 'b':
            case 'c':
            case 'd':
            case 'e':
            case 'f':
                code = (*in - 'a' + 10) << 4;
                break;

            case 'A':
            case 'B':
            case 'C':
            case 'D':
            case 'E':
            case 'F':
                code = (*in - 'A' + 10) << 4;
                break;

            // TODO(edisonn): spec does not say how to handle this error
            default:
                break;
        }

        in++;  // advance

        while (in < end && isPdfWhiteSpace(*in)) {
            in++;
        }

        // TODO(edisonn): report error
        if (in >= end) {
            if (hasOut) { *out = code; }
            out++;
            break;
        }

        if (*in == kClosedInequityBracket_PdfDelimiter) {
            if (hasOut) { *out = code; }
            out++;
            in++;
            break;
        }

        switch (*in) {
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
                code += (*in - '0');
                break;

            case 'a':
            case 'b':
            case 'c':
            case 'd':
            case 'e':
            case 'f':
                code += (*in - 'a' + 10);
                break;

            case 'A':
            case 'B':
            case 'C':
            case 'D':
            case 'E':
            case 'F':
                code += (*in - 'A' + 10);
                break;

            // TODO(edisonn): spec does not say how to handle this error
            default:
                break;
        }

        if (hasOut) { *out = code; }
        out++;
        in++;
    }

    if (hasOut) {
        return in;  // consumed already ) at the end of the string
    } else {
        // return where the string would end if we reuse the string
        return start + (out - (const unsigned char*)NULL);
    }
}

static int readHexStringLength(const unsigned char* start, const unsigned char* end) {
    return readHexString(start, end, NULL) - start;
}

static const unsigned char* readHexString(const unsigned char* start, const unsigned char* end, SkPdfNativeObject* str, SkPdfAllocator* allocator) {
    if (!allocator) {
        // TODO(edisonn): report error/warn/assert
        return end;
    }
    int outLength = readHexStringLength(start, end);
    unsigned char* out = (unsigned char*)allocator->alloc(outLength);
    const unsigned char* now = readHexString(start, end, out);
    SkPdfNativeObject::makeHexString(out, out + outLength, str);
    // str PUT_TRACK_STREAM(start, now)
    TRACE_HEXSTRING(out, out + outLength);
    return now;  // consumed already > at the end of the string
}

// TODO(edisonn): add version parameter, before PDF 1.2 name could not have special characters.
static const unsigned char* readName(const unsigned char* start, const unsigned char* end,
                                     unsigned char* out) {
    bool hasOut = (out != NULL);
    const unsigned char* in = start;

    unsigned char code = 0;

    while (in < end) {
        if (isPdfWhiteSpaceOrPdfDelimiter(*in)) {
            break;
        }

        if (*in == '#' && in + 2 < end) {
            in++;
            switch (*in) {
                case '0':
                case '1':
                case '2':
                case '3':
                case '4':
                case '5':
                case '6':
                case '7':
                case '8':
                case '9':
                    code = (*in - '0') << 4;
                    break;

                case 'a':
                case 'b':
                case 'c':
                case 'd':
                case 'e':
                case 'f':
                    code = (*in - 'a' + 10) << 4;
                    break;

                case 'A':
                case 'B':
                case 'C':
                case 'D':
                case 'E':
                case 'F':
                    code = (*in - 'A' + 10) << 4;
                    break;

                // TODO(edisonn): spec does not say how to handle this error
                default:
                    break;
            }

            in++;  // advance

            switch (*in) {
                case '0':
                case '1':
                case '2':
                case '3':
                case '4':
                case '5':
                case '6':
                case '7':
                case '8':
                case '9':
                    code += (*in - '0');
                    break;

                case 'a':
                case 'b':
                case 'c':
                case 'd':
                case 'e':
                case 'f':
                    code += (*in - 'a' + 10);
                    break;

                case 'A':
                case 'B':
                case 'C':
                case 'D':
                case 'E':
                case 'F':
                    code += (*in - 'A' + 10);
                    break;

                // TODO(edisonn): spec does not say how to handle this error
                default:
                    break;
            }

            if (hasOut) { *out = code; }
            out++;
            in++;
        } else {
            if (hasOut) { *out = *in; }
            out++;
            in++;
        }
    }

    if (hasOut) {
        return in;  // consumed already ) at the end of the string
    } else {
        // return where the string would end if we reuse the string
        return start + (out - (const unsigned char*)NULL);
    }
}

static int readNameLength(const unsigned char* start, const unsigned char* end) {
    return readName(start, end, NULL) - start;
}

static const unsigned char* readName(const unsigned char* start, const unsigned char* end,
                                     SkPdfNativeObject* name, SkPdfAllocator* allocator) {
    if (!allocator) {
        // TODO(edisonn): report error/warn/assert
        return end;
    }
    int outLength = readNameLength(start, end);
    unsigned char* out = (unsigned char*)allocator->alloc(outLength);
    const unsigned char* now = readName(start, end, out);
    SkPdfNativeObject::makeName(out, out + outLength, name);
    //PUT_TRACK_STREAM(start, now)
    TRACE_NAME(out, out + outLength);
    return now;
}

// TODO(edisonn): pdf spec let Length to be an indirect object define after the stream
// that makes for an interesting scenario, where the stream itself contains endstream, together
// with a reference object with the length, but the real length object would be somewhere else
// it could confuse the parser
/*example:

7 0 obj
<< /length 8 0 R>>
stream
...............
endstream
8 0 obj #we are in stream actually, not a real object
<< 10 >> #we are in stream actually, not a real object
endobj
endstream
8 0 obj #real obj
<< 100 >> #real obj
endobj
and it could get worse, with multiple object like this
*/

// right now implement the silly algorithm that assumes endstream is finishing the stream

static const unsigned char* readStream(const unsigned char* start, const unsigned char* end,
                                       SkPdfNativeObject* dict, SkPdfNativeDoc* doc) {
    start = skipPdfWhiteSpaces(start, end);
    if (!(  start[0] == 's' &&
            start[1] == 't' &&
            start[2] == 'r' &&
            start[3] == 'e' &&
            start[4] == 'a' &&
            start[5] == 'm')) {
        // no stream. return.
        return start;
    }

    start += 6; // strlen("stream")
    if (start[0] == kCR_PdfWhiteSpace && start[1] == kLF_PdfWhiteSpace) {
        start += 2;
    } else if (start[0] == kLF_PdfWhiteSpace) {
        start += 1;
    } else if (isPdfWhiteSpace(start[0])) {
        start += 1;
    } else {
        // TODO(edisonn): warn it should be isPdfDelimiter(start[0])) ?
    }

    SkPdfStreamCommonDictionary* stream = (SkPdfStreamCommonDictionary*) dict;
    // TODO(edisonn): load Length
    int64_t length = -1;

    // TODO(edisonn): very basic implementation
    if (stream->has_Length() && stream->Length(doc) > 0) {
        length = stream->Length(doc);
    }

    // TODO(edisonn): load external streams
    // TODO(edisonn): look at the last filter, to determine how to deal with possible parsing
    // issues. The last filter can have special rules to terminate a stream, which we could
    // use to determine end of stream.

    if (length >= 0) {
        const unsigned char* endstream = start + length;

        if (endstream[0] == kCR_PdfWhiteSpace && endstream[1] == kLF_PdfWhiteSpace) {
            endstream += 2;
        } else if (endstream[0] == kLF_PdfWhiteSpace) {
            endstream += 1;
        }

        if (strncmp((const char*)endstream, "endstream", strlen("endstream")) != 0) {
            length = -1;
        }
    }

    if (length < 0) {
        // scan the buffer, until we find first endstream
        // TODO(edisonn): all buffers must have a 0 at the end now,
        const unsigned char* endstream = (const unsigned char*)strrstrk((char*)start, (char*)end,
                                                                        "endstream");

        if (endstream) {
            length = endstream - start;
            if (*(endstream-1) == kLF_PdfWhiteSpace) length--;
            if (*(endstream-2) == kCR_PdfWhiteSpace) length--;
        }
    }
    if (length >= 0) {
        const unsigned char* endstream = start + length;

        if (endstream[0] == kCR_PdfWhiteSpace && endstream[1] == kLF_PdfWhiteSpace) {
            endstream += 2;
        } else if (endstream[0] == kLF_PdfWhiteSpace) {
            endstream += 1;
        }

        // TODO(edisonn): verify the next bytes are "endstream"

        endstream += strlen("endstream");
        // TODO(edisonn): Assert? report error/warning?
        dict->addStream(start, (size_t)length);
        return endstream;
    }
    return start;
}

static const unsigned char* readInlineImageStream(const unsigned char* start,
                                                  const unsigned char* end,
                                                  SkPdfImageDictionary* inlineImage,
                                                  SkPdfNativeDoc* doc) {
    // We already processed ID keyword, and we should be positioned immediately after it

    // TODO(edisonn): security: either make all streams to have extra 2 bytes at the end,
    // instead of this if.
    //if (end - start <= 2) {
    //    // TODO(edisonn): warning?
    //    return end; // but can we have a pixel image encoded in 1-2 bytes?
    //}

    if (start[0] == kCR_PdfWhiteSpace && start[1] == kLF_PdfWhiteSpace) {
        start += 2;
    } else if (start[0] == kLF_PdfWhiteSpace) {
        start += 1;
    } else if (isPdfWhiteSpace(start[0])) {
        start += 1;
    } else {
        SkASSERT(isPdfDelimiter(start[0]));
        // TODO(edisonn): warning?
    }

    const unsigned char* endstream = (const unsigned char*)strrstrk((char*)start, (char*)end, "EI");
    const unsigned char* endEI = endstream ? endstream + 2 : NULL;  // 2 == strlen("EI")

    if (endstream) {
        int length = endstream - start;
        if (*(endstream-1) == kLF_PdfWhiteSpace) length--;
        if (*(endstream-2) == kCR_PdfWhiteSpace) length--;
        inlineImage->addStream(start, (size_t)length);
    } else {
        // TODO(edisonn): report error in inline image stream (ID-EI) section
        // TODO(edisonn): based on filter, try to ignore a missing EI, and read data properly
        return end;
    }
    return endEI;
}

static const unsigned char* readDictionary(const unsigned char* start, const unsigned char* end,
                                           SkPdfNativeObject* dict,
                                           SkPdfAllocator* allocator, SkPdfNativeDoc* doc) {
    if (allocator == NULL) {
        // TODO(edisonn): report/warning error
        return end;
    }
    SkPdfNativeObject::makeEmptyDictionary(dict);
    // PUT_TRACK_STREAM(dict, start, start)

    start = skipPdfWhiteSpaces(start, end);
    SkPdfAllocator tmpStorage;  // keys will be stored in dict, we can free them after set.

    while (start < end && *start == kNamed_PdfDelimiter) {
        SkPdfNativeObject key;
        //*start = '\0';
        start++;
        start = readName(start, end, &key, &tmpStorage);
        start = skipPdfWhiteSpaces(start, end);

        if (start < end) {
            SkPdfNativeObject* value = allocator->allocObject();
            start = nextObject(start, end, value, allocator, doc);

            start = skipPdfWhiteSpaces(start, end);

            if (start < end) {
                // We should have an indirect reference
                if (isPdfDigit(*start)) {
                    SkPdfNativeObject generation;
                    start = nextObject(start, end, &generation, allocator, doc);

                    SkPdfNativeObject keywordR;
                    start = nextObject(start, end, &keywordR, allocator, doc);

                    if (value->isInteger() && generation.isInteger() &&
                            keywordR.isKeywordReference()) {
                        int64_t id = value->intValue();
                        SkPdfNativeObject::resetAndMakeReference(
                                (unsigned int)id,
                                (unsigned int)generation.intValue(),
                                value);
                        //  PUT_TRACK_PARAMETERS_OBJ2(value, &generation)
                        dict->set(&key, value);
                    } else {
                        // TODO(edisonn) error?, ignore it for now.
                        dict->set(&key, value);
                    }
                } else {
                    // next elem is not a digit, but it might not be / either!
                    dict->set(&key, value);
                }
            } else {
                // /key >>
                dict->set(&key, value);
                return end;
            }
            start = skipPdfWhiteSpaces(start, end);
        } else {
            dict->set(&key, &SkPdfNativeObject::kNull);
            return end;
        }
    }

    // now we should expect >>
    start = skipPdfWhiteSpaces(start, end);
    if (*start != kClosedInequityBracket_PdfDelimiter) {
        // TODO(edisonn): report/warning
    }

    start++;  // skip >
    if (*start != kClosedInequityBracket_PdfDelimiter) {
        // TODO(edisonn): report/warning
    }

    start++;  // skip >

    //STORE_TRACK_PARAMETER_OFFSET_END(dict,start);

    start = readStream(start, end, dict, doc);

    return start;
}

const unsigned char* nextObject(const unsigned char* start, const unsigned char* end,
                                SkPdfNativeObject* token,
                                SkPdfAllocator* allocator, SkPdfNativeDoc* doc) {
    const unsigned char* current;

    // skip white spaces
    start = skipPdfWhiteSpaces(start, end);

    if (start >= end) {
        return end;
    }

    current = endOfPdfToken(start, end);

    // no token, len would be 0
    if (current == start || current == end) {
        return end;
    }

    int tokenLen = current - start;

    if (tokenLen == 1) {
        // start array
        switch (*start) {
            case kOpenedSquareBracket_PdfDelimiter:
                return readArray(current, end, token, allocator, doc);

            case kOpenedRoundBracket_PdfDelimiter:
                return readString(start + 1, end, token, allocator);

            case kOpenedInequityBracket_PdfDelimiter:
                if (end > start + 1 && start[1] == kOpenedInequityBracket_PdfDelimiter) {
                    // TODO(edisonn): pass here the length somehow?
                    return readDictionary(start + 2, end, token, allocator, doc);  // skip <<
                } else {
                    return readHexString(start + 1, end, token, allocator);  // skip <
                }

            case kNamed_PdfDelimiter:
                return readName(start + 1, end, token, allocator);

            // TODO(edisonn): what to do curly brackets?
            case kOpenedCurlyBracket_PdfDelimiter:
            default:
                break;
        }

        SkASSERT(!isPdfWhiteSpace(*start));
        if (isPdfDelimiter(*start)) {
            // TODO(edisonn): how unexpected stream ] } > ) will be handled?
            // for now ignore, and it will become a keyword to be ignored
        }
    }

    if (tokenLen == 4 && start[0] == 'n' && start[1] == 'u' && start[2] == 'l' && start[3] == 'l') {
        SkPdfNativeObject::makeNull(token);
        // PUT_TRACK_STREAM(start, start + 4)
        return current;
    }

    if (tokenLen == 4 && start[0] == 't' && start[1] == 'r' && start[2] == 'u' && start[3] == 'e') {
        SkPdfNativeObject::makeBoolean(true, token);
        // PUT_TRACK_STREAM(start, start + 4)
        return current;
    }

    // TODO(edisonn): again, make all buffers have 5 extra bytes
    if (tokenLen == 5 && start[0] == 'f' &&
                         start[1] == 'a' &&
                         start[2] == 'l' &&
                         start[3] == 's' &&
                         start[4] == 'e') {
        SkPdfNativeObject::makeBoolean(false, token);
        // PUT_TRACK_STREAM(start, start + 5)
        return current;
    }

    if (isPdfNumeric(*start)) {
        SkPdfNativeObject::makeNumeric(start, current, token);
        //  PUT_TRACK_STREAM(start, current)
    } else {
        SkPdfNativeObject::makeKeyword(start, current, token);
        // PUT_TRACK_STREAM(start, current)
    }
    return current;
}

SkPdfNativeObject* SkPdfAllocator::allocBlock() {
    fSizeInBytes += BUFFER_SIZE * sizeof(SkPdfNativeObject);
    return new SkPdfNativeObject[BUFFER_SIZE];
}

SkPdfAllocator::~SkPdfAllocator() {
    for (int i = 0 ; i < fHandles.count(); i++) {
        free(fHandles[i]);
    }
    for (int i = 0 ; i < fHistory.count(); i++) {
        for (int j = 0 ; j < BUFFER_SIZE; j++) {
            fHistory[i][j].reset();
        }
        delete[] fHistory[i];
    }
    for (int j = 0 ; j < BUFFER_SIZE; j++) {
        fCurrent[j].reset();
    }
    delete[] fCurrent;
}

SkPdfNativeObject* SkPdfAllocator::allocObject() {
    if (fCurrentUsed >= BUFFER_SIZE) {
        fHistory.push(fCurrent);
        fCurrent = allocBlock();
        fCurrentUsed = 0;
        fSizeInBytes += sizeof(SkPdfNativeObject*);
    }
    fCurrentUsed++;
    return &fCurrent[fCurrentUsed - 1];
}

// TODO(edisonn): perf: do no copy the buffers, but reuse them, and mark cache the result,
// so there is no need of a second pass
SkPdfNativeTokenizer::SkPdfNativeTokenizer(SkPdfNativeObject* objWithStream,
                                           SkPdfAllocator* allocator,
                                           SkPdfNativeDoc* doc)
            : fDoc(doc)
            , fAllocator(allocator)
            , fUncompressedStream(NULL)
            , fUncompressedStreamEnd(NULL)
            , fEmpty(false)
            , fHasPutBack(false) {
    const unsigned char* buffer = NULL;
    size_t len = 0;
    objWithStream->GetFilteredStreamRef(&buffer, &len);
    // TODO(edisonn): really bad hack, find end of object (endobj might be in a comment!)
    // we need to do now for perf, and our generated pdfs do not have comments,
    // but we need to remove this hack for pdfs in the wild
    char* endobj = strrstrk((char*)buffer, (char*)buffer + len, "endobj");
    if (endobj) {
        len = endobj - (char*)buffer + strlen("endobj");
    }
    fUncompressedStreamStart = fUncompressedStream = buffer;
    fUncompressedStreamEnd = fUncompressedStream + len;
}

SkPdfNativeTokenizer::SkPdfNativeTokenizer(const unsigned char* buffer, int len,
                                           SkPdfAllocator* allocator,
                                           SkPdfNativeDoc* doc) : fDoc(doc)
                                                                , fAllocator(allocator)
                                                                , fEmpty(false)
                                                                , fHasPutBack(false) {
    // TODO(edisonn): really bad hack, find end of object (endobj might be in a comment!)
    // we need to do now for perf, and our generated pdfs do not have comments,
    // but we need to remove this hack for pdfs in the wild
    char* endobj = strrstrk((char*)buffer, (char*)buffer + len, "endobj");
    if (endobj) {
        len = endobj - (char*)buffer + strlen("endobj");
    }
    fUncompressedStreamStart = fUncompressedStream = buffer;
    fUncompressedStreamEnd = fUncompressedStream + len;
}

SkPdfNativeTokenizer::~SkPdfNativeTokenizer() {
}

bool SkPdfNativeTokenizer::readTokenCore(PdfToken* token) {
#ifdef PDF_TRACE_READ_TOKEN
    static int read_op = 0;
#endif

    token->fKeyword = NULL;
    token->fObject = NULL;

    fUncompressedStream = skipPdfWhiteSpaces(fUncompressedStream, fUncompressedStreamEnd);
    if (fUncompressedStream >= fUncompressedStreamEnd) {
        fEmpty = true;
        return false;
    }

    SkPdfNativeObject obj;
    fUncompressedStream = nextObject(fUncompressedStream, fUncompressedStreamEnd, &obj, fAllocator, fDoc);
    //  PUT_TRACK_STREAM_ARGS_EXPL2(fStreamId, fUncompressedStreamStart)

    // If it is a keyword, we will only get the pointer of the string.
    if (obj.type() == SkPdfNativeObject::kKeyword_PdfObjectType) {
        token->fKeyword = obj.c_str();
        token->fKeywordLength = obj.lenstr();
        token->fType = kKeyword_TokenType;
    } else {
        SkPdfNativeObject* pobj = fAllocator->allocObject();
        *pobj = obj;
        token->fObject = pobj;
        token->fType = kObject_TokenType;
    }

#ifdef PDF_TRACE_READ_TOKEN
    read_op++;
#if 0
    if (548 == read_op) {
        printf("break;\n");
    }
#endif
    printf("%i READ %s %s\n", read_op, token->fType == kKeyword_TokenType ? "Keyword" : "Object",
           token->fKeyword ? SkString(token->fKeyword, token->fKeywordLength).c_str() :
                             token->fObject->toString().c_str());
#endif

    return true;
}

void SkPdfNativeTokenizer::PutBack(PdfToken token) {
    SkASSERT(!fHasPutBack);
    fHasPutBack = true;
    fPutBack = token;
#ifdef PDF_TRACE_READ_TOKEN
    printf("PUT_BACK %s %s\n", token.fType == kKeyword_TokenType ? "Keyword" : "Object",
           token.fKeyword ? SkString(token.fKeyword, token.fKeywordLength).c_str() :
                            token.fObject->toString().c_str());
#endif
}

bool SkPdfNativeTokenizer::readToken(PdfToken* token, bool writeDiff) {
    if (fHasPutBack) {
        *token = fPutBack;
        fHasPutBack = false;
#ifdef PDF_TRACE_READ_TOKEN
        printf("READ_BACK %s %s\n", token->fType == kKeyword_TokenType ? "Keyword" : "Object",
               token->fKeyword ? SkString(token->fKeyword, token->fKeywordLength).c_str() :
                                 token->fObject->toString().c_str());
#endif
        if (writeDiff) {
            SkPdfDiffEncoder::WriteToFile(token);
        }
        return true;
    }

    if (fEmpty) {
#ifdef PDF_TRACE_READ_TOKEN
        printf("EMPTY TOKENIZER\n");
#endif
        return false;
    }

    const bool result = readTokenCore(token);
    if (result && writeDiff) {
        SkPdfDiffEncoder::WriteToFile(token);
    }
    return result;
}

#define DECLARE_PDF_NAME(longName) SkPdfName longName((char*)#longName)

// keys
DECLARE_PDF_NAME(BitsPerComponent);
DECLARE_PDF_NAME(ColorSpace);
DECLARE_PDF_NAME(Decode);
DECLARE_PDF_NAME(DecodeParms);
DECLARE_PDF_NAME(Filter);
DECLARE_PDF_NAME(Height);
DECLARE_PDF_NAME(ImageMask);
DECLARE_PDF_NAME(Intent); // PDF 1.1 - the key, or the abBreviations?
DECLARE_PDF_NAME(Interpolate);
DECLARE_PDF_NAME(Width);

// values
DECLARE_PDF_NAME(DeviceGray);
DECLARE_PDF_NAME(DeviceRGB);
DECLARE_PDF_NAME(DeviceCMYK);
DECLARE_PDF_NAME(Indexed);
DECLARE_PDF_NAME(ASCIIHexDecode);
DECLARE_PDF_NAME(ASCII85Decode);
DECLARE_PDF_NAME(LZWDecode);
DECLARE_PDF_NAME(FlateDecode);  // PDF 1.2
DECLARE_PDF_NAME(RunLengthDecode);
DECLARE_PDF_NAME(CCITTFaxDecode);
DECLARE_PDF_NAME(DCTDecode);

#define HANDLE_NAME_ABBR(obj,longName,shortName) if (obj->isName(#shortName)) return &longName;


static SkPdfNativeObject* inlineImageKeyAbbreviationExpand(SkPdfNativeObject* key) {
    if (!key || !key->isName()) {
        return key;
    }

    // TODO(edisonn): use autogenerated code!
    HANDLE_NAME_ABBR(key, BitsPerComponent, BPC);
    HANDLE_NAME_ABBR(key, ColorSpace, CS);
    HANDLE_NAME_ABBR(key, Decode, D);
    HANDLE_NAME_ABBR(key, DecodeParms, DP);
    HANDLE_NAME_ABBR(key, Filter, F);
    HANDLE_NAME_ABBR(key, Height, H);
    HANDLE_NAME_ABBR(key, ImageMask, IM);
//    HANDLE_NAME_ABBR(key, Intent, );
    HANDLE_NAME_ABBR(key, Interpolate, I);
    HANDLE_NAME_ABBR(key, Width, W);

    return key;
}

static SkPdfNativeObject* inlineImageValueAbbreviationExpand(SkPdfNativeObject* value) {
    if (!value || !value->isName()) {
        return value;
    }

    // TODO(edisonn): use autogenerated code!
    HANDLE_NAME_ABBR(value, DeviceGray, G);
    HANDLE_NAME_ABBR(value, DeviceRGB, RGB);
    HANDLE_NAME_ABBR(value, DeviceCMYK, CMYK);
    HANDLE_NAME_ABBR(value, Indexed, I);
    HANDLE_NAME_ABBR(value, ASCIIHexDecode, AHx);
    HANDLE_NAME_ABBR(value, ASCII85Decode, A85);
    HANDLE_NAME_ABBR(value, LZWDecode, LZW);
    HANDLE_NAME_ABBR(value, FlateDecode, Fl);  // (PDF 1.2)
    HANDLE_NAME_ABBR(value, RunLengthDecode, RL);
    HANDLE_NAME_ABBR(value, CCITTFaxDecode, CCF);
    HANDLE_NAME_ABBR(value, DCTDecode, DCT);

    return value;
}

SkPdfImageDictionary* SkPdfNativeTokenizer::readInlineImage() {
    // BI already processed
    fUncompressedStream = skipPdfWhiteSpaces(fUncompressedStream, fUncompressedStreamEnd);
    if (fUncompressedStream >= fUncompressedStreamEnd) {
        return NULL;
    }

    SkPdfImageDictionary* inlineImage = (SkPdfImageDictionary*)fAllocator->allocObject();
    SkPdfNativeObject::makeEmptyDictionary(inlineImage);
    //  PUT_TRACK_STREAM_ARGS_EXPL(fStreamId, fUncompressedStream - fUncompressedStreamStart,
    //                             fUncompressedStream - fUncompressedStreamStart)

    while (fUncompressedStream < fUncompressedStreamEnd) {
        SkPdfNativeObject* key = fAllocator->allocObject();
        fUncompressedStream = nextObject(fUncompressedStream, fUncompressedStreamEnd, key,
                                         fAllocator, fDoc);
        // PUT_TRACK_STREAM_ARGS_EXPL2(fStreamId, fUncompressedStreamStart)s

        if (key->isKeyword() && key->lenstr() == 2 &&
                    key->c_str()[0] == 'I' && key->c_str()[1] == 'D') { // ID
            fUncompressedStream = readInlineImageStream(fUncompressedStream, fUncompressedStreamEnd,
                                                        inlineImage, fDoc);
            return inlineImage;
        } else {
            SkPdfNativeObject* obj = fAllocator->allocObject();
            fUncompressedStream = nextObject(fUncompressedStream, fUncompressedStreamEnd, obj,
                                             fAllocator, fDoc);
            //  PUT_TRACK_STREAM_ARGS_EXPL2(fStreamId, fUncompressedStreamStart)s
            // TODO(edisonn): perf maybe we should not expand abBreviation like this
            inlineImage->set(inlineImageKeyAbbreviationExpand(key),
                             inlineImageValueAbbreviationExpand(obj));
        }
    }
    // TODO(edisonn): report end of data with inline image without an EI
    return inlineImage;
}