/**
 *
 * Copyright (c) 2006 Henrik Sundts and Petr Svarovsky 
 *
 * This code is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This code is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * More information about GNU Lesser General Public License
 * can be obtained from the
 * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, MA  02111-1307  USA
 *
 * @authors Henrik Sundt and Petr Svarovsky 
 */
 
// Code for sensing physical proximity of another phone through bluetooth.

// The range of each phone's bluetooth antenna should be jammed with aluminium foil.

// Experimental code, not working perfectly. Works somehow with 2-4 phones.



// On startup, number of each phone must be set to a different number (1,2,3, etc)



// This code is written for Mobile Processing ( http://mobile.processing.org )

// and Java j2me



// Written by Petr Svarovsky (petrsvar at khio.no)

// and Henrik Sundt (hsundt at notam02.no)

// October 2006



// for Trolley Singers Project (see http://silver.avu.cz/trolley_singers )



import processing.sound.*;      // Sound library import

import processing.bluetooth.*;  // Bluetooth library import



PFont font;



Bluetooth bt;

Service[] services;

Device[] devices;





Sound sound1;

Sound sound2;

Sound sound3;

Sound sound4;

Sound sound5;

Sound sound6;

Sound sound7;

Sound sound8;

Sound sound9;

Sound sound10; 



String info1 = ""; // status information

String info2 = "";                 // status information

String info3 = "";                 // status information

String info4 = "";                 // status information

String info5 = "";                 // status information

String info6 = "";                 // status information



boolean startIt = false;           // true for start connection

int l = 0;                         // number of visible services



int loudness = 50;                   // sound volume





int[] phone1_client_of  = {  2, 3, 4, 5, 6, 7 };   int[] phone1_serves  = {  8, 9,10,11,12, 0 };

int[] phone2_client_of  = {  3, 4, 5, 6, 7, 8 };   int[] phone2_serves  = {  1, 9,10,11,12, 0 };

int[] phone3_client_of  = {  4, 5, 6, 7, 8, 9 };   int[] phone3_serves  = {  1, 2,10,11,12, 0 };

int[] phone4_client_of  = {  5, 6, 7, 8, 9,10 };   int[] phone4_serves  = {  1, 2, 3,11,12, 0 };

int[] phone5_client_of  = {  6, 7, 8, 9,10,11 };   int[] phone5_serves  = {  1, 2, 3, 4,12, 0 };

int[] phone6_client_of  = {  7, 8, 9,10,11,12 };   int[] phone6_serves  = {  1, 2, 3, 4, 5, 0 };

int[] phone7_client_of  = {  8, 9,10,11,12, 0 };   int[] phone7_serves  = {  1, 2, 3, 4, 5, 6 };

int[] phone8_client_of  = {  9,10,11,12, 1, 0 };   int[] phone8_serves  = {  2, 3, 4, 5, 6, 7 };

int[] phone9_client_of  = { 10,11,12, 1, 2, 0 };   int[] phone9_serves  = {  3, 4, 5, 6, 7, 8 };

int[] phone10_client_of = { 11,12, 1, 2, 3, 0 };   int[] phone10_serves = {  4, 5, 6, 7, 8, 9 };

int[] phone11_client_of = { 12, 1, 2, 3, 4, 0 };   int[] phone11_serves = {  5, 6, 7, 8, 9,10 };

int[] phone12_client_of = {  1, 2, 3, 4, 5, 0 };   int[] phone12_serves = {  6, 7, 8, 9,10,11 };

int[] i_am_client_of;                              int[] i_serve;





int my_phone_nr=0;



// Client and service thread:

// see the file "connectService" for some documentation on ConnectThread





class ConnectThread implements Runnable {

  public int service_nr;

  private int my_array_nr;

  public boolean conn_success;

  private int action;

  public Client cref;



  

  // actions:

  private int DO_NOTHING=0;

  private int DO_CONNLOOP=1;





  ConnectThread(int a) {

    this.my_array_nr = a;

    this.service_nr = -1;

    this.conn_success=false;

    this.action=DO_NOTHING;

    this.cref=null;

   }

   private void reset_action() {     // called from while-loop to make each action only happen once

     this.action=DO_NOTHING;

   }

      

   public void start_connloop() {

     action=DO_CONNLOOP;

   }

     



   public void run() {

     // wait for start signal

     while(action==DO_NOTHING)

       msleep(100);

       

     // ---------- infinte loop -------------------------------------------------------------

     while(true) {

       if(action==DO_CONNLOOP) {

         // try to connect:

         try {

           info2="try to connect in thread";

           this.cref = services[service_nr].connect();  // try to connect to service, will get exception if can't connect

           if(this.cref!=null) {

             this.conn_success=true;

             info2="connect succeeded in thread "+my_array_nr;

           } else {

             this.conn_success=false;

             info2="connect nullpointer in thread";

           }

         } catch (RuntimeException re) {  // if could not connect

           this.conn_success=false;

           info2="did not connect in thread";

         }

         // sleep for a while

         msleep(6000+my_random_threadpause);   // must be random to avoid sync



         // disconnect

         if(this.conn_success) {

           try {

             this.cref.stop();

             this.cref=null;

           } catch(PException pe) {

             info6="exception i cref.stop()";

           }

         }

         msleep(1000);  // wait a bit before trying to connect again

       }

     }

     // ---------- end of loop -------------------------------------------------------------

   }

  

   private void msleep(int sleepDelay) {

     try {

       Thread.currentThread().sleep(sleepDelay);

     } 

     catch (InterruptedException ie) {}

   }

}









int max_nr_threads=13;  // max number of possible threads  (phones numbered 1 to 12,  0 is a dummy)

ConnectThread[] service_connect_thread = new ConnectThread[max_nr_threads];  // possible threads, not yet active.

int my_service_thread_nr;



int my_random_threadpause;



boolean bt_running=false;  // whether bluetooth is running

boolean searching=false;  // whether is searching

boolean initiated=false;



boolean do_play_sound;  // if a sound is going to be played



void setup() { 

  background(255);           // Set background color to white

  stroke(0);                 // Set line drawing color to black

  fill(0);                   // Set fill drawing color to black



  font = loadFont(FACE_MONOSPACE, STYLE_PLAIN, SIZE_SMALL);         // Font adjustment

  textFont(font);                                                   // Font adjustment



  bt = new Bluetooth(this);                                         // Instantiate a new bluetooth object;



  //... should check if sounds are in directory, otherwise gets NullPointerExeption !



  sound1 = new Sound("1.wav", "audio/x-wav");                       // Instantiate sounds

  sound2 = new Sound("2.wav", "audio/x-wav");

  sound3 = new Sound("3.wav", "audio/x-wav");

  sound4 = new Sound("4.wav", "audio/x-wav");

  sound5 = new Sound("5.wav", "audio/x-wav");

  sound6 = new Sound("6.wav", "audio/x-wav");

  sound7 = new Sound("7.wav", "audio/x-wav");

  sound8 = new Sound("8.wav", "audio/x-wav");

  sound9 = new Sound("9.wav", "audio/x-wav");

  sound10 = new Sound("10.wav", "audio/x-wav");



  // create service threads:

  int i;

  for(i=0; i< max_nr_threads; i++)

    service_connect_thread[i] = new ConnectThread(i);   // create the threads (but not run yet)



  my_random_threadpause=random(2000);

}




Thread mythread;

boolean newthread=true;

boolean play_alert_sound=false;



void draw()

{

  // this code prevents second thread generated by keypress to run

  Thread thisthread=Thread.currentThread();

  if(newthread) {

     mythread=thisthread;

     newthread=false;

   }

   else if(thisthread != mythread) {

     try {

       Thread.currentThread().sleep(10000);  // unneeded thread sleeps for 10 secs

     } catch(InterruptedException ie)

     {}

     return;

  }

  // -- end ----------------------------------------------------



  if(play_alert_sound) {

    play_alert_sound=false;

    playSound(1);

    sleep(2000);

  }







  background(255);

  text("Key 0 : Change phone nr (now "+my_phone_nr+")", 2, 10);

  text("Key 1 : Start", 2, 20); 

  text("Key 2 : Search service", 2, 30); 

  text("Key 3 : Start communication", 2, 40); 

  text("Key 4 : Stop service", 2, 50);

  text("Key * : Volume down (now "+loudness+")", 2, 60); 

  text("Key # : Volume up", 2, 70);





  if(!startIt)

    info6="obs: Communication not started";

  else info6="";

    

  text(info1, 2, 70+12); 

  text(info2, 2, 70+12*2);

  text(info3, 2, 70+12*3); 

  text(info4, 2, 70+12*4);

  text(info5, 2, 70+12*5);

  text(info6, 2, 70+12*6);



 

  sleep(200);     // sleep at least 40 ms every draw - because of Java sound playback bug on N70



//  control ----------------------------------------------------->



  int i;



  if(startIt && !initiated) {

    for(i=0; i< max_nr_threads; i++) {

      if(service_connect_thread[i].service_nr!=-1) {  // if the service exists

        service_connect_thread[i].start_connloop();  // start the connect/disconnect-loop in the thread

        sleep(1000);  // 1 second gap between each start of thread

      }

    }

    initiated=true;

  }

    

    

  // if had success in last connect to any services, play sound

  if(startIt) {

    for(i=0; i< max_nr_threads; i++) {

      if(service_connect_thread[i].conn_success) {

        do_play_sound=true;

        break;

      }

    }

  }

   

 



  // PLAY SOUND



  if(do_play_sound) {

    info4="do play sound";

    do_play_sound=false;

    //... now playing only random sounds

    playSound(1+random(9));  // start playing a new sound

    sleep(2000);

  }

  else

    info4="dont play sound";  // info to be displayed on next draw()



    

 sleep(500);  // not stress the phone too much  //..... sleep longer to save power ?

     

    

//---  end of control ------------------------------------------------------------





}


void keyPressed() {

  switch (key){



  case '0': // change my phone number

    if(startIt)

      info1 = "cannot change, comm started";

    else {

      my_phone_nr++;

      if(my_phone_nr>12)

        my_phone_nr=1;

      set_my_phone_ref();

      info1 = "Phone number "+my_phone_nr;

     }

    break;

    

  case '1': // start service

    if(my_phone_nr==0)

      info1 = "Set phone number first! (key 0)";

    else if(bt_running)

      info1 = "service already started !";

    else {

    try {

      bt.start("trolley"+str(my_phone_nr));

      bt_running=true;

      info1 = "service start ok";

    }

    catch(RuntimeException re) {

      info1 = "service start failed";

    }

    }

    break;



  case '2':  // search service

    if(!bt_running)

      info1 = "start bluetooth first! (key 1)";

    else if(searching)

      info1 = "already searching !";

    else {

      searching=true;

      services = null;

      bt.find();

      info1 = "searching for service...";

    }

    break; 



  case '3': // start communication

  if(searching)

    info1 = "cannot start before done search.. please wait.";

  else if(l<=0)

    info1 = "cannot start before search (key 2)";

  else if(startIt)

    info1 = "comm already started";

  else {  // puh..., can start communication

      info1 = "communication started";

      start_service_threads();

      startIt = true;

    }

    break;  



  case '4': // stop service

    if(!bt_running)

      info1 = "already stopped";

    else if(startIt)

      info1 = "cannot stop, comm already started";

    else {

    try {

      bt.stop();

      bt_running=false;

      startIt=false;

      info1 = "service stop ok";

    }

    catch(RuntimeException re) {

      info1 = "service stop failed";

    }

    }

    break;

    

  case '*': // volume down

    loudness-=10;

    if(loudness< 0) loudness=0;

    set_volume_all(loudness);

    info1 = "Volume "+loudness;

    break;

     

  case '#': // volume up

    loudness+=10;

    if(loudness>100) loudness=100;

    set_volume_all(loudness);

    info1 = "Volume "+loudness;

    break;

  }

}


void sleep(int sleepDelay) {

  try {

    Thread.currentThread().sleep(sleepDelay);

  } 

  catch (InterruptedException ie) {

  }

}



void libraryEvent(Object library, int event, Object data) {

  Client tmpclient;

  int client_phone_nr;

  boolean success;

    

  if (library == bt) {

    switch (event) {



    case Bluetooth.EVENT_DISCOVER_DEVICE:     

      info2 = "still searching...";

      break;



    case Bluetooth.EVENT_DISCOVER_DEVICE_COMPLETED:

      devices = (Device[]) data;

      info2 = length(devices) + " devices found";

      break;



    case Bluetooth.EVENT_DISCOVER_SERVICE:

      info2 = "still searching...";

      break;



    case Bluetooth.EVENT_DISCOVER_SERVICE_COMPLETED:

      searching=false;  // done searching

      services = (Service[]) data;

      l = length(services);

      info2 = l + " services found";

      play_alert_sound=true;

      break;



    case Bluetooth.EVENT_CLIENT_CONNECTED:   // event generated by client trying to connect to my service

      tmpclient = (Client) data;

      info3 = "client connected";

      do_play_sound=true;

      

      break;

    }

  }

}



void playSound(int x) {

  switch(x) {

  case 1:

    sound1.play();

    break;

  case 2:

    sound2.play();

    break; 

   case 3:

    sound3.play();

    break; 

  case 4:

    sound4.play();

    break; 

  case 5:

    sound5.play();

    break;

   case 6:

    sound6.play();

    break;

  case 7:

    sound7.play();

    break; 

   case 8:

    sound8.play();

    break; 

  case 9:

    sound9.play();

    break; 

  case 10:

    sound10.play();

    break;    

  }

}



void set_volume_all(int l)

{

    sound1.volume(l);

    sound2.volume(l);

    sound3.volume(l);

    sound4.volume(l);

    sound5.volume(l);

    sound6.volume(l);

    sound7.volume(l);

    sound8.volume(l);

    sound9.volume(l);

    sound10.volume(l);

}


// ServiceConnectThread:

// - service_connect_thread[i] goes from 0 to 12

// - phone numbers 1 to 12  (0 is a dummy)

// - each thread keeps track of connection to every phone i'm a client of

// - each thread knows it's service number

// - phones that dont connect will never be active threads





void start_service_threads()

{

  // thread setup and start:

  int service_nr,phone_nr;

  String sname;

  

  // connect to services

  for(service_nr=0; service_nr< l; service_nr++) {

    sname = services[service_nr].name;

    

    // if is one of our phones

    if(sname.equals("trolley1") || sname.equals("trolley2") || sname.equals("trolley3") || sname.equals("trolley4") ||

       sname.equals("trolley5") || sname.equals("trolley6") || sname.equals("trolley7") || sname.equals("trolley8") ||

       sname.equals("trolley9") || sname.equals("trolley10") || sname.equals("trolley11") || sname.equals("trolley12"))

    {

      phone_nr = Integer.parseInt(sname.substring(7));  // convert phone name string to int

      if(phone_nr==i_am_client_of[0] || phone_nr==i_am_client_of[1] || phone_nr==i_am_client_of[2] ||  // if we are going to be a client,

         phone_nr==i_am_client_of[3] || phone_nr==i_am_client_of[4] || phone_nr==i_am_client_of[5])    // according to the array

      {

        if(service_connect_thread[phone_nr].service_nr == -1) { // if not already started                  

           service_connect_thread[phone_nr].service_nr = service_nr;       // reference to service nr

           new Thread(service_connect_thread[phone_nr]).start();  // start the thread (which will start in wait-modus)

         }

         else info1="error: service to connect twice on same";

      }

//      else

//                  println("i am not client of  phone_nr "+phone_nr);

    }

  }

}

 



void set_my_phone_ref()

{

  switch(my_phone_nr) {

    case 0:

      info1 = "error: tried with phone nr 0";

      break;

      

    case 1:

      i_am_client_of = phone1_client_of;     i_serve = phone1_serves;      break;

    case 2:

      i_am_client_of = phone2_client_of;     i_serve = phone2_serves;      break;

    case 3:

      i_am_client_of = phone3_client_of;     i_serve = phone3_serves;      break;

    case 4:

      i_am_client_of = phone4_client_of;     i_serve = phone4_serves;      break;

    case 5:

      i_am_client_of = phone5_client_of;     i_serve = phone5_serves;      break;

    case 6:

      i_am_client_of = phone6_client_of;     i_serve = phone6_serves;      break;

    case 7:

      i_am_client_of = phone7_client_of;     i_serve = phone7_serves;      break;

    case 8:

      i_am_client_of = phone8_client_of;     i_serve = phone8_serves;      break;

    case 9:

      i_am_client_of = phone9_client_of;     i_serve = phone9_serves;      break;

    case 10:

      i_am_client_of = phone10_client_of;    i_serve = phone10_serves;     break;

    case 11:

      i_am_client_of = phone11_client_of;    i_serve = phone11_serves;     break;

    case 12:

      i_am_client_of = phone12_client_of;    i_serve = phone12_serves;     break;

      

  }

}


void destroy()

{

    

    // for now, just try to stop bluetooth

    bt.stop();



}