/*
 * Copyright 2013 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 <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <getopt.h>

#define LOG_TAG "bdAddrLoader"

#include <cutils/log.h>
#include <cutils/properties.h>

#define FILE_PATH_MAX   100
#define BD_ADDR_LEN  6
#define BD_ADDR_STR_LEN 18


#define ARG_TYPE_PATH_FILE  0x11
#define ARG_TYPE_PATH_PROP  0x12

#define ARG_TYPE_DATA_HEX   0x21
#define ARG_TYPE_DATA_ASCII 0x22

typedef struct _ArgEl {
   const char *szSrc;    // Source Path
   int nPathType;        // Type of Source Path
   int nDataType;        // Type of Data
} ArgEl;

typedef ArgEl InArg;

#define DEFAULT_BDADDR_PROP "persist.service.bdroid.bdaddr"

typedef struct _OutArg {
   ArgEl dest;
   char  cSeperator;    // a character to be used for sperating like ':' of "XX:XX:XX:XX:XX:XX"
   char  bPrintOut;     // Print out bd addr in standard out or not
} OutArg;

typedef struct _LoadedData {
    union {
       unsigned char bin[BD_ADDR_LEN];
       char sz[BD_ADDR_STR_LEN];
    }data;
    int nDataType;
} LoadedBDAddr;

typedef enum _res {
    SUCCESS = 0,
    FAIL
} Res;

int hexa_to_ascii(const unsigned char* hexa, char* ascii, int nHexLen)
{
    int i, j;
    char hex_table[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
                'A', 'B', 'C', 'D', 'E', 'F'};

    for (i = 0, j = 0; i <nHexLen; i++, j += 2) {
        ascii[j] = hex_table[hexa[i] >> 4];
        ascii[j + 1] = hex_table[hexa[i] & 0x0F];
    }

    ascii[nHexLen*2] = '\0';

    ALOGI("hex_to_ascii() - converted Data (%s)", ascii);

    return SUCCESS;
}

int readBDAddrData(const char* szFilePath, unsigned char* addrData, int nDataLen)
{
    int nFd, nRdCnt;

    nFd = open(szFilePath, O_RDONLY);

    if (nFd < 0) {
        ALOGW("There is no Address File in FTM area : %s\n", szFilePath);
        return FAIL;
    }

    nRdCnt = read(nFd, addrData, nDataLen);
    if (nRdCnt != nDataLen) {
        ALOGE("Fail to read Address data from FTM area\n");
        close(nFd);
        return FAIL;
    }
    close(nFd);
    return SUCCESS;
}

void formattingBdAddr(char *szBDAddr, const char cSep)
{
    int i = 1, j = 0;
    int pos = 0;
    for (i=1; i<BD_ADDR_LEN; i++) {
       pos = strlen(szBDAddr);
       for (j=0; j<(BD_ADDR_LEN*2)-i*2; j++) {
          szBDAddr[pos-j] = szBDAddr[pos-j-1];
       }
       szBDAddr[pos-j]=cSep;
    }
}

int readBDAddr(InArg inArg, LoadedBDAddr *loadedBDAddr)
{
    Res res = FAIL;
    unsigned char addrData[BD_ADDR_LEN] = {0,};
    int nDataLen = 0;

    ALOGI("Read From %s by Path type(0x%2x), Data type (0x%2x)",
            inArg.szSrc, inArg.nPathType, inArg.nDataType);

    if (inArg.nPathType == ARG_TYPE_PATH_FILE) {
        switch (inArg.nDataType) {
        case ARG_TYPE_DATA_HEX:
            if (!readBDAddrData(inArg.szSrc, loadedBDAddr->data.bin, BD_ADDR_LEN)) {
                loadedBDAddr->nDataType = ARG_TYPE_DATA_HEX;
                return SUCCESS;
            }
            break;
        case ARG_TYPE_DATA_ASCII:
           if (!readBDAddrData(inArg.szSrc, (unsigned char *)loadedBDAddr->data.sz, BD_ADDR_STR_LEN)) {
                loadedBDAddr->nDataType = ARG_TYPE_DATA_ASCII;
                return SUCCESS;
            }
            break;
        default:
            return FAIL;
        }
    } else if (inArg.nPathType == ARG_TYPE_PATH_PROP) {
        char prop_value[PROPERTY_VALUE_MAX];
        switch (inArg.nDataType) {
        case ARG_TYPE_DATA_HEX:
            if (property_get(inArg.szSrc, prop_value, "") >= 0 && strlen(prop_value) < BD_ADDR_LEN) {
                strlcpy((char *)loadedBDAddr->data.bin, prop_value, BD_ADDR_LEN);
                loadedBDAddr->nDataType = ARG_TYPE_DATA_HEX;
                return SUCCESS;
            }
            break;
        case ARG_TYPE_DATA_ASCII:
            if (property_get(inArg.szSrc, prop_value, "") >= 0 && strlen(prop_value) < BD_ADDR_STR_LEN) {
                strlcpy(loadedBDAddr->data.sz, prop_value, BD_ADDR_STR_LEN);
                loadedBDAddr->nDataType = ARG_TYPE_DATA_ASCII;
                return SUCCESS;
            }
            break;
        default:
            return FAIL;
        }
    } else {
        ALOGE("Error invalid argument : (%d)", inArg.nPathType);
    }

    ALOGE("Fail to read BDAddr from %s", inArg.szSrc);
    return FAIL;
}

int writeBDAddr(OutArg outArg, LoadedBDAddr *loadedBDAddr)
{
    char szTmp[BD_ADDR_STR_LEN] = {0,};

    ALOGI("Output Data type(0x%2x), bPrintout(%d), bPath(%s)",
        outArg.dest.nDataType, outArg.bPrintOut, outArg.dest.szSrc);

    ALOGI("Loaded Data type(0x%2x)", loadedBDAddr->nDataType);

    if (outArg.dest.nDataType == ARG_TYPE_DATA_ASCII
            && loadedBDAddr->nDataType == ARG_TYPE_DATA_HEX) {
        if (!hexa_to_ascii(loadedBDAddr->data.bin, szTmp, BD_ADDR_LEN)) {
            memcpy(loadedBDAddr->data.sz, szTmp, BD_ADDR_STR_LEN);
            loadedBDAddr->nDataType = ARG_TYPE_DATA_ASCII;
        } else {
            ALOGE("Fail to convert data");
            return FAIL;
        }
    }

    if (loadedBDAddr->nDataType == ARG_TYPE_DATA_ASCII) {
       // check out which addr data is already formated
       if (strchr(loadedBDAddr->data.sz, '.') == NULL
               && strchr(loadedBDAddr->data.sz, ':') == NULL) {
           formattingBdAddr(loadedBDAddr->data.sz, outArg.cSeperator);
       }
    }
    // print out szBDAddr
    if (outArg.bPrintOut
            && loadedBDAddr->nDataType == ARG_TYPE_DATA_ASCII
            && strlen(loadedBDAddr->data.sz)==(BD_ADDR_STR_LEN-1)) {
       printf("%s",loadedBDAddr->data.sz);
       if (property_set(DEFAULT_BDADDR_PROP, loadedBDAddr->data.sz) < 0)
           ALOGE("Failed to set address in prop %s", DEFAULT_BDADDR_PROP);
    } else {
       ALOGE("Invalid Data is loaded : %s", loadedBDAddr->data.sz);
       return FAIL;
    }
    // TODO :: writing File or Property
    return SUCCESS;
}

int main(int argc, char *argv[])
{
    int nFd, nRdCnt;
    int c;

    InArg inArg;
    OutArg outArg;
    LoadedBDAddr loadedBDAddr;

    //initialize arg
    memset(&inArg, 0, sizeof(InArg));
    memset(&outArg, 0, sizeof(OutArg));
    memset(&loadedBDAddr, 0, sizeof(LoadedBDAddr));

    //load args;
    while((c=getopt(argc, argv, ":f:p:hsx")) != -1){
        switch(c){
        case 'f': // input path
            if (optarg != NULL) {
                ALOGI("option : f=%s", optarg);
                inArg.szSrc = optarg;
            } else {
                ALOGW("Invalid Argument(%s) of input path", optarg);
            }
            inArg.nPathType = ARG_TYPE_PATH_FILE;
            break;
        case 'p': // output path
            if (optarg != NULL) {
                ALOGI("option : p=%s", optarg);
                inArg.szSrc = optarg;
            } else {
                ALOGW("Invalid Argument(%s) of out Path", optarg);
            }
            inArg.nPathType = ARG_TYPE_PATH_PROP;
            break;
        case 'h': // data type to be read is hex
            ALOGI("option : h");
            inArg.nDataType = ARG_TYPE_DATA_HEX;
            break;
        case 's': // data type to be read is ascii
            ALOGI("option : s");
            inArg.nDataType = ARG_TYPE_DATA_ASCII;
            break;
        case 'x':
            ALOGI("option : x");
            outArg.bPrintOut = 1; //true
            break;
        default:
            ALOGW("Unknown option : %c", c);
            break;
        }
    }

    // setting up Arguments with default value
    outArg.cSeperator = ':';
    outArg.dest.nDataType = ARG_TYPE_DATA_ASCII;

    // load bd addr and print out bd addr in formated ascii
    if (readBDAddr(inArg, &loadedBDAddr)) {
        ALOGE("Fail to load data !!");
        return FAIL;
    }

    if (writeBDAddr(outArg, &loadedBDAddr)) {
        ALOGE("Fail to write data !!");
        return FAIL;
    }

    return 1;
}