/***************************************************************************
 *   Copyright (C) 2003 by Mattia Merzi                                    *
 *   ottobit@ferrara.linux.it                                              *
 *                                                                         *
 *   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 2 of the License, or     *
 *   (at your option) any later version.                                   *
 ***************************************************************************/

#define TRUE 1
#define FALSE !TRUE

#include <kdebug.h>

static struct
{
    const char  *str;
    uint16_t rev;
}
csr_map[] = {
    { "HCI 11.2 (bc01b)",   114 },
    { "HCI 11.3 (bc01b)",   115 },
    { "HCI 12.1 (bc01b)",   119 },
    { "HCI 12.3 (bc01b)",   134 },
    { "HCI 12.7 (bc01b)",   188 },
    { "HCI 12.8 (bc01b)",   218 },
    { "HCI 12.9 (bc01b)",   283 },
    { "HCI 13.10 (bc01b)",  309 },
    { "HCI 13.11 (bc01b)",  351 },
    { "HCI 16.4 (bc01b)",   523 },
    { "HCI 14.3 (bc02x)",   272 },
    { "HCI 14.6 (bc02x)",   336 },
    { "HCI 14.7 (bc02x)",   373 },
    { "HCI 14.8 (bc02x)",   487 },
    { "HCI 15.3 (bc02x)",   443 },
    { "HCI 16.4 (bc02x)",   525 },
    { NULL, 0}
};



BluezConnector::KBTDevice::KBTDevice(struct hci_dev_info* di) {
    memcpy(&device_info,di,sizeof(struct hci_dev_info));
    init(&device_info);
}

void BluezConnector::KBTDevice::init(struct hci_dev_info* di) {
    kdDebug() << di->name << hci_dtypetostr(di->type) << endl
         << "Flaaaaags: " << di->flags << endl;
    dev = (char*)malloc(strlen(di->name));
    strcpy(dev,di->name);
    setDevId(di->dev_id);
    setType(di->type);
    kdDebug() << "test bit: " << hci_test_bit(HCI_UP, &di->flags) << endl;
    setFlags(di->flags);
    kdDebug() << "getStatus(): " << getStatus() << endl;
    setBDAddr(di);
    setFeatures(di->features);
    devopen = FALSE;
    manufacturer[0] = '\0';
}

void BluezConnector::KBTDevice::reinit() {
    //  uint16_t tmp_dev_id = device_info.dev_id;
    //  bzero(&device_info,sizeof(struct hci_dev_info));
    //  device_info.dev_id = tmp_dev_id;
    if( ioctl(getfd(), HCIGETDEVINFO, (void*)&device_info) ) {
        kdDebug() << "!!! WARNING !!!" << endl << "cannot reinit device" << endl;
    }
    init(&device_info);
}

BluezConnector::KBTDevice::~KBTDevice() {
    if (devOpen())
        close(getfd());
    kdDebug() << "KBTDevice: RIP" << endl;
}

void BluezConnector::KBTDevice::openDev() {
    setfd(hci_open_dev(getDevId()));
    if (getfd() < 0) {
        kdDebug() << "Can't open device " << getDev() << endl;
        kdDebug() << strerror(errno) << errno << endl;
        exit(1);
    }
    devopen = TRUE;
}

const char* BluezConnector::KBTDevice::getName() {
    if (!devOpen())
        openDev();
    if (hci_read_local_name(getfd(), sizeof(name), name, 1000) < 0) {
        if (errno != ETIMEDOUT) {
            kdDebug() << "Can't read local name on " << getDev() << endl
                 << strerror(errno) << errno << endl
                 << "defaulting to " << getDev() << endl;
        } // ETIMEDOUT error is quite normal, device is down ... I think ! :)
        strcpy(name,getDev());
    }
    return name;
}

void BluezConnector::KBTDevice::setBDAddr(struct hci_dev_info* _di) {
    bdaddr_t _bdaddr;
    baswap(&_bdaddr, &_di->bdaddr);
    bdaddr = batostr(&_bdaddr);
}

void BluezConnector::KBTDevice::setFlags(uint32_t flags) {
    //  kdDebug() << "Setting KBTDevice Values ..." << endl;
    setStatus(BTVALUE(hci_test_bit(HCI_UP, &flags)));
    if (getStatus()) {
        kdDebug() << "ok, getStatus() says: UP!" << endl;
        setIScan(BTVALUE(hci_test_bit(HCI_ISCAN, &flags)));
        kdDebug() << "getIScan() says: " << getIScan() << endl;
        setPScan(BTVALUE(hci_test_bit(HCI_PSCAN, &flags)));
        kdDebug() << "getPScan() says: " << getPScan() << endl;
        setAuth(BTVALUE(hci_test_bit(HCI_AUTH, &flags)));
        kdDebug() << "getAuth() says: " << getAuth() << endl;
        setEncrypt(BTVALUE(hci_test_bit(HCI_ENCRYPT, &flags)));
        kdDebug() << "getEncrypt() says: " << getEncrypt() << endl;
    }
    else {
        setIScan(BT_UNKNOWN);
        setPScan(BT_UNKNOWN);
        setAuth(BT_UNKNOWN);
        setEncrypt(BT_UNKNOWN);
    }
}

const char* BluezConnector::KBTDevice::getRevision()
{
    struct hci_version ver;

    if (!devOpen())
        openDev();
    if (hci_read_local_version(getfd(), &ver, 1000) < 0)
    {
        kdDebug() << "getRevision(): " << endl
             << "Can't read revision info on " << getDev() << endl
             << errno << ": " << strerror(errno) << endl;
        return EMPTY_STRING;
    }

    setManufacturer(ver.manufacturer);

    switch (ver.manufacturer)
    {
    case 0:
        return getRevEricsson();
        break;
    case 10:
        return getRevCsr(ver.hci_rev);
        break;
    default:
        return "Unsupported manufacturer";
        break;
    }
}

const char* BluezConnector::KBTDevice::getRevEricsson()
{
    struct hci_request rq;

    memset(&rq, 0, sizeof(rq));
    rq.ogf = 0x3f;
    rq.ocf = 0x000f;
    rq.cparam = NULL;
    rq.clen = 0;
    rq.rparam = &revision;
    rq.rlen = sizeof(revision);

    if (hci_send_req(getfd(), &rq, 1000) < 0)
    {
        kdDebug() << "getRevEricsson(): " << endl
             << "Can't read revision info on " << getDev() << endl
             << errno << ": " << strerror(errno);
        return "";
    }
    return revision+1;
}

const char* BluezConnector::KBTDevice::getRevCsr(uint16_t rev)
{
    int i;

    for (i = 0; csr_map[i].str; i++)
        if (csr_map[i].rev == rev)
        {
            strcpy(revision,csr_map[i].str);
            return revision;
        }

    strcpy(revision,"Unknown firmware");
    return revision;
}

int BluezConnector::KBTDevice::reset() {
    if( ioctl(getfd(), HCIDEVRESET, getDevId()) < 0 ) {
        kdDebug() << "reset(): " << endl;
        kdDebug() << "Reset failed " << getDev() << endl;
        kdDebug() << errno << ": " << strerror(errno);
        if (errno == EACCES) {
            return EACCES;
        }
        else {
            exit(1);
        }
    }
    return 0;
}

void BluezConnector::KBTDevice::devUp() {
    if (!getStatus()) {
        kdDebug() << "bringing up interface: " << getDev() << endl;
        if( ioctl(getfd(), HCIDEVUP, getDevId()) < 0 ) {
            kdDebug() << "devUp(): " << endl;
            kdDebug() << "Cannot bring interface " << getDev() << " up" << endl;
            kdDebug() << errno << ": " << strerror(errno);
            exit(1);
        }
        sleep(3); // this is needed, otherwise values obtained by
        // `ioctl(HCIGETDEVINFO)-ing' are wrong.
        // Three seconds is enough to have a sip of coffe ...
        reinit();
    }
}

void BluezConnector::KBTDevice::devDown() {
    if (getStatus()) {
        kdDebug() << "bringing down interface: " << getDev() << endl;
        if( ioctl(getfd(), HCIDEVDOWN, getDevId()) < 0 ) {
            kdDebug() << "devDown(): " << endl;
            kdDebug() << "Cannot bring interface " << getDev() << " down" << endl;
            kdDebug() << errno << ": " << strerror(errno);
            exit(1);
        }
        reinit();
    }
}

void BluezConnector::KBTDevice::setScanMode(bool iscan, bool pscan) {

  struct hci_dev_req dr;

  dr.dev_id  = getDevId();
  dr.dev_opt = SCAN_DISABLED;
  if( iscan&&(!pscan) )
    dr.dev_opt = SCAN_INQUIRY;
  else if( pscan&&(!iscan) )
    dr.dev_opt = SCAN_PAGE;
  else if( pscan&&iscan )
    dr.dev_opt = SCAN_PAGE | SCAN_INQUIRY;

  if( ioctl(getfd(), HCISETSCAN, (unsigned long)&dr) < 0 ) {
    kdDebug() << "Can't set scan mode on " << getDev()
         << errno << ": " << strerror(errno);
    exit(1);
  }

  reinit();
}

void BluezConnector::KBTDevice::changeDevName(const char* name) {
  if (hci_write_local_name(getfd(), name, 1000) < 0) {
    kdDebug() << "Can't change local name on " << getDev()
         << errno << ": " << strerror(errno);
    exit(1);
  }
}

void BluezConnector::KBTDevice::changeAuthentication(bool _auth) {
  struct hci_dev_req dr;

  if (!devOpen())
    openDev();

  dr.dev_id  = getDevId();
  dr.dev_opt = _auth?AUTH_ENABLED:AUTH_DISABLED;
 
  if (ioctl(getfd(),HCISETAUTH,(unsigned long)&dr) < 0) {
    kdDebug() << "Can't change authentication on " << getDev()
         << errno << ": " << strerror(errno);
    exit(1);
  }
  reinit();
}

void BluezConnector::KBTDevice::changeEncryption(bool _encrypt) {
  struct hci_dev_req dr;

  if (!devOpen())
    openDev();

  dr.dev_id  = getDevId();
  dr.dev_opt = _encrypt?ENCRYPT_P2P:ENCRYPT_DISABLED;
      
  if (ioctl(getfd(),HCISETENCRYPT,(unsigned long)&dr) < 0) {
    kdDebug() << "Can't change encryption on " << getDev()
         << errno << ": " << strerror(errno);
    exit(1);
  }
  reinit();
}

void BluezConnector::KBTDevice::changeClass
       (uint8_t service, uint8_t major, uint8_t minor) {
  uint32_t cod = 0;
  cod = (service << 16) |
        (major   <<  8) |
        (minor        ) ;
  
  if (!devOpen())
    openDev();

  if (0 > hci_write_class_of_dev(getfd(),cod,1000)) {
    kdDebug() << "Can't change class information " << getDev()
         << errno << ": " << strerror(errno);
    exit(1);
  }
}

void BluezConnector::KBTDevice::getClass
       (uint8_t* service, uint8_t* major, uint8_t* minor) {
  uint8_t cls[3];
  
  if (!devOpen())
    openDev();

  if (0 > hci_read_class_of_dev(getfd(),cls,1000)) {
    kdDebug() << "Can't read class information " << getDev()
         << errno << ": " << strerror(errno);
    exit(1);
  }

  *service = cls[2];
  *major   = cls[1];
  *minor   = cls[0];
}
