/*
 * Copyright (C) 2015 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.
 */

#define LOG_TAG "radio_hal_tool"

#include <stdlib.h>
#include <string.h>

#include <cutils/log.h>
#include <hardware/hardware.h>
#include <hardware/radio.h>
#include <system/radio.h>
#include <system/radio_metadata.h>


// Global state variables.
const struct radio_tuner *hal_tuner = NULL;

void usage() {
    printf("Usage: "
            "./radio_hal_tool -l\n"
            "-l: List properties global to the Radio.\n"
    );
}

void list_all_properties(radio_hw_device_t *device) {
    radio_hal_properties_t hal_properties;
    device->get_properties(device, &hal_properties);
    printf("Class: %d\n"
           "Impl: %s\n"
           "Tuners: %d\n"
           "Bands: %d\n\n",
           hal_properties.class_id, hal_properties.implementor, hal_properties.num_tuners,
           hal_properties.num_bands);

    uint32_t i;
    for (i = 0; i < hal_properties.num_bands; i++) {
        printf("Band Information\n"
               "Type: %d\n"
               "Connected: %d\n"
               "Lower limit: %d\n"
               "Upper limit: %d\n"
               "Spacing: %d\n\n",
               hal_properties.bands[i].type,
               hal_properties.bands[i].antenna_connected,
               hal_properties.bands[i].lower_limit,
               hal_properties.bands[i].upper_limit,
               hal_properties.bands[i].num_spacings);
    }
}

void callback(radio_hal_event_t *event, void *cookie) {
    printf("\nEvent detected\n"
           "Type: %d\n", event->type);
}

void tune(radio_hw_device_t *device, int band_number) {
    int ret;
    radio_hal_properties_t hal_properties;
    ret = device->get_properties(device, &hal_properties);
    if (ret != 0) {
        printf("Err: get_properties returned: %d\n", ret);
        return;
    }

    if ((uint32_t) band_number >= hal_properties.num_bands) {
        printf("Tuner number range should be: [0, %d]\n", hal_properties.num_bands);
    }
    printf("Setting band config as:\n"
           "Type: %d\n"
           "Connected: %d\n"
           "Lower limit: %d\n"
           "Upper limit: %d\n"
           "Spacing: %d\n\n",
           hal_properties.bands[band_number].type,
           hal_properties.bands[band_number].antenna_connected,
           hal_properties.bands[band_number].lower_limit,
           hal_properties.bands[band_number].upper_limit,
           hal_properties.bands[band_number].num_spacings);
    int cookie = 0;
    ret = device->open_tuner(
        device, (const radio_hal_band_config_t *) (&(hal_properties.bands[band_number])), false,
        callback, &cookie, &hal_tuner);
    if (ret != 0) {
        printf("Err: open_tuner returned: %d\n", ret);
        return;
    }
    // It takes some time to apply the config which is currently set as 500ms in
    // the stub implementation.
    sleep(1);

    // Stub tuner implementation will regard this magic channel as a valid channel to tune.
    ret = hal_tuner->tune(hal_tuner, 87916, 0);
    if (ret != 0) {
        printf("Err: tune returned: %d\n", ret);
        return;
    }
    // In the stub implementation it takes ~100ms to tune to the channel and the
    // data is set rightafter.
    sleep(1);
}

void get_tuner_metadata(radio_hw_device_t *device) {
    // Get the metadata and print it.
    radio_program_info_t info;
    radio_metadata_allocate(&info.metadata, 87916, 0);
    int ret;
    ret = hal_tuner->get_program_information(hal_tuner, &info);
    if (ret != 0) {
        printf("Err: Get program info ret code: %d\n", ret);
        return;
    }

    // Print the info.
    printf("Metadata from the band\n");
    int i;
    for (i = 0; i < radio_metadata_get_count(info.metadata); i++) {
        radio_metadata_key_t key;
        radio_metadata_type_t type;
        void *value;
        size_t size;

        radio_metadata_get_at_index(info.metadata, i, &key, &type, &value, &size);

        printf("\nMetadata key: %d\n"
               "Type: %d\n", key, type);

        switch (type) {
            case RADIO_METADATA_TYPE_INT:
                printf("Int value: %d\n", *((int *) value));
                break;
            case RADIO_METADATA_TYPE_TEXT:
                printf("Text value: %s\n", (char *) value);
                break;
            case RADIO_METADATA_TYPE_RAW:
                printf("Raw value, skipping\n");
                break;
            case RADIO_METADATA_TYPE_CLOCK:
                printf("UTC Epoch: %lld\n"
                       "UTC Offset: %d\n",
                       (long long)((radio_metadata_clock_t *) value)->utc_seconds_since_epoch,
                       ((radio_metadata_clock_t *) value)->timezone_offset_in_minutes);
        }
    }

    // Close the tuner when we are done.
    ret = device->close_tuner(device, hal_tuner);
    if (ret != 0) {
        printf("Err: close_tuner returned: %d\n", ret);
    }
}

int main(int argc, char** argv) {
    // Open the radio module and just ask for the list of properties.
    const hw_module_t *hw_module = NULL;
    int rc;
    rc = hw_get_module_by_class(RADIO_HARDWARE_MODULE_ID, RADIO_HARDWARE_MODULE_ID_FM, &hw_module);
    if (rc != 0) {
        printf("Cannot open the hw module. Does the HAL exist? %d\n", rc);
        return -1;
    }

    radio_hw_device_t *dev;
    rc = radio_hw_device_open(hw_module, &dev);
    if (rc != 0) {
        printf("Cannot open the device. Check that HAL implementation. %d\n", rc);
        return -1;
    }
    printf("HAL Loaded!\n");

    // If this is a list properties command - we check for -l command.
    int list_properties = 0;
    // Get metadata.
    int get_metadata = 0;
    // Tune. Takes a tuner number (see bands obtainaed by list_properties).
    int should_tune = 0;
    int band_number = -1;

    int opt;
    while ((opt = getopt(argc, argv, "lmt:")) != -1) {
        switch (opt) {
            case 'l':
                list_properties = 1;
                break;
            case 't':
                should_tune = 1;
                band_number = atoi(optarg);
                break;
            case 'm':
                get_metadata = 1;
                break;
        }
    }

    if (list_properties) {
        printf("Listing properties...\n");
        list_all_properties(dev);
    } else {
        if (should_tune) {
            if (band_number < 0) {
                printf("Tuner number should be positive");
                return -1;
            }
            printf("Tuning to a station...\n");
            tune(dev, band_number);
        }
        if (get_metadata) {
            if (!hal_tuner) {
                printf("Please pass -t <band_number> to tune to a valid station to get metadata.");
                exit(1);
            }
            get_tuner_metadata(dev);
        }
    }
    return 0;
}