/*
** Copyright 2007, 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 <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>

#define FAILIF(x, args...) do {          \
    if (x) {                             \
        fprintf(stderr, ##args);         \
        exit(1);                         \
    }                                    \
} while(0)

static void usage() {
    printf("Usage: brfpatch INPUT OUTPUT\n"
           "\n"
           "\tGenerates bluetooth firmware\n"
           "\n"
           "INPUT: Bluetooth script in ASCII format.\n"
           "       For TI BRF chips this can be generated from .bts files using the TI Bluetooth\n"
           "       script pad to save as .txt. This txt file can be used as input.\n"
           "       Alternately, run strings on the .bts and manually edit to change decimal\n"
           "       arguments into hex of the appropriate number of octets.\n"
           "       FORMAT: Send_HCI_xxxx OPCODE DATA1 DATA2 DATA3 ...\n"
           "       where OPCODE, DATA1 etc are one of:\n"
           "       0x12          (1 byte)\n"
           "       0x1234        (2 byte)\n"
           "       0x12345678    (4 byte)\n"
           "       \"0123456789ABCDEF0123\"            (multibyte)\n"
           "       \"01:23:45:67:89:AB:CD:EF:01:23\"   (multibyte)\n"
           "\n"
           "OUTPUT: Binary firmware\n"
           "        FORMAT: 0x01 OPCODE DATA_LEN DATA\n");
    exit(1);
}

static void dump_record(FILE *fpo, unsigned short opcode, unsigned char len,
                        unsigned char *data) {

    unsigned char prefix = 0x01;  // H4 UART command packet
    fwrite(&prefix, 1, 1, fpo);
    fwrite(&opcode, 2, 1, fpo);  // opcode
    fwrite(&len, 1, 1, fpo);     // data length
    fwrite(data, len, 1, fpo);   // data
}

// advance beyond next whitespace. Return -1 if end of string reached
static int advance(char **buf) {
    char *b = *buf;
    while (*b && !isspace(*b))
        b++;
    while (*b && isspace(*b))
        b++;
    *buf = b;
    if (!(*b))
        return -1;
    return 0;
}

static void process_line(FILE *file_out, char *buf, char *buffer) {
    FAILIF(strncmp(buf, "Send_", 5) != 0, "Not expecting: %s\n", buffer);


    unsigned int opcode;

    FAILIF(advance(&buf), "Could not find opcode in: %s\n", buffer);
    FAILIF(sscanf(buf, "0x%04x\n", &opcode) != 1,
           "Could not find opcode in: %s\n", buffer);


    unsigned char data[1024];
    unsigned char *dp = data;

    while (!advance(&buf)) {
        switch (*buf) {
        case '"':
            buf++;
            while (*buf != '"' && *buf != 0) {
                FAILIF(dp > data + sizeof(data),
                       "Too much data: %s\n", buffer);
                FAILIF(sscanf(buf, "%02x", (unsigned int *)dp) != 1,
                       "Error parsing (%d): %s\n", __LINE__, buffer);
                dp++;
                buf += 2;
                if (*buf == ':')
                    buf++;
            }
            break;
        case '0':
            buf++;
            FAILIF(*buf != 'x', "Error parsing: %s\n", buffer);
            buf++;

            // find length of this piece of data
            char *end = buf;
            while (isalnum(*end))
                end++;

            // switch on length
            switch ((unsigned int)end - (unsigned int)buf) {
            case 2:
                FAILIF(sscanf(buf, "%02x", (unsigned int *)dp) != 1,
                       "Error parsing (%d): %s\n", __LINE__, buffer);
                buf += 2;
                dp += 1;
                break;
            case 4:
                FAILIF(sscanf(buf, "%04x", (unsigned int *)dp) != 1,
                       "Error parsing (%d): %s\n", __LINE__, buffer);
                buf += 4;
                dp += 2;
                break;
            case 6:
                FAILIF(sscanf(buf, "%06x", (unsigned int *)dp) != 1,
                       "Error parsing (%d): %s\n", __LINE__, buffer);
                buf += 6;
                dp += 3;
                break;
            case 8:
                FAILIF(sscanf(buf, "%08x", (unsigned int *)dp) != 1,
                       "Error parsing (%d): %s\n", __LINE__, buffer);
                buf += 8;
                dp += 4;
                break;
            case 16:
                dp += 4;
                FAILIF(sscanf(buf, "%08x", (unsigned int *)dp) != 1,
                       "Error parsing (%d): %s\n", __LINE__, buffer);
                buf += 8;
                dp -= 4;
                FAILIF(sscanf(buf, "%08x", (unsigned int *)dp) != 1,
                       "Error parsing (%d): %s\n", __LINE__, buffer);
                buf += 8;
                dp += 8;
                break;
            default:
                FAILIF(1, "Error parsing (%d): %s\n", __LINE__, buffer);
            }
            break;
        default:
            FAILIF(1, "Error parsing (%d): %s\n", __LINE__, buffer);
        }
    }

    dump_record(file_out, opcode, dp - data, data);
}


int main(int argc, char **argv) {

    if (argc != 3)
        usage();

    FILE *file_in = fopen(argv[1], "r");
    FAILIF(!file_in, "Could not open %s: %s\n", argv[1], strerror(errno));

    FILE *file_out = fopen(argv[2], "w+");
    FAILIF(!file_out, "Could not open %s: %s\n", argv[2], strerror(errno));

    char buffer[1024];
    char *buf;

    while (fgets(buffer, 1024, file_in) != NULL) {
        buf = buffer;
        while (*buf && isspace(*buf))
            buf++;
        switch (*buf) {
        case 'S':
            process_line(file_out, buf, buffer);
            break;
        case 'W':  // Wait_HCI_Command... meta-data, not needed
        case '#':
        case 0:
            continue;
        default:
            FAILIF(1, "Don't know what to do with: %s\n", buffer);
        }
    }

    fclose(file_in);
    fclose(file_out);

    return 0;
}