/*
 * BellDownloader.java
 *
 * Created on 9 Июнь 2007 г., 21:12
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */


package belldownloader;

import javax.comm.*;
import java.io.*;
import java.util.*;

/**
 *
 * @author Trol
 */
public class BellDownloader { // implements SerialPortEventListener,  CommPortOwnershipListener {

    private CommPortIdentifier portId;
    private SerialPort sPort;
    private InputStream is;
    private OutputStream os;
    
    static final int CODE_SERV_INIT = 0xAA; // код, посылаемый устройству для показания активности
    static final int CODE_DEV_READY = 0x05; // код, посылаемый устройством как признак его готовности
    static final int CODE_DEV_FINALY = 0x08; // код, посылаемый устройством как признак установления связи
    static final int CODE_SERV_READY = 0x10; // код, посылаемый в ответ сервером, подтверждающий готовность
    
    static final int CODE_MODE_READ =  0x01; // код, посылаемый сервером для чтения памяти устройства
    static final int CODE_MODE_WRITE = 0x02; // код, посылаемый сервером для записи памяти устройства
    static final int CODE_MODE_PLAY =  0x03; // код, посылаемый сервером для проигрывания мелодии устройством
    static final int CODE_MODE_NOTE =  0x04; // код, посылаемый сервером для проигрывания устройством ноты
    static final int CODE_MODE_OFF  =  0x05; // код, посылаемый сервером для выключения питания устройства
    
    
    static final int CODE_DEV_OK        = 0x27;  // код удачного окончания операции, выдаваесмый устройством 

    static final int chipAddr[] = { 0xA0, 0xA0, 0xA0 };            // i2c адреса микросхем памяти
    static final int chipSize[] = { 32*1024, 32*1024, 32*1024 };   // объемы микросхем памяти
    static final int WRITE_PAGE_SIZE = 64;                         // длина страницы, которыми ведется запись  
    
    
    private boolean opened;
    
    static final String[] names = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"};
    static final int notes_count = 12;
    
    BufferedReader stdin;
    
    
    /** Creates a new instance of BellDownloader */
    public BellDownloader() {
        stdin = new BufferedReader(new InputStreamReader(System.in));
    }
    
    
    
    // посылает устройству код и ожидает нужного ответа. если ответ не приходит, продолжает слать запрос
    public final void sendAndWaitResponseCode(int codeSend, int codeResp, String strSendCode, String strRespCode) {
        System.out.println("Sending " + strSendCode + " code");
        System.out.println("Waiting " + strRespCode + " code...");            
        int c = codeResp+1; // != codeResp
        try {
            os.write(codeSend);
            int emptyCount = 0;        
            do {                
                if ( is.available() > 0 ) {
                    c = is.read();
                    System.out.println("  code = " + c);
                } else {
                    emptyCount++;
                    if ( emptyCount > 5 ) {
                        emptyCount = 0;
                        os.write(codeSend);
                    } else
                        Thread.sleep(100); 
                 }
            } while ( c != codeResp  );
        } catch (Exception ex) {
            System.out.println("> "+ex);
        }        
    }

    
    
    // открывает указаный ком-порт, иннициирует сеанс связи и ожидает от устройства подтверждения связи
    public final boolean open(String name) {
        System.out.println("Getting port...");
	try {
	    portId = CommPortIdentifier.getPortIdentifier(name);
	} catch (NoSuchPortException e) {            
	    e.printStackTrace();
            return false;
	}
        System.out.println("Opening "+name+"...");
	try {
	    sPort = (SerialPort)portId.open("BellDownloader", 30000);
	} catch (PortInUseException e) {
            e.printStackTrace();	    
            return false;
	}
        System.out.println("Setting io...");
	try {
	    os = sPort.getOutputStream();
	    is = sPort.getInputStream();
	} catch (IOException e) {
	    sPort.close();
            e.printStackTrace();	    
	}
        System.out.println("Setting params...");
        try {
	    sPort.setSerialPortParams(2400, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort. PARITY_NONE);
        } catch ( UnsupportedCommOperationException e) {
            sPort.close();
            e.printStackTrace();
            return false;
        }

        try {
            // 1. УСТАНОВЛЕНИЕ СВЯЗИ С УСТРОЙСТВОМ
            // 1.1. Шлем устройству код признак активности сервера
            System.out.println("Waiting for device...");
            while ( is.available() <= 0 )
                os.write(CODE_SERV_INIT);            
            // 1.2 Ждем кода готовности
            System.out.println("Waiting ready code...");
            int c;
            do {
                c = is.read();
                System.out.println("code = " + c);
            } while ( c != CODE_DEV_READY );            
            // 1.3. Отвечаем кодом готовности и ждем подтверждения
            sendAndWaitResponseCode(CODE_SERV_READY, CODE_DEV_FINALY, "ready", "finality");
            System.out.println("Connection established!");
        } catch (Exception ex) {
            System.out.println("> "+ex);
        }
        

        opened = true;
        return true;
    }
    
    
    
    // читает содержимое микросхемы памяти по ее номеру
    public final int[] readChip(int chipNum) {
        int [] result = new int[chipSize[chipNum]];
        int size = 0;
        for (int i = 0; i < result.length; i++)
            result[i] = 0;
    
        try {
            // 1. Выбираем режим чтения
            sendAndWaitResponseCode(CODE_MODE_READ, CODE_MODE_READ, "CODE_MODE_READ", "CODE_MODE_READ");
            // 2. Передаем номер микросхемы памяти
            System.out.println("chip number = " + chipNum);
            os.write(chipAddr[chipNum]);
            // 3. Передаем количество запрашиваемых страниц
            int pages = chipSize[chipNum] / 0xFF;
            if ( chipSize[chipNum] % 0xFF != 0 )
                pages++;
            System.out.println("pages count = " + pages);
            os.write(pages);
            // 4. Читаем содержимое памяти            
            int c;
            System.out.println("DATA ("+chipSize[chipNum]+"bytes):");
            System.out.print("0000: ");
            int linesCnt = 0;
            while ( size < chipSize[chipNum] ) {                
                c = is.read();
                result[size++] = c;                
                String s = Integer.toHexString(c);
                if ( s.length() < 2 )
                    s = "0" + s;
                System.out.print(s + " ");                
                if ( linesCnt++ >= 15 ) {
                    linesCnt = 0;
                    System.out.println("    | "+(100*size/chipSize[chipNum])+"%");
                    s = Integer.toHexString(size);
                    while ( s.length() < 4 )
                        s = "0"+s;
                    System.out.print(s+": ");
                    System.out.flush();
                }                
            };
            while ( size < pages*0xFF ) {
                is.read();
                size++;
            }
            System.out.println("\nDONE! " + size + " bytes readed");
            
        } catch ( Exception ex ) {
            System.out.println("> "+ex);
        }
        return result;
    }
    
    
    
    // записывает страницу в микросхему памяти
    public final void writeChipPage(int chipNum, int data[], int offset, int addres) {
        int cnt = 0;
        try {
            // 1. Выбираем режим записи
            sendAndWaitResponseCode(CODE_MODE_WRITE, CODE_MODE_WRITE, "CODE_MODE_WRITE", "CODE_MODE_WRITE");
            // 2. Передаем номер микросхемы памяти
            System.out.println("chip number = " + chipNum);
            os.write(chipAddr[chipNum]);
            // 3. Передаем 2 байта адреса, сначала старший
            System.out.println("addres = "+addres);
            os.write((addres >> 8) & 0xFF);
            os.write(addres & 0xFF);
            
            // 4. Передаем данные страницы
            System.out.println("DATA:");
            String s = Integer.toHexString(addres);
             while ( s.length() < 4 )
                 s = "0"+s;
             System.out.print(s+": ");
             System.out.flush();
            
            int linesCnt = 0;            
            for (int i = 0; i < WRITE_PAGE_SIZE; i++) {
                int c = data[offset+i];
                s = Integer.toHexString(c);
                if ( s.length() < 2 )
                    s = "0" + s;
                System.out.print(s + " ");
                if ( linesCnt++ >= 15 ) {
                    linesCnt = 0;
                    System.out.println("");
                    System.out.flush();
                    s = Integer.toHexString(addres+i);
                    while ( s.length() < 4 )
                        s = "0"+s;
                    System.out.print(s+": ");
                    System.out.flush();                    
                }                
                os.write(c);                
                cnt++;
            }            
        } catch ( Exception ex ) {
            System.out.println("> "+ex);
        }
        System.out.println("\nDONE! " + cnt + " bytes writed");            
    }
    
    
 
    // записывает блок данных м микросхему памяти
    public final void writeChip(int chipNum, int data[]) {
        int size = data.length > chipSize[chipNum] ? chipSize[chipNum] : data.length;
        // если в массиве нецелое число страниц, то дополняем его нулями
        if ( size % WRITE_PAGE_SIZE != 0 ) {
            int[] tmp = data;
            size = ((size / WRITE_PAGE_SIZE)+1) * WRITE_PAGE_SIZE;
            data = new int[size];
            for (int i = 0; i < tmp.length; i++)
                data[i] = tmp[i];
            for (int i = tmp.length; i < size; i++)
                data[i] = 0;
        }
        for (int page = 0; page < size / WRITE_PAGE_SIZE; page++) {
            System.out.println("Page " + page + " of "+(size / WRITE_PAGE_SIZE));
            writeChipPage(chipNum, data, page*WRITE_PAGE_SIZE, page*WRITE_PAGE_SIZE); 
        }
        
    }
    

    // воспроизводит мелодию в указанной микросхеме
    public final void playChip(int chipNum) {
        try {
            // 1. Выбираем режим проигрывания
            sendAndWaitResponseCode(CODE_MODE_PLAY, CODE_MODE_PLAY, "CODE_MODE_PLAY", "CODE_MODE_PLAY");
            // 2. Передаем номер микросхемы памяти
            System.out.println("chip number = " + chipNum);
            os.write(chipAddr[chipNum]);
            
            System.out.println("Waiting end playing...");
            // 3. Дожидаемся от устройсва кода завершения проигрывания
            int c;
            do {  
                c = is.read();
                System.out.println("Device response: " + c);
            } while ( c != CODE_DEV_OK );
            System.out.println("Play done!");
        } catch ( Exception ex ) {
            System.out.println("> "+ex);
        }
    }
    
   
    // воспроизводит мелодию в указанной микросхеме
    public final void playNote(int note, int duration) {
        try {
            // 1. Выбираем режим проигрывания
            sendAndWaitResponseCode(CODE_MODE_NOTE, CODE_MODE_NOTE, "CODE_MODE_NOTE", "CODE_MODE_NOTE");
            // 2. Передаем номер микросхемы памяти
            System.out.println("note = " + note);
            os.write(note);
            System.out.println("duration = " + duration);
            os.write(duration);
            
            System.out.println("Waiting end playing...");
            // 3. Дожидаемся от устройсва кода завершения проигрывания
            int c;
            do {  
                c = is.read();
                System.out.println("Device response: " + c);
            } while ( c != CODE_DEV_OK );
            System.out.println("Play note done!");
        } catch ( Exception ex ) {
            System.out.println("> "+ex);
        }
    }
    
    
    
    // выключение питания устройства
    public final void powerOff() {
        System.out.println("Power OFF");
        sendAndWaitResponseCode(CODE_MODE_OFF, CODE_MODE_OFF, "CODE_MODE_OFF", "CODE_MODE_OFF");
        close();
    }
    
    
    // закрывает соединение и прекращает сеанс связи
    public final void close() {
        if ( !opened )
            return;        
	if ( sPort != null ) {
	    try {		
	    	os.close();
	    	is.close();
	    } catch (IOException e) {
                e.printStackTrace();
	    }        
            sPort.close();
        }
        
        opened = false;        
    }
    
    
    
    // загружает двоичный файл
    public static final int[] loadFile(String fileName) {
        int[] result = null;
        try {                        
            FileInputStream f = new FileInputStream(fileName);
            System.out.println("Loading file "+fileName + " ("+f.available()+" bytes)...");
            result = new int[f.available()];
            int i = 0;
            while ( f.available() > 0 )
                result[i++] = f.read();            
            f.close();
        } catch (Exception ex) {
            System.out.println("> "+ex);
        }
        return result;
    }
    
    
    
    // сохраняет двоичный файл
    public static final void saveFile(String fileName, int[] data) {
        try {
            FileOutputStream f = new FileOutputStream(fileName);
            System.out.println("Saving file "+fileName + " ("+data.length+" bytes)...");
            for (int i = 0; i < data.length; i++)
                f.write(data[i]);
        } catch (Exception ex) {
            System.out.println("> "+ex);            
        }
    }
    
    
    public static final void printNotesTable() {
        
        int num = 0;
        for (int o = -1; o < 10; o++) {
            for (int n = 0; n < notes_count; n++) {
                double f = 440.0 * Math.pow(2.0, 1.0*(num-69)/12.0);
                String fs = ""+f+"000000";
                fs = fs.substring(0, 8);
                String ts = ""+(500.0/f)*1000.0;
                ts = ts.substring(0, ts.indexOf('.'));
                int us = Integer.parseInt(ts);
                int usl, ush, realUs;                
                if ( us <= 522 ) {
                    ush = 0;
                    usl = (us-12)/2;
                    realUs = 12+2*usl;
                } else {
                    ush = (us-12)/514;
                    usl = (us-10-514*ush)/2;
                    if ( usl == 0 )
                        usl = 1;
                    else if (usl > 255)
                        usl = 255;
                    realUs = 10 + 2*usl + 514*ush;
                }
                
                if (o >= 0 && o < 9) {
                    System.out.println((num-11)+"\t"+o+"-"+names[n]+"\t"+fs + " Hz\t"+ts+" uS\t"+usl+"\t"+ush+"\t("+realUs+" us)");
//                    System.out.println("; "+(num-11)+" - ["+o+"] "+names[n] +"\t t = "+us+" us");
//                    System.out.println("    db "+ush);
//                    System.out.println("    db "+usl);
                }
                num++;
            }
        }
        
    }
    
    public final int[] compileMelody(String melody, int temp) {
        Vector m = new Vector();
        int odelta = 12;
        // разлагаем по нотам
        while ( melody.trim().length() > 0 ) {
            melody = melody.trim();
            int pos = melody.indexOf(' ');            
            String note = ( pos > 0) ? melody.substring(0, pos) : melody;
            melody = pos > 0 ? melody.substring(pos).trim() : "";                
            m.addElement(note.trim());            
        }
        int result[] = new int[m.size()*2+2];
        
        for (int i = 0; i < m.size(); i++) {
            String s = (String)m.elementAt(i);
            String sn = s.substring(0, s.length()-1);
            String sd = s.substring(s.length()-1);
            int d = Integer.parseInt(sd);
            int dur = d *40/temp;
            int nt = 0;
            
            
            if ( sd.equals("B-") )
                nt = 24+odelta;
            
            else if ( sn.equals("C-") )
                nt = 25+odelta;
            else if ( sn.equals("C#-") )
                nt = 26+odelta;
            else if ( sn.equals("D-") )
                nt = 27+odelta;
            else if ( sn.equals("D#-") )
                nt = 28+odelta; 
            else if ( sn.equals("E-") )
                nt = 29+odelta;
            else if ( sn.equals("F-") )
                nt = 30+odelta;            
            else if ( sn.equals("F#-") )
                nt = 31+odelta;                        
            else if ( sn.equals("G-") )
                nt = 32+odelta;
            else if ( sn.equals("G#-") )
                nt = 33+odelta;            
            else if ( sn.equals("A") )
                nt = 34+odelta;                        
            else if ( sn.equals("A#") )
                nt = 35+odelta; 
            else if ( sn.equals("B") )
                nt = 36+odelta; 
            
            else if ( sn.equals("C") )
                nt = 37+odelta;
            else if ( sn.equals("C#") )
                nt = 38+odelta;
            else if ( sn.equals("D") )
                nt = 39+odelta;
            else if ( sn.equals("D#") )
                nt = 40+odelta; 
            else if ( sn.equals("E") )
                nt = 41+odelta;
            else if ( sn.equals("F") )
                nt = 42+odelta;            
            else if ( sn.equals("F#") )
                nt = 43+odelta;                        
            else if ( sn.equals("G") )
                nt = 44+odelta;
            else if ( sn.equals("G#") )
                nt = 45+odelta;            
            else if ( sn.equals("A+") )
                nt = 46+odelta;                        
            else if ( sn.equals("A#+") )
                nt = 47+odelta; 
            else if ( sn.equals("B+") )
                nt = 48+odelta; 
            
            else if ( sn.equals("C+") )
                nt = 49+odelta;
            else if ( sn.equals("C#+") )
                nt = 50+odelta;
            else if ( sn.equals("D+") )
                nt = 51+odelta;
            else if ( sn.equals("D#+") )
                nt = 52+odelta; 
            else if ( sn.equals("E+") )
                nt = 53+odelta;
            else if ( sn.equals("F+") )
                nt = 54+odelta;            
            else if ( sn.equals("R") )
                nt = 0;
            
            result[2*i] = nt;
            result[2*i+1] = dur;                           
        }
        result[result.length-2] = 0;
        result[result.length-1] = 0;
        return result;
    }
 
    
    
    public final void playMelody(String melody, int temp) {
        int[] m = compileMelody(melody, temp);
        for (int i = 0; i < m.length/2; i++)
            playNote(m[2*i], m[2*i+1]);
    }
    
    
    
    public final static void showHelp() {
        System.out.println("read    - read chip");
        System.out.println("flash   - flash chip");
        System.out.println("halt    - disable device and exit");
        System.out.println("play    - play chip");
        System.out.println("compile - compile melody");
        System.out.println("melody  - play melody");
        System.out.println("note    - play note");
        System.out.println("merge   - merge files");
        System.out.println("?       - this screen");        
        System.out.println("");
    }
    
    
    
    public final int inputChipNumber() {
        System.out.print("Chip number(0,1,2): ");                
        System.out.flush();
        int chipN = 0;
        try {
            chipN = Integer.parseInt(stdin.readLine().trim());
        } catch (Exception ex) {
            System.out.println("ERROR! Invalid chip number");
            return -1;
        }                
        if ( chipN < 0 || chipN > 2 )
            return -1;
        else
            return chipN;
    }
    
    
    public final String inputString(String request) {
        System.out.print(request);
        System.out.flush();
        try {
            return stdin.readLine().trim();
        } catch( Exception ex ) {
            return "";
        }        
    }

    
    /**
     * @param afrgs the command line arguments
     */
    public static void main(String[] args) {
        BellDownloader bd = new BellDownloader();              
    
        if ( !bd.open("COM2") )
            return;
        bd.showHelp();
        
        
        while ( true ) {
            String cmd = bd.inputString("> ").toLowerCase();
            if ( cmd.equals("read") ) {                
                int chipN = bd.inputChipNumber();
                if ( chipN < 0 )
                    continue;                
                String fileName = bd.inputString("Save chip data to: ");
                if ( fileName.trim().length() == 0 )
                    continue;
                int[] data = bd.readChip(chipN);
                bd.saveFile(fileName, data);
            } else if ( cmd.equals("flash")) {
                int chipN = bd.inputChipNumber();
                if ( chipN < 0 )
                    continue;                
                String fileName = bd.inputString("Load data from: ");
                if ( fileName.trim().length() == 0 )
                    continue;
                int[] data = bd.loadFile(fileName);
                bd.writeChip(chipN, data);                
            } else if ( cmd.equals("halt") ) {
                bd.powerOff();
                return;
            } else if ( cmd.equals("play") ) {
                int chipN = bd.inputChipNumber();
                if ( chipN < 0 )
                    continue;
                bd.playChip(chipN);
            } else if ( cmd.equals("compile") ) {                
                String sMelody = bd.inputString("Melody: ");
                System.out.print("Temp (1, 2, 3, ...): ");
                int temp = 1;                
                try {
                    temp = Integer.parseInt(bd.stdin.readLine());
                    if ( temp <= 0)
                        throw new Exception("invalid temp");
                } catch (Exception ex) {
                    System.out.println("ERROR! Invalid temp");
                    continue;
                }                                
                String fileName = bd.inputString("Save to file: ");
                if ( fileName.trim().length() == 0 )
                    continue;                
                int data[] = bd.compileMelody(sMelody, temp);
                bd.saveFile(fileName, data);
            } else if ( cmd.equals("melody") ) {                
                String fileName = bd.inputString("Load melody from: ");
                if ( fileName.trim().length() == 0 )
                    continue;                
                int data[] = bd.loadFile(fileName);
                for (int i = 0; i < data.length/2; i++)
                    bd.playNote(data[2*i], data[2*i+1]);
            } else if ( cmd.equals("note") ) {
                System.out.print("Note (0..108): ");
                int note = 0;
                try {
                    note = Integer.parseInt(bd.stdin.readLine().trim());
                    if ( note < 0 || note > 108)
                        throw new Exception("invalid note");
                } catch (Exception ex) {
                    System.out.println("ERROR! Invalid note");
                    continue;
                }                
                System.out.print("Duration (x25 ms): ");
                int duration = 0;
                try {
                    duration = Integer.parseInt(bd.stdin.readLine().trim());
                    if ( duration < 0 || duration > 0xFF )
                        throw new Exception("invalid duration");
                } catch (Exception ex) {
                    System.out.println("ERROR! Invalid duration");
                    continue;
                }                                
                bd.playNote(note, duration);
            } else if ( cmd.equals("merge") ) {
                String files[] = new String[5];
                int data[][];
                data = new int[5][];
                int size = 0;
                for (int i = 0; i < files.length; i++) {
                    files[i] = bd.inputString("File "+i+": ").trim();
                    if ( files[i].length() == 0 ) {
                        files[i] = null;
                        break;
                    }                    
                    data[i] = bd.loadFile(files[i]);
                    if ( data[i] == null) {
                        files[i] = null;
                        break;                        
                    }
                    size += data[i].length;
                }
                int result[] = new int[size];
                int pos = 0;
                for (int i = 0; i < files.length; i++) {
                    if ( files[i] == null )
                        break;
                    for (int j = 0; j < data[i].length; j++)
                        result[pos++] = data[i][j];
                }
                String fileName = bd.inputString("Save to file: ").trim();
                if ( fileName.length() == 0 )
                    continue;
                bd.saveFile(fileName, result);                
            } else if ( cmd.equals("?") ) {
                bd.showHelp();
            } else {
                System.out.println("Invalid command (type '?' to help)");
            }
        }        
    }    
}













/* 
 *     РЕЖИМ ЧТЕНИЯ:
 *  Шлем устройству запрос на чтение, дожидаемся эхо-ответа. Передаем адрес микросхемы. Передаем количество страниц, 
 *  по 255 байтов, которые надо прочинать. Далее, читаем данные и попадаем в меню устройства.
 *
 *
 *     РЕЖИМ ЗАПИСИ:
 *  Шлем устройству запрос на запись, дожидаемся эхо-ответа. Передаем адрес микросхемы. Передаем 2х байтный адрес, 
 *  начиная с которого надо  начать запись. Запись идет 64-байтными страницами. После записи страницы попадаем в 
 *  меню устройства, откуда можем продолжить запись следующей страницы.
 *
 *
 *     РЕЖИМ ВОСПРОИЗВЕДЕНИЯ:
 *  Шлем устройству запрос на воспроизведение, дожидаемся эхо-ответа. Передаем адрес микросхемы. С этого момента начнется 
 *  воспроизведение мелодии, по окончании которого устройство передаст байт успешного завершения и вернется в
 *  меню. 
 *
 *
 *     РЕЖИМ ПРОИГРЫВАНИЯ НОТЫ
 *  Шлем устройству запрос на проигрывание ноты и дожидаемся эхо-ответа. Передаем номер ноты и ее длительность. После 
 *  проигрывания устройство вернет код удачного завершения и возвратится в меню. 
 */ 