/*
 * Copyright (C) 2005 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <utils/Debug.h>

#include <utils/misc.h>

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>

namespace android {

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

static const char indentStr[] =
"                                                                            "
"                                                                            ";

const char* stringForIndent(int32_t indentLevel)
{
    ssize_t off = sizeof(indentStr)-1-(indentLevel*2);
    return indentStr + (off < 0 ? 0 : off);
}

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

static void defaultPrintFunc(void* cookie, const char* txt)
{
    printf("%s", txt);
}

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

static inline int isident(int c)
{
    return isalnum(c) || c == '_';
}

static inline bool isasciitype(char c)
{
    if( c >= ' ' && c < 127 && c != '\'' && c != '\\' ) return true;
    return false;
}

static inline char makehexdigit(uint32_t val)
{
    return "0123456789abcdef"[val&0xF];
}

static char* appendhexnum(uint32_t val, char* out)
{
    for( int32_t i=28; i>=0; i-=4 ) {
        *out++ = makehexdigit( val>>i );
    }
    *out = 0;
    return out;
}

static inline char makeupperhexdigit(uint32_t val)
{
    return "0123456789ABCDEF"[val&0xF];
}

static char* appendupperhexnum(uint32_t val, char* out)
{
    for( int32_t i=28; i>=0; i-=4 ) {
        *out++ = makeupperhexdigit( val>>i );
    }
    *out = 0;
    return out;
}

static char* appendcharornum(char c, char* out, bool skipzero = true)
{
    if (skipzero && c == 0) return out;

    if (isasciitype(c)) {
        *out++ = c;
        return out;
    }

    *out++ = '\\';
    *out++ = 'x';
    *out++ = makehexdigit(c>>4);
    *out++ = makehexdigit(c);
    return out;
}

static char* typetostring(uint32_t type, char* out,
                          bool fullContext = true,
                          bool strict = false)
{
    char* pos = out;
    char c[4];
    c[0] = (char)((type>>24)&0xFF);
    c[1] = (char)((type>>16)&0xFF);
    c[2] = (char)((type>>8)&0xFF);
    c[3] = (char)(type&0xFF);
    bool valid;
    if( !strict ) {
        // now even less strict!
        // valid = isasciitype(c[3]);
        valid = true;
        int32_t i = 0;
        bool zero = true;
        while (valid && i<3) {
            if (c[i] == 0) {
                if (!zero) valid = false;
            } else {
                zero = false;
                //if (!isasciitype(c[i])) valid = false;
            }
            i++;
        }
        // if all zeros, not a valid type code.
        if (zero) valid = false;
    } else {
        valid = isident(c[3]) ? true : false;
        int32_t i = 0;
        bool zero = true;
        while (valid && i<3) {
            if (c[i] == 0) {
                if (!zero) valid = false;
            } else {
                zero = false;
                if (!isident(c[i])) valid = false;
            }
            i++;
        }
    }
    if( valid && (!fullContext || c[0] != '0' || c[1] != 'x') ) {
        if( fullContext ) *pos++ = '\'';
        pos = appendcharornum(c[0], pos);
        pos = appendcharornum(c[1], pos);
        pos = appendcharornum(c[2], pos);
        pos = appendcharornum(c[3], pos);
        if( fullContext ) *pos++ = '\'';
        *pos = 0;
        return pos;
    }
    
    if( fullContext ) {
        *pos++ = '0';
        *pos++ = 'x';
    }
    return appendhexnum(type, pos);
}

void printTypeCode(uint32_t typeCode, debugPrintFunc func, void* cookie)
{
    char buffer[32];
    char* end = typetostring(typeCode, buffer);
    *end = 0;
    func ? (*func)(cookie, buffer) : defaultPrintFunc(cookie, buffer);
}

void printHexData(int32_t indent, const void *buf, size_t length,
    size_t bytesPerLine, int32_t singleLineBytesCutoff,
    size_t alignment, bool cStyle,
    debugPrintFunc func, void* cookie)
{
    if (alignment == 0) {
        if (bytesPerLine >= 16) alignment = 4;
        else if (bytesPerLine >= 8) alignment = 2;
        else alignment = 1;
    }
    if (func == NULL) func = defaultPrintFunc;

    size_t offset;
    
    unsigned char *pos = (unsigned char *)buf;
    
    if (pos == NULL) {
        if (singleLineBytesCutoff < 0) func(cookie, "\n");
        func(cookie, "(NULL)");
        return;
    }
    
    if (length == 0) {
        if (singleLineBytesCutoff < 0) func(cookie, "\n");
        func(cookie, "(empty)");
        return;
    }
    
    if ((int32_t)length < 0) {
        if (singleLineBytesCutoff < 0) func(cookie, "\n");
        char buf[64];
        sprintf(buf, "(bad length: %zu)", length);
        func(cookie, buf);
        return;
    }
    
    char buffer[256];
    static const size_t maxBytesPerLine = (sizeof(buffer)-1-11-4)/(3+1);
    
    if (bytesPerLine > maxBytesPerLine) bytesPerLine = maxBytesPerLine;
    
    const bool oneLine = (int32_t)length <= singleLineBytesCutoff;
    bool newLine = false;
    if (cStyle) {
        indent++;
        func(cookie, "{\n");
        newLine = true;
    } else if (!oneLine) {
        func(cookie, "\n");
        newLine = true;
    }
    
    for (offset = 0; ; offset += bytesPerLine, pos += bytesPerLine) {
        long remain = length;

        char* c = buffer;
        if (!oneLine && !cStyle) {
            sprintf(c, "0x%08x: ", (int)offset);
            c += 12;
        }

        size_t index;
        size_t word;
        
        for (word = 0; word < bytesPerLine; ) {

#ifdef HAVE_LITTLE_ENDIAN
            const size_t startIndex = word+(alignment-(alignment?1:0));
            const ssize_t dir = -1;
#else
            const size_t startIndex = word;
            const ssize_t dir = 1;
#endif

            for (index = 0; index < alignment || (alignment == 0 && index < bytesPerLine); index++) {
            
                if (!cStyle) {
                    if (index == 0 && word > 0 && alignment > 0) {
                        *c++ = ' ';
                    }
                
                    if (remain-- > 0) {
                        const unsigned char val = *(pos+startIndex+(index*dir));
                        *c++ = makehexdigit(val>>4);
                        *c++ = makehexdigit(val);
                    } else if (!oneLine) {
                        *c++ = ' ';
                        *c++ = ' ';
                    }
                } else {
                    if (remain > 0) {
                        if (index == 0 && word > 0) {
                            *c++ = ',';
                            *c++ = ' ';
                        }
                        if (index == 0) {
                            *c++ = '0';
                            *c++ = 'x';
                        }
                        const unsigned char val = *(pos+startIndex+(index*dir));
                        *c++ = makehexdigit(val>>4);
                        *c++ = makehexdigit(val);
                        remain--;
                    }
                }
            }
            
            word += index;
        }

        if (!cStyle) {
            remain = length;
            *c++ = ' ';
            *c++ = '\'';
            for (index = 0; index < bytesPerLine; index++) {

                if (remain-- > 0) {
                    const unsigned char val = pos[index];
                    *c++ = (val >= ' ' && val < 127) ? val : '.';
                } else if (!oneLine) {
                    *c++ = ' ';
                }
            }
            
            *c++ = '\'';
            if (length > bytesPerLine) *c++ = '\n';
        } else {
            if (remain > 0) *c++ = ',';
            *c++ = '\n';
        }

        if (newLine && indent) func(cookie, stringForIndent(indent));
        *c = 0;
        func(cookie, buffer);
        newLine = true;
        
        if (length <= bytesPerLine) break;
        length -= bytesPerLine;
    }

    if (cStyle) {
        if (indent > 0) func(cookie, stringForIndent(indent-1));
        func(cookie, "};");
    }
}

}; // namespace android

