/*
 * Copyright (c) 2015 ARM Limited. All rights reserved.
 * SPDX-License-Identifier: Apache-2.0
 * 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 "mbed.h"       // this tells us to load mbed OS related functions
#include "tones.h"                   // list of all the tones and their frequencies
#include "simpleclient.h"
#include <string>
#include <sstream>
#include <vector>

Serial output(USBTX, USBRX);

PwmOut buzzer(D3);                   // our buzzer is a PWM output (pulse-width modulation)

static int BPM = 35;

EthernetInterface eth;

// These are example resource values for the Device Object
struct MbedClientDevice device = {
    "Manufacturer_String",      // Manufacturer
    "Type_String",              // Type
    "ModelNumber_String",       // ModelNumber
    "SerialNumber_String"       // SerialNumber
};

// Instantiate the class which implements LWM2M Client API (from simpleclient.h)
MbedClient mbed_client(device);

// this is our function that plays a tone. 
// Takes in a tone frequency, and after duration (in ms.) we stop playing again
static void playTone(int tone, int duration) {

    buzzer.period_us(1000000/tone);  //period in us
    buzzer.write(0.10f); // 10% duty cycle, otherwise it's too loud
    wait_us(1000*duration/2);  //play for half the length
    buzzer.write(0.0f);
    wait_us(1000*duration/2);  //silence for half the length

}

static void play_song(std::vector<int>* melody, std::vector<int>* duration) {
    
	output.printf("play_song\n\r");
	for (int i = 0; i < melody->size(); i++) {
        int tone = melody->at(0);
        // BPM is quarter notes per minute, so length in milliseconds is:
        int length = static_cast<int>(static_cast<float>(1000 / duration->at(0)) * (60000.0f / static_cast<float>(BPM * 1000)));
        
        // printf("tone %d, length %d, duration %d\r\n", tone, length, duration->at(0));
        
        if (melody->at(i) != 0) {
            playTone(melody->at(i), length);
        }
        else {
            buzzer = 0.0f;
            wait_ms(length);
        }
    }
}


/*
 * The buzzer contains two properties (notes, duration) and a function (play).
 * When the function play_song_from_cloud is executed, the notes and duration patterns are read,
 * and the song will be played.
 */
class BuzzerResource {
public:
    BuzzerResource() {
        // create ObjectID with metadata tag of 'buzzer', which is not defined by the LWM2M standard but we can use it for this demo
        buzzer_object = M2MInterfaceFactory::create_object("buzzer");
        M2MObjectInstance* buzzer_inst = buzzer_object->create_object_instance();

        // notes resource
        M2MResource* notes_res = buzzer_inst->create_dynamic_resource("notes", "",
            M2MResourceInstance::STRING, true); //observable

        // read and write
        notes_res->set_operation(M2MBase::GET_PUT_ALLOWED);
        
		// set initial pattern
        char notes_buffer[100];
        int notes_size = sprintf(notes_buffer,"262:277");
                    
        notes_res->set_value((const uint8_t*)notes_buffer,
                             (uint32_t)notes_size);
    
        // duration resource
        M2MResource* duration_res = buzzer_inst->create_dynamic_resource("duration", "",
            M2MResourceInstance::STRING, true);  //observable

        duration_res->set_operation(M2MBase::GET_PUT_ALLOWED);
        
		// set initial pattern
        char dur_buffer[100];
        int dur_size = sprintf(dur_buffer,"4:4");
                                
        duration_res->set_value((const uint8_t*)dur_buffer,
                                (uint32_t)dur_size);

        // play resource
	    M2MResource* play_res = buzzer_inst->create_dynamic_resource("play", "",
	        M2MResourceInstance::STRING, false);  //not observable

        play_res->set_operation(M2MBase::POST_ALLOWED);
                    
		play_res->set_execute_function(execute_callback(this, &BuzzerResource::play_song_cloud));


}

    M2MObject* get_object() {
        return buzzer_object;
    }

	/*TODO - move the actual call to play_song_cloud to the main function, to avoid
	  running it in interrupt context.  Use a flag or semaphore set by the callback and checked for
	  in the main function */
	  
    void play_song_cloud(void *) {

		output.printf("play song cloud triggered!\r\n");

        // read the object storing resources 'notes' and 'duration'
        M2MObjectInstance* inst = buzzer_object->object_instance();

        M2MResource* n_res = inst->resource("notes");

        // values in mbed Client are all buffers, and we need a vector of int's
        uint8_t* n_buffIn = NULL;
        uint32_t n_sizeIn;
        n_res->get_value(n_buffIn, n_sizeIn);

		output.printf("notes = %s\n\r",n_buffIn);
		
    	std::stringstream notesPattern((char*)n_buffIn, n_sizeIn);
    	
    	std::vector<int>* notes = new std::vector<int>;
    	{
        	std::string n_item;
        	while (std::getline(notesPattern, n_item, ':')) {
            	notes->push_back(atoi((const char*)n_item.c_str()));
         	}
     	}
    
        M2MResource* d_res = inst->resource("duration");

        // values in mbed Client are all buffers, and we need a vector of int's
        uint8_t* d_buffIn = NULL;
        uint32_t d_sizeIn;
        
        d_res->get_value(d_buffIn, d_sizeIn);
        
		output.printf("durations = %s\n\r",d_buffIn);
        
        //TODO: investigate why passing in d_sizeIn causes it to think there are always 0 durations 
    	//std::stringstream durationPattern((char*)d_buffIn, d_sizeIn);
    	std::stringstream durationPattern((char*)d_buffIn, 100);
    	    		
    	std::vector<int>* durations = new std::vector<int>;
    	{
        	std::string d_item;
           	while (std::getline(durationPattern, d_item, ':')) {
            	durations->push_back(atoi((const char*)d_item.c_str()));
        	}
    	}
   	
    	if (notes->size() != durations->size()) {
        	output.printf("Notes and duration have different sizes (%d vs %d), abort!\r\n", notes->size(), durations->size());
        	return;
    	}
    
    	play_song(notes, durations);
	}

private:
    M2MObject* buzzer_object;


};

// Entry point to the program
int main() {
 
    green = 1; // turn green off
    
    // This sets up the network interface configuration which will be used
    // by LWM2M Client API to communicate with mbed Device server.
    eth.init(); //Use DHCP
    eth.connect();

    output.printf("[ETH] IP address %s\r\n", eth.getIPAddress());
    output.printf("[ETH] Device name %s\r\n", MBED_ENDPOINT_NAME);

    // Create LWM2M Client API interface to manage register and unregister
    mbed_client.create_interface();

	// Create resource for interactions between the device and server
	BuzzerResource * buzzer_resource = new BuzzerResource();

    // Create LWM2M server object specifying mbed device server
    // information.
    M2MSecurity* register_object = mbed_client.create_register_object();

    // Add all the objects that you would like to register
    // into the list and pass the list for register API.
    M2MObjectList object_list;
    object_list.push_back(buzzer_resource->get_object());

    mbed_client.set_register_object(register_object);

    // Register with mbed Device Connector
    mbed_client.test_register(register_object, object_list);

    //TODO: should check for play command received and play song from main
    //      to avoid doing too much in interrupt context.
    	
    while (true) {
    	
    	wait_ms(25000);  //wait 25 seconds
    	output.printf("Updating registration\n\r");
    	mbed_client.test_update_register();  //update registration
    	
    }
      
 }

