<!doctype html>
<html lang="en">
    <head>
        <!-- Bootstrap CSS -->
       <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootswatch/4.5.0/slate/bootstrap.min.css"  crossorigin="anonymous">

       <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
       <script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.14.0/js/all.min.js"></script>

       <title>${obj.name} BLE</title>
   </head>
   <body class="">
       <div id="app">
           <h1>${obj.name} Gatt ICD</h1>
           <div class="d-flex justify-content-between align-items-center">

               <ul>
                   <li> Generated with <a href="https://github.com/uprev-mrt/mrtutils/wiki/mrt-device" MrT BLE tool>mrt-ble</a> on ${obj.genTime}</li>
                   <li> Services: {{profile.services.length}}</li>
                   <li> Characteristics: {{char_count}}</li>
                   <li> Attributes: {{attrib_count}}</li>
                </ul>
                <div>
                    <!-- Rounded switch -->
                    <label class="switch">
                        <input type="checkbox" v-model="showAll">
                        <span class="slider round"></span>
                    </label>
                    <label>
                        Show All &nbsp;&nbsp;
                    </label>

                    <button class="btn btn-sm btn-info"  v-if="!bluetoothDevice.gatt.connected" id="connect" v-on:click="bleConnect(showAll)">Connect with BLE device</button>
                    <button class="btn btn-sm btn-error" v-if="bluetoothDevice.gatt.connected" onclick="bleDisconnect()">Di 
                </div>
            </div>
            
            <p>${obj.desc}</p>

    
    
        
            <div class="col-md-12">

                <div id="accordion" role="tablist" aria-multiselectable="true">
                    <service-item v-for="service in profile.services" v-bind:service="service" v-bind:key="service.uuid"></service-item>
                </div>
            </div>
        </div>
        
        
        <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
    
        <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
            <!-- <script src="ble.js"></script> -->
        <script >

Vue.component('char-item',{
                props: ['char'],
                methods: {
                    writeChar : function(char)
                    {
                        var buffer = new ArrayBuffer(char.size * char.arrayLen);
                        var data = new DataView(buffer);



                        var offset = 0;
                        var rawValue = 0;
                        for( i=0; i < char.arrayLen; i++ )
                        {

                            rawValue = char.reqValues[i] * char.coef;

 
                            offset = i* char.size;

                            switch(char.type)
                            {
                                case "enum":
                                case "uint8" : 
                                case "mask" :
                                    data.setUint8(offset, rawValue);
                                break;
                                case "int8" : 
                                    data.setInt8(offset, chr.rawValue);
                                break;
                                case "char" : 
                                    data.setUint8(offset, rawValue);
                                break;
                                case "uint16" : 
                                    data.setUint16(offset, rawValue);
                                break;
                                case "int16" : 
                                    data.setInt16(offset, rawValue);
                                break;
                                case "uint32" : 
                                    data.setUint32(offset, rawValue);
                                break;
                                case "int32" : 
                                    data.setInt32(offset, rawValue);
                                break;
                                case "int64" : 
                                    data.setBigInt64(offset, rawValue);
                                break;
                                case "uint64" : 
                                    data.setBigUint64(offset, rawValue);
                                break;
                                case "string":
                                    break;
                                default:
                                    break;
                            }
                        }

                        char.chr_handle.writeValue(data.buffer)
                        .then(function(){char.chr_handle.readValue();});


                    },
                    readChar : function(char)
                    {
                       char.chr_handle.readValue();

                    },
                    updateMask: function(char, event)
                    {
                        if(event.target.checked)
                        {
                            char.reqValues[0] = char.reqValues[0] | event.target.value;
                        }
                        else 
                        {
                            char.reqValues[0] = char.reqValues[0] & ~event.target.value;
                        }
                    },
                    toggleNotifications : function(char)
                    {
                        if(char.notificationsEnabled)
                        {
                            char.notificationsEnabled = false;
                            char.chr_handle.stopNotifications();
                        }
                        else{
                            char.notificationsEnabled = true;
                            char.chr_handle.startNotifications();
                        }

                        this.$forceUpdate();

                    }
                },
                template: `
                     <div  class="col-md-3" >
                        <div class="card mb-12 box-shadow char-card"> 
                            <div class="card-header "  v-bind:class="{enabled: char.enabled}"> 
                                <div class="d-flex justify-content-between align-items-center">
                                    <span >
                                        <i class="fa " v-if="char.icon != ''" :class="char.icon" aria-hidden="true"></i>
                                        <strong style="font-size: 1.6em">{{char.name}}</strong>
                                        <span>{{char.type}}</span><span v-if="char.arrayLen>1">[{{char.arrayLen}}]</span>
                                    </span>
                                    <a :href="char.url" target="_blank" v-if="char.uuid_type=='e16Bit'" v-bind:class="{enabled: char.enabled}"> 0x{{char.short_uuid}}</a>
                                    <span v-if="char.uuid_type!='e16Bit'"> 0x{{char.short_uuid}}</span>
                                </div>
                            </div>
                            <div class="card-body">
                                <div class="row">
                                    <div class="col-md-12">
                                        <p >{{char.desc}} </p>
                                    </div>
                                </div>
                                <div class="row"> 

                                    <!--  NORMAL VIEW -->
                                    <div class="col-md-12" style="text-align:right"  v-if="char.type!='mask'">
                                        <span v-if="char.arrayLen==1" style="font-size: 3em; align: right"> {{char.values[0]}}</span>
                                        <span v-if="char.arrayLen>1 && char.values[0]" style="font-size: 3em; align: right"> {{char.values}}</span>
                                        <span class="text-muted">{{char.unit}}</span>
                                    </div>

                                    <!--  MASK VIEW -->
                                    <div class="col-md-12"  v-if="char.type=='mask'" style="font-size: 1.3em;">
                                        <ul style="list-style-type:none;">
                                            <li v-for="val in char.vals">
                                                <label class="switch">
                                                    <input type="checkbox" :value="val.val" @change="updateMask(char,$event)" >
                                                    <span class="slider round"></span>
                                                </label>
                                                &nbsp;
                                                <span :class=" char.values[0] & val.val? 'bg-success dot' : 'bg-secondary dot'"></span>
                                                <label :class=" char.values[0] & val.val? 'enabled' : ''">
                                                    {{val.name}} 
                                                </label>
                                            </li>
                                        </ul>
                                    </div>
                                       
    
                                </div>
                                <div class="row"> 
                                    &nbsp;
                                </div>
                                        <div class="d-flex justify-content-between align-items-center">
                                            <small v-if="char.lastWrite[0]" class="text-muted">{{char.lastWrite}}</small>

                                            <div  v-if="char.perm.includes('W')" >
                                                <input type="text" v-if="char.type == 'string'"  v-model="char.reqValues[0]" value="">
                                                <input type="number"  v-if="char.type.includes('int')" v-for="(val,index) in char.reqValues"  v-model="char.reqValues[index]" value="">
                                                <select v-if="char.type == 'enum'"  v-model="char.reqValues[0]" value="">
                                                    <option v-for="val in char.vals" v-bind:value="val.val"> {{val.name}} </option>
                                                </select>
                                            </div>
                                            &nbsp;
                                            <div class="btn-group">
                                                <button type="button" class="btn btn-sm btn-outline-secondary" v-on:click="writeChar(char)" v-if="char.perm.includes('W')">Write</button>
                                                <button type="button" class="btn btn-sm btn-outline-secondary" v-on:click="readChar(char)" v-if="char.perm.includes('R')">Read</button>
                                                <button type="button" class="btn btn-sm btn-outline-secondary" v-on:click="toggleNotifications(char)" v-if="char.perm.includes('N')"><i :class="[char.notificationsEnabled ? 'fa-pause' : 'fa-play', 'fa']"  aria-hidden="true"></i></button>                                                
                                            </div>
                                        </div>
                                </div>
                            </div>
                        </div>
                    </div>`
            })

            Vue.component('service-item',{
                props: ['service'],
                template: `
                        <div class="card  box-shadow" > 
                            <div class="card-header collapsed d-block " :id="service.id+'_header'"  data-toggle="collapse" data-parent="#accordion" :href="'#'+service.id+'_body'" aria-expanded="false" :aria-controls="service.id+'_body'">
                                <div class="d-flex justify-content-between align-items-left">
                                    <h4 v-bind:class="{enabled: service.enabled}">
                                        <i class="fa " v-if="service.icon != ''" :class="service.icon" aria-hidden="true"></i>
                                        <span> {{service.name}} </span>
                                    </h4>
                                    <span >
                                        <a :href="service.url" target="_blank" v-if="service.uuid_type=='e16Bit'">0x{{service.short_uuid}}</a>
                                        <span  v-if="service.uuid_type!='e16Bit'"> {{service.uuid}}</span>
                                    </span>
                                </div>
                            </div>
                            <div :id="service.id+'_body'" class="collapse " role="tabpanel" :aria-labelledby="service.id+'_header'">
                                <div class="card-body ">
                                        <div class="row">
                                            <char-item v-for="char in service.characteristics" v-bind:char="char" :service_uuid="service.uuid" v-bind:key="char.uuid"></char-item>
                                        </div>
                                </div>
                            </div>
                        </div>`
            })
            
            var bluetoothDevice = {gatt:{connected:false}};
            var gattServer;
            var char_count =0;
            var attrib_count =0;
            var profile = ${obj.json};



            profile.services.forEach(svc => {
                attrib_count++;
                svc.characteristics.forEach( chr => {

                    char_count++;
                    attrib_count+=2;
                    svc.enabled = false;
                    if(chr.perm.toLowerCase().includes('n'))
                    {
                        attrib_count+=2;
                    }
                    chr.values = Array(chr.arrayLen);
                    chr.reqValues = Array(chr.arrayLen);
                    chr.lastWrite = Array(chr.arrayLen);
                    chr.enabled = false;
                    chr.notificationsEnabled = false;
                });
            });

            var app = new Vue({
                el: '#app',
                data: {
                 profile:  profile,
                 bluetoothDevice: bluetoothDevice,
                 showAll: true,
                 char_count: char_count,
                 attrib_count: attrib_count
                },
                methods:{
                    bleConnect: function(showAll)
                    {
                        connect(showAll);
                    }
                }
            })


    function findCharacteristicByUuid(uuid)
    {
        let ret = null;
        profile.services.forEach(svc => {
            svc.characteristics.forEach( chr => {
                if(chr.uuid.toLowerCase() == uuid)
                {
                        ret = chr; 
                        return chr;
                }
            }) ;
        });
      
        return ret;
    }

    function findServiceByUuid(uuid)
    {
        let ret = null;
        profile.services.forEach(svc => {
            if(svc.uuid.toLowerCase() == uuid)
                {
                        ret = svc; 
                        return svc;
                }
        });
      
        return ret;
    }


    function updateValue(uuid, value)
    {

        var chr = findCharacteristicByUuid(uuid);
        if(chr != null)
        {
            var arr = Array(chr.arrayLen);
            var newValue =0;
            if(chr.type == "string")
            {
                let decoder = new TextDecoder('utf-8');
                arr[0] = decoder.decode(value);
                chr.values = arr;
            }
            else
            {

                
                var offset =0;
                for( i=0; i < chr.arrayLen; i++)
                {
                    offset = chr.size * i;
                    switch(chr.type)
                    {
                        case "uint8" : 
                        case "enum":
                        case "mask":
                            newValue = value.getUint8(offset);
                            break;
                        case "int8" : 
                            newValue = value.getInt8(offset);
                            break;
                        case "char" : 
                            newValue = value.getUint8(offset);
                            break;
                        case "uint16" : 
                            newValue = value.getUint16(offset);
                            break;
                        case "int16" : 
                            newValue = value.getInt16(offset);
                            break;
                        case "uint32" : 
                            newValue = value.getUint32(offset);
                            break;
                        case "int32" : 
                            newValue= value.getInt32(offset);
                            break;
                        case "int64" : 
                            newValue = value.getBigInt64(offset);
                            break;
                        case "uint64" : 
                            newValue = value.getBigUint64(offset);
                            break;
                        default:
                            break;
                    }
    
 
                    newValue= newValue / chr.coef;
                    
                    arr[i] = newValue; 
                    if(chr.type == 'enum' && chr.vals.length > newValue)
                    {
                        arr[i] = chr.vals[newValue].name;
                    }
                }

                chr.values = arr;
            }

        }
        else
        {
            console.log("Failed to find UUID");
        }
    
    }


    function handleCharacteristicValueChanged(event) {
        updateValue(event.target.uuid, event.target.value);
    }

    
    function onDisconnected(event) {
    // Object event.target is Bluetooth Device getting disconnected.
    profile.services.forEach(svc => {
        svc.enabled = false;
        svc.characteristics.forEach( chr => {

                chr.values = Array(chr.arrayLen);
                chr.reqValues = Array(chr.arrayLen);
                chr.lastWrite = Array(chr.arrayLen);
                chr.enabled = false;
            });
        });
        alert("Bluetooth Device Disconnected");
        console.log('> Bluetooth Device disconnected');
        
    }


    function connect(showAll) 
    {

        var filters = [];
        var args= {}

        profile.services.forEach(svc => {
                filters.push(svc.uuid);
            });

        if(!showAll)
        {

            args = { optionalServices: filters, filters: [{services: filters}]};
        }
        else 
        {
            args = {"acceptAllDevices": showAll, optionalServices: filters};
        }

        if (navigator.bluetooth) 
        {
            console.log('Requesting Bluetooth device0');

            navigator.bluetooth.requestDevice(args)
            .then(device => {
                bluetoothDevice = device;
            console.log(device);
            console.log('> Name:      ' + device.name);
            console.log('> Id:        ' + device.id);
            console.log('> Connected: ' + device.gatt.connected);
            bluetoothDevice.addEventListener('gattserverdisconnected', onDisconnected);

            return device.gatt.connect();
            })
            .then(server => {
                gattServer = server;


                console.log('> Connected: ' + true);
                return server.getPrimaryServices();
            })
            .then(services => {

                for( i=0; i < services.length; i++)
                {
                    let svc = findServiceByUuid(services[i].uuid);
                    if(svc != null)
                    {
                        svc.enabled = true;
                    }

                    services[i].getCharacteristics()
                    .then( characteristics => {
                        console.log(characteristics);

                        for( a=0; a< characteristics.length; a++)
                        {

                            let chr = findCharacteristicByUuid(characteristics[a].uuid);
                            if(chr != null)
                            {
                                chr.chr_handle = characteristics[a];
                                chr.enabled = true;
                                //chr.notificationsEnabled = true;
                                //characteristics[a].startNotifications();
                            }
                            characteristics[a].addEventListener('characteristicvaluechanged',
                                    handleCharacteristicValueChanged);
                        
                            characteristics[a].readValue();
                            
                            
                        }
                    })
                }
                
            })
            .catch(error => {
                console.log("Error " + error);
                alert("Error: " + error);
            });
        }
        else 
        {
            alert('Web Bluetooth API is not available in this browser!');
            console.log('Web Bluetooth API is not available in this browser!')
        }
    }

            
        </script>
        <style>
            html, body {
                    margin: 5px;
                    padding: 0;
                }
            
            .char-card {
                margin-top: 2%;
            }



            table {
                border-collapse: collapse;
                }

                td {
                border: 1px solid #000000;
                text-align: left;
                padding: 8px;
                vertical-align: top;
                }

                .desc{
                font-size: 1.2em;
                }

                th {
                border: 1px solid #000000;
                text-align: left;
                padding: 8px;
                background-color: #94b2d3;
                }

                table.fixed { table-layout:auto; }
                table.fixed td { overflow:visible; }

                table.chars{
                table-layout:auto;
                }

                .card-header{
                padding: 0.3rem 1.2rem ;
                }


                .card-header .fa {
                transition: .3s transform ease-in-out;
                }
                .card-header .collapsed .fa {
                transform: rotate(90deg);
                }

                .enabled{
                    color: #62c462;
                }

                .switch {
                    position: relative;
                    display: inline-block;
                    width: 30px;
                    height: 18px;
                    }

                    /* Hide default HTML checkbox */
                    .switch input {
                    opacity: 0;
                    width: 0;
                    height: 0;
                    }

                    /* The slider */
                    .slider {
                    position: absolute;
                    cursor: pointer;
                    top: 0;
                    left: 0;
                    right: 0;
                    bottom: 0;
                    background-color: #ccc;
                    -webkit-transition: .4s;
                    transition: .4s;
                    }

                    .slider:before {
                    position: absolute;
                    content: "";
                    height: 14px;
                    width: 14px;
                    left: 2px;
                    bottom: 2px;
                    background-color: white;
                    -webkit-transition: .4s;
                    transition: .4s;
                    }

                    input:checked + .slider {
                    background-color: #2196F3;
                    }

                    input:focus + .slider {
                    box-shadow: 0 0 1px #2196F3;
                    }

                    input:checked + .slider:before {
                    -webkit-transform: translateX(12px);
                    -ms-transform: translateX(12px);
                    transform: translateX(12px);
                    }

                    /* Rounded sliders */
                    .slider.round {
                    border-radius: 30px;
                    }

                    .slider.round:before {
                    border-radius: 50%;
                    }

                    .dot {
                    height: 16px;
                    width: 16px;
                    border-radius: 50%;
                    display: inline-block;
                    }


        </style>
    </body>
</html>