package jnyqide;


import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.io.BufferedReader;
import java.io.FileReader;
import java.util.ArrayList;
import java.io.File;

import javax.swing.BorderFactory;
import javax.swing.ComboBoxModel;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JInternalFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSlider;
import javax.swing.JTextArea;
import javax.swing.ListModel;
import javax.swing.border.Border;
import javax.swing.border.EtchedBorder;
import javax.swing.SpringLayout;
import javax.swing.border.TitledBorder;
import javax.swing.event.ChangeListener;
import javax.swing.event.ChangeEvent;
import javax.swing.SwingUtilities;

/***
** Class: Browser
** Author: Priyanka Raghavan and Roger B. Dannenberg
** Description: This is the frame class that implements the sound browser. 
**/

class Browser extends JNonHideableInternalFrame {
    
    NyquistThread nyquistThread;
    MainFrame mainFrame;
    ArrayList instruments;
    InstrInfo currentInstr;

    /**
     * @ desc Default constructor, takes the nyquist thread from main frame
     *  @author prirags - removed all references to the log factory
     */
    
    public Browser(MainFrame mainFrame, NyquistThread nyquistThread) {
        this.mainFrame = mainFrame;
        this.nyquistThread=nyquistThread;
        instruments = new ArrayList();
        currentInstr = null;
        try {
            System.out.println("in Browser()");
            initGUI();
            setLocation(50+10, 50+10);
            setSize(600, 500);
            setResizable(true);
            setClosable(true);
            setMaximizable(true);
            setIconifiable(true);
            final Browser browser = this;
            SwingUtilities.invokeLater(new Runnable() {
                    public void run() {
                        browser.show();
                    }});
            // setDefaultCloseOperation(JInternalFrame.DO_NOTHING_ON_CLOSE);
            System.out.println("at end of Browser()");
        }
        catch (Exception exception) {
            exception.printStackTrace();
        }
    }
    

    // See if an instrument needs an extension
    // returns null or string name of extension
    private String instrNeedsExtension(InstrInfo instr) {
        String extensionName = instr.getExtension();
        String item = instr.getSubcategoryName();
        System.out.println("instrNeedsExtension(" + item + "), extensionName " +
                           extensionName);
        if (extensionName != null) {
            String extensionDir = mainFrame.extDir + extensionName;
            boolean haveExt = (new File(extensionDir)).exists();
            System.out.println("    extensionDir " + extensionDir +
                               " haveExt " + haveExt);
            if (!haveExt) {
                System.out.println("    instrNeedsExtension returns " +
                                   extensionName);
                return extensionName;
            }
        }
        System.out.println("    instrNeedsExtension returns null");
        return null; // no extension needed or we have it
    }


    // If instrument requires an extension, display as 
    //    name (requires extension foo)
    private void addToSubcategory(InstrInfo instr) {
        String item = instr.getSubcategoryName();
        String extensionName = instrNeedsExtension(instr);
        if (extensionName != null) {
            item = item + " (requires extension " + extensionName + ")";
        }
        subcategoryCombo.addItem(item);
    }


    /**
     * @ desc intializes gui elements for the Sound Browser dialog
     */
    private void initGUI() {
        setTitle("Sound Browser");
        this.setSize(800, 513);
        jPanel1 = new JPanel(new SpringLayout());
        Border etchedLine;
        etchedLine = BorderFactory.createEtchedBorder(EtchedBorder.RAISED);
        this.getContentPane().add(jPanel1, BorderLayout.NORTH);
        jPanel1.setBorder(etchedLine);
        // jPanel1.setPreferredSize(new java.awt.Dimension(638, 101));
        categoryLabel = new JLabel();
        jPanel1.add(categoryLabel);
        categoryLabel.setText("Sound Category:");
        ComboBoxModel categoryComboModel = new DefaultComboBoxModel();
        categoryCombo = new JComboBox();
        categoryCombo.addItemListener(new ItemListener() {	
                public void itemStateChanged(ItemEvent evt) {
                    if (evt.getStateChange() != evt.SELECTED) return;
                    if (subcategoryCombo == null) return; // just in case
                    // set up subcategory
                    subcategoryCombo.removeAllItems();
                    subcategoryCombo.addItem("Select One");
                    String category = (String) evt.getItem();
                    System.out.println("category is " + category);
                    for (int i = 0; i < instruments.size(); i++) {
                        InstrInfo info = (InstrInfo) instruments.get(i);
                        if (info.getCategoryName().equalsIgnoreCase(category)) {
                            addToSubcategory(info);
                        }
                    }
                }
            });
        jPanel1.add(categoryCombo);
        categoryCombo.setModel(categoryComboModel);
        // categoryCombo.setPreferredSize(new java.awt.Dimension(306, 25));
        subcategoryLabel = new JLabel();
        jPanel1.add(subcategoryLabel);
        subcategoryLabel.setText("Sound Name:");
        ComboBoxModel subcategoryComboModel = new DefaultComboBoxModel();
        subcategoryCombo = new JComboBox();
        subcategoryCombo.addItemListener(new ItemListener() {	
                public void itemStateChanged(ItemEvent evt) {
                    for (int i = 0; i < instruments.size(); i++) {
                        InstrInfo ic = (InstrInfo) instruments.get(i);
                        String name = (String) evt.getItem();
                        // if item is "name (requires extension foo)", then
                        // extract the name only to compare to instruments
                        int end = name.indexOf("(requires extension ") - 1;
                        if (end > 0) {
                            name = name.substring(0, end);
                        }
                        if (ic.getSubcategoryName().equalsIgnoreCase(name)) {
                            currentInstr = ic;
                            visitSound();
                        }
                    }
                }
            });
        jPanel1.add(subcategoryCombo);
        subcategoryCombo.setModel(subcategoryComboModel);
        // subcategoryCombo.setPreferredSize(new java.awt.Dimension(304, 28));
        SpringUtilities.makeCompactGrid(jPanel1, 2, 2, 6, 6, 6, 6);


        jPanel3 = new JPanel(new SpringLayout());
        this.getContentPane().add(jPanel3, BorderLayout.CENTER);
        etchedLine = BorderFactory.createEtchedBorder(EtchedBorder.RAISED);
        jPanel3.setBorder(etchedLine);
        // jPanel3.setPreferredSize(new java.awt.Dimension(638, 148));
	
        BorderLayout bl = new BorderLayout();
        bl.setHgap(6);
        bl.setVgap(6);
        jPanel2 = new JPanel(bl);
        this.getContentPane().add(jPanel2, BorderLayout.SOUTH);
        etchedLine = BorderFactory.createEtchedBorder(EtchedBorder.RAISED);
        jPanel2.setBorder(etchedLine);
        // jPanel2.setPreferredSize(new java.awt.Dimension(791, 159));

        playButton = new JButton();
        playButton.setText("PLAY");
        // playButton.setPreferredSize(new java.awt.Dimension(100, 36));
        // playButton.setBounds(4, -5, 100, 36);
        playButton.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    playSound();
                }
		
            });

        // try this: 
        // (west: (north: play button))
        //         center: (north: 
        //                  (center: "Lisp Expression that produced the sound")
        //                  center: scrolling text))
        bl = new BorderLayout();
        bl.setHgap(6);
        bl.setVgap(6);
        JPanel jPanel2a = new JPanel(bl);
        jPanel2.add(jPanel2a, BorderLayout.WEST);
        jPanel2a.add(playButton, BorderLayout.NORTH);
        bl = new BorderLayout();
        bl.setHgap(6);
        bl.setVgap(6);
        JPanel jPanel2b = new JPanel(bl);
        jPanel2.add(jPanel2b, BorderLayout.CENTER);
        bl = new BorderLayout();
        bl.setHgap(6);
        bl.setVgap(6);
        JPanel jPanel2b1 = new JPanel(bl);
        jPanel2b.add(jPanel2b1, BorderLayout.NORTH);
        JLabel label = new JLabel("Lisp Expression that produced the sound:");
        label.setHorizontalAlignment(label.CENTER);
        jPanel2b1.add(label, BorderLayout.CENTER);
        jScrollPane1 = new JScrollPane();
        expArea = new JTextArea();
        jScrollPane1.setViewportView(expArea);
	
        // expArea.setPreferredSize(new java.awt.Dimension(650, 60));
	
        jPanel2b.add(jScrollPane1, BorderLayout.CENTER);
        jScrollPane1.setPreferredSize(new java.awt.Dimension(700, 62));
        // SpringUtilities.makeCompactGrid(jPanel2, 2, 2, 6, 6, 6, 6);

        categoryCombo.addItem("Select One");

        // read the instrument.txt text file from lib
        // and create the InstrumentCharacteristic objects.
        //
        BufferedReader br;
        String instrPath = mainFrame.extDir + "instruments.txt";
        try {
            br = new BufferedReader(new FileReader(instrPath));
        } catch (Exception e) {
            System.out.println("ERROR -- could not open " + instrPath);
            return;
        }
        // parse instrument descriptions from buffered reader br
        // add each instrument to ArrayList instruments, and search to
        // see if the instrument category is in categoryCombo. If not,
        // add it.
        while (true) {
            InstrInfo ic = new InstrInfo();
            if (!ic.readData(br)) break;
            System.out.println("new IC: " + ic.getCategoryName() + ":" +
                               ic.getSubcategoryName());
            instruments.add(ic);
            boolean foundIt = false;
            for (int k = 0; k < categoryCombo.getItemCount(); k++) {
                if (categoryCombo.getItemAt(k).toString().
                    equalsIgnoreCase(ic.getCategoryName())) {
                    foundIt = true;
                    break;
                }
            } //end of for
            if (!foundIt) {
                System.out.println("Added " + ic.getCategoryName());
                categoryCombo.addItem(ic.getCategoryName());
            }					
        }
    }

    private JPanel jPanel1;
    private JComboBox subcategoryCombo;
    private JLabel subcategoryLabel;
    private JLabel categoryLabel;
    private JComboBox categoryCombo;
    
    private JScrollPane jScrollPane1;
    
    private JPanel jPanel3;
    private JTextArea expArea;
    private JButton playButton;
    
    private JPanel jPanel2;
    private JLabel[] paramLabels = null;
    private JSlider[] paramSliders = null;
    private JLabel extLabel = null;
    
    public void newParameterCount(int n) {
        // make array for new labels and sliders
        JLabel[] newParamLabels = paramLabels;
        JSlider[] newParamSliders = paramSliders;
        int oldN = 0;
        if (paramLabels != null) {
            oldN = paramLabels.length;
        }
        if (n > oldN) {
            newParamLabels = new JLabel[n];
            newParamSliders = new JSlider[n];
        }
        // make old labels and sliders invisible before reuse
        if (paramLabels != null) {
            // paramLabel0.setVisible(false);
            for (int i = 0; i < oldN; i++) {
                paramLabels[i].setVisible(false);
                newParamLabels[i] = paramLabels[i];

                paramSliders[i].setVisible(false);
                newParamSliders[i] = paramSliders[i];
            }
        } else {
            // paramLabel0 = new JLabel();
            // paramLabel0.setText("SLIDERS");
            // jPanel3.add(paramLabel0);
        }
        // if we don't have enough, make more
        if (n > oldN) {
            for (int i = oldN; i < n; i++) {
                newParamLabels[i] = new JLabel();
                jPanel3.add(newParamLabels[i]);
                newParamSliders[i] = new JSlider();
                jPanel3.add(newParamSliders[i]);
            }
            paramLabels = newParamLabels;
            paramSliders = newParamSliders;
        }
        SpringUtilities.makeCompactGrid(jPanel3, n, 2, 6, 6, 6, 6);

        // now we have n labels and sliders in arrays
        // we also have paramLabel0
    }


    public void visitSound() {
        System.out.println("visitSound called: " + 
                           currentInstr.getSubcategoryName());
        
        // first, find out if we need an extension
        String extensionName = instrNeedsExtension(currentInstr);
        if (extensionName != null) {
            // cannot play this sound
            newParameterCount(0);
            if (extLabel == null) extLabel = new JLabel();
            jPanel3.add(extLabel);
            extLabel.setText("<html>This sound requires the extension <b>" +
                    extensionName + "</b>.<br><br>You can download it " +
                    "with the Extension Manager item in the Window " +
                    "menu.</html>");
            extLabel.setVisible(true);
            SpringUtilities.makeCompactGrid(jPanel3, 1, 1, 6, 6, 6, 6);
            return;
        } else {
            if (extLabel != null) {
                jPanel3.remove(extLabel);
                extLabel = null;
            }
        }


        /*
         * This hides the labels and the sliders that could have 
         * been created in previous runs
         */
        int numParams = currentInstr.getParameters().size();
        newParameterCount(numParams);
        System.out.println("visitSound: numParams " + numParams);

        for(int i = 0; i < numParams; i++) {
            Parameter p = (Parameter) (currentInstr.getParameters().get(i));
            paramLabels[i].setText(p.getName().toUpperCase());
            System.out.println("Parameter" + p.getName());
            paramLabels[i].setVisible(true);
            if (p.getType().equals("int")) {
                final JSlider s = paramSliders[i];
                int max = Integer.parseInt(p.getMaxValue());
                s.setMaximum(max);
                int min = Integer.parseInt(p.getMinValue());
                s.setMinimum(min);
                int range = max - min;
                int major = 1;
                while (major < range) major *= 10;
                s.setMajorTickSpacing(major / 10);
                s.setMinorTickSpacing(major / 100);
                System.out.println("major " + major);
                s.setPaintTicks(true);
                s.setPaintLabels(true);
                s.setValue(Integer.parseInt(p.getDefaultValue()));
            } else if (p.getType().equals("float")) {
                final float min = Float.valueOf(p.getMinValue()).floatValue();
                final float max = Float.valueOf(p.getMaxValue()).floatValue();
                final float width = max - min;
                final Parameter param = p;
                final JSlider s = paramSliders[i];
                float fval = Float.valueOf(p.getDefaultValue()).floatValue();
                param.setValue(fval);
                s.setMinimum(0);
                s.setMaximum(1000);
                s.setValue((int) ((fval - min) / width * 1000));
                java.util.Hashtable<Integer, JLabel> labels = 
                        new java.util.Hashtable<Integer, JLabel>(3);
                labels.put(0, new JLabel(p.getMinValue()));
                labels.put(500, new JLabel(String.valueOf((max + min) / 2.0f)));
                labels.put(1000, new JLabel(p.getMaxValue()));
                s.setLabelTable(labels);
                s.setPaintTicks(false);
                s.setPaintLabels(true);
                // s.setBorder(new TitledBorder("border text"));
                s.addChangeListener(new ChangeListener() {
                        public void stateChanged(ChangeEvent e) {
                            int j = s.getValue();
                            float f = min + (j * width / 1000.0f);
                            param.setValue(f);
                        }
                    });
            } else {
                System.out.println("Expected int or float in " + 
                                   i + " |" + p.getType() + "|");
            }
						      
            if (numParams > 1) {
                paramSliders[i].setOrientation(JSlider.HORIZONTAL);
            } else {
                paramSliders[i].setOrientation(JSlider.HORIZONTAL);
            }
            paramSliders[i].setVisible(true);
        }	    
    }	    

    
    public void playSound() {
        boolean isSal = mainFrame.codeInputPane.isSal;
        /* debugging 
        System.out.println("isSal: " + isSal + "\n" +
                           currentInstr.getFunctionName() + "\nSal: " +
                           currentInstr.getSalImplementation() + "\nLisp: " +
                           currentInstr.getLispImplementation() + "\nReq: " +
                           currentInstr.getRequire());
           end debugging */
        String cmd = (isSal ? "play " : "(play (") + 
                     currentInstr.getFunctionName();
        String sep = (isSal ? ", " : " ");
        if (isSal) cmd = cmd + "(";
        boolean first = isSal;
        for (int i = 0; i < currentInstr.getParameters().size(); i++) {
	    Parameter p = (Parameter) currentInstr.getParameters().get(i);
	    // is this a keyword parameter? If so, put it in
	    if (p.getName().charAt(0) == ':') {
                if (!first) cmd = cmd + sep;
		cmd = cmd + p.getName();
                first = false;
	    }
	    String actual;
	    if (p.getType().equals("int")) {
		actual = String.valueOf(paramSliders[i].getValue());
	    } else if (p.getType().equals("float")) {
		actual = String.valueOf(p.getValue());
	    } else {
		actual = "";
		System.out.println("bad parameter type");
	    }
            if (!first) cmd = cmd + sep;
            first = false;
            cmd = cmd + actual;
        }
        if (!isSal) cmd = cmd + ")"; // to match (play
        cmd = cmd + ")\n";
        String impl = (isSal ? currentInstr.getSalImplementation() :
                               currentInstr.getLispImplementation());
        if (impl == null && currentInstr.getRequire() != null) {
            impl = currentInstr.getRequire();
            if (isSal) {
                impl = "if ! fboundp(quote(" + currentInstr.getFunctionName() +
                       ")) then load " + impl + "\n";
            } else {
                impl = "(if (not (fboundp '" + currentInstr.getFunctionName() +
                       ")) (load " + impl + "))\n";
            }
        }
        if (impl != null) {
            if (isSal) { // for SAL, need one command
                cmd = "begin\n  " + impl + "  " + cmd + "end\n";
            } else {
                cmd = impl + cmd; // lisp can take sequence of commands
            }
        }
        cmd = cmd + "\n"; // need extra newline to cause parser to run
	expArea.setText(cmd);
	mainFrame.sendInput(cmd);
    }
}    
