package rns2tf3;

import java.io.*;
import java.util.*;
import java.io.File;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
/**
 *
 * @author pap
 */
public class Rns2tf3 {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        // TODO code application logic here
        if(args.length != 2) {
            System.out.println("*** Renoise to TuneFish3 (Enigma Studio 3) Converter ***");
            System.out.println("Usage: \n     java -jar rns2tf2.jar <infile.xrns> <targetfile.e3prj>");
            System.exit(0);
        }
        
        String zipname = args[0];
        String inFileName = args[1];
/*
        String zipname = "renoise2.xrns";
        String inFileName = "C:/dev/enigma3/demos/traincontrol/traincontrol.e3prj";
*/        
        
	File xmlFile = new File("__song_rns.xml");        
        
        PatternTrack.init();
        
        HashMap<String, PatternTrack> trackMap = new HashMap<String, PatternTrack>();
        LinkedList<Integer> numTracksInPatters = new LinkedList<Integer>();
        LinkedList<Integer> patternLength = new LinkedList<Integer>();
        
        SAXBuilder builder = new SAXBuilder();
        
        try {
            
            ZipInputStream zin = new ZipInputStream(new FileInputStream(zipname));
            ZipEntry entry;
            System.out.println("");
            while ((entry = zin.getNextEntry()) != null) {
                if (entry.getName().equals("Song.xml")) {
                    BufferedReader in = new BufferedReader(new InputStreamReader(zin));
                    PrintWriter out = new PrintWriter(xmlFile);
                    String s;
                    while ((s = in.readLine()) != null)
                        out.println(s);
                    out.close();
                }
                zin.closeEntry();
            }
            zin.close();
            
            
            Document document = (Document) builder.build(xmlFile);
            Element rootNode = document.getRootElement();

            Element globalSongNode = (Element)rootNode.getChildren("GlobalSongData").get(0);
            int bpm = Integer.parseInt(globalSongNode.getChildText("BeatsPerMin"));
            
            // -------------------------
            // read patterns
            Element patternPoolNode = (Element)rootNode.getChildren("PatternPool").get(0);
            Element patternsNode = (Element)patternPoolNode.getChildren("Patterns").get(0);
            List patterns = patternsNode.getChildren();
            for(int p = 0; p < patterns.size(); p++) {
                Element pattern = (Element)patterns.get(p);
                int numberOfLines = Integer.parseInt(pattern.getChildText("NumberOfLines"));
                System.out.println("Pattern: " + pattern + " NoL " + numberOfLines + "\n");
                Element tracksNode = (Element)pattern.getChildren("Tracks").get(0);
                List tracks = tracksNode.getChildren();
                numTracksInPatters.add(tracks.size());
                patternLength.add(numberOfLines);
                for(int t = 0; t < tracks.size(); t++) {
                    Element track = (Element)tracks.get(t);
                    
                    if(!"PatternTrack".equals(track.getAttributeValue("type")))
                        continue;

                    System.out.println("Track: " + track);
                    
                    PatternTrack pt = new PatternTrack(p, t, numberOfLines);
                    trackMap.put("" + p + ":" + t, pt);

                    List lineNodes = track.getChildren("Lines");
                    if(lineNodes.size() > 0) {
                        Element linesNode = (Element)lineNodes.get(0);
                        List lines = linesNode.getChildren();
                        for(int l = 0; l < lines.size(); l++) {
                            Element line = (Element)lines.get(l);
                            String lineIdx = line.getAttributeValue("index");

                            Element ntcolsNode = (Element)line.getChildren("NoteColumns").get(0);
                            List columns = ntcolsNode.getChildren();
                            for(int c = 0;  c < columns.size(); c++) {
                                Element ntcolNode = (Element)columns.get(c);

                                String noteValue = ntcolNode.getChildText("Note");
                                String instValue = ntcolNode.getChildText("Instrument");
                                String volValue = ntcolNode.getChildText("Volume");

                                if("---".equals(noteValue))
                                    continue;

                                pt.setNote(c, lineIdx, instValue, noteValue);
                            }
                        }
                    }                    
                }
                
            }
            
            // -------------------------
            // Read Sequencer
            LinkedList<PatternTrack> uniquePatters = new LinkedList<PatternTrack>();
            Element patternSequenceNode = (Element)rootNode.getChildren("PatternSequence").get(0);
            Element sequenceEntriesNode = (Element)patternSequenceNode.getChildren("SequenceEntries").get(0);
            List sequenceEntries = sequenceEntriesNode.getChildren();
            String patternInstancesXml = "";
            System.out.println("Number of entries: " + sequenceEntries.size());
            int rowOffset = 0;
            for(int s = 0; s < sequenceEntries.size(); s++) {
                Element sequenceEntryNode = (Element)sequenceEntries.get(s);
                int patternNr = Integer.parseInt(sequenceEntryNode.getChildText("Pattern"));
                int patternLen = patternLength.get(patternNr);
                
                LinkedList<Integer> tracks = new LinkedList<Integer>();
                for(int i = 0; i < numTracksInPatters.get(patternNr); i++)
                    tracks.add(i);
                
                if(sequenceEntryNode.getChildren("MutedTracks").size() != 0) {
                    Element mutedTracksNode = (Element)sequenceEntryNode.getChildren("MutedTracks").get(0);
                    List mutedTracks = mutedTracksNode.getChildren();
                    for(int m = 0; m < mutedTracks.size(); m++) {
                        Element mutedTrackNode = (Element)mutedTracks.get(m);
                        int mnr = Integer.parseInt(mutedTrackNode.getValue());
                        tracks.remove((Integer)mnr);
//                        System.out.println("Muting " + mnr);
                    }
                }
                
                for(int t = 0; t < tracks.size(); t++) {
                    int tnr = tracks.get(t);

                    PatternTrack pt = trackMap.get("" + patternNr + ":" + tnr);
                    
                    if(pt != null) {
                        boolean isUnique = true;
                        for(int k = 0; k < uniquePatters.size(); k++) 
                            if(uniquePatters.get(k).isIdentical(pt)) {
                                pt = uniquePatters.get(k);
                                isUnique = false;
                                break;
                            }

                        if(isUnique)
                            uniquePatters.add(pt);
//                        System.out.println("-> " + patternNr + ":" + tnr + ": " + pt);
                        patternInstancesXml +=
                                "<patterninstance rowoffset=\"" + rowOffset + "\" patternid=\"" + pt.m_numID + "\" track=\"" + t + "\"/>\n";
                    }
                }
                
                rowOffset += patternLen;
            }

            // -------------------------
            // Read Instruments
            Element instrumentsNode = (Element)rootNode.getChildren("Instruments").get(0);
            List instrumentsList = instrumentsNode.getChildren();
            LinkedList<Instrument> instruments = new LinkedList<Instrument>();
            for(int i = 0; i < instrumentsList.size(); i++) {
                Element instrumentNode = (Element)instrumentsList.get(i);
                String instName = instrumentNode.getChildText("Name");
                
                Element plugPropNode = (Element)instrumentNode.getChildren("PluginProperties").get(0);
                List plugDevices = plugPropNode.getChildren("PluginDevice");
                if(plugDevices.size() == 1) {
                    Element plugDeviceNode = (Element)plugDevices.get(0);
                    Element parameterChunkNode = (Element)plugDeviceNode.getChildren("ParameterChunk").get(0);
                    String text = plugDeviceNode.getChildText("ParameterChunk");
//                    System.out.println("Instrument " + i + ": " + text);
                    while(text.length() % 4 != 0)
                        text = text.substring(0, text.length() - 1);
                    int activeProgram = Integer.parseInt(plugDeviceNode.getChildText("ActiveProgram"));
                    byte[] decoded = Base64Coder.decode(text);
//                    System.out.println("Text: " + decoded.length);
                    // 4 byte filler
                    // per preset:
                    //  4 byte parameter count
                    //  4 byte stringlen (name)
                    //  String als unicode (2byte per char)
                    //  paramcnt * 4
                    int pos = 4;
                    int numPresets = byteArrayToInt(decoded, pos); pos += 4;
                    int paramCnt = byteArrayToInt(decoded, pos); pos += 4;
                    for(int ps = 0; ps < numPresets; ps++) {
                        int strLen = byteArrayToInt(decoded, pos);  pos += 4;
                        
                        // skip preset name (unicode)
                        pos += 2 * strLen;
                        // now parameter floats
                        if(ps == activeProgram) {
                            LinkedList<String> paramValues = new LinkedList<String>();
                            for(int p = 0; p < paramCnt; p++) {
                                float val = byteArrayToFloat(decoded, pos); pos += 4;
                                paramValues.add("" + val);
                            };
                            instruments.add(new Instrument(instName, paramValues));
                        } else {
                            // just skip
                            pos += paramCnt * 4;
                        }
//                        System.out.println("Preset " + ps + " ends: " + pos);
                    }
                    
                    
                    
/*                    
                    Element parametersNode = (Element)plugDeviceNode.getChildren("Parameters").get(0);
                    List params = parametersNode.getChildren();
                    LinkedList<String> paramValues = new LinkedList<String>();
                    for(int p = 0; p < params.size(); p++) {
                        Element param = (Element)params.get(p);
                        String value = param.getChildText("Value");
                        paramValues.add(value);
                    }
                    instruments.add(new Instrument(instName, paramValues));
                    * 
                    */
                }
            }

            
            // -------------------------
            // parse base file
            String InPart1 = "";
            String InPart2 = "";
            String InPart3 = "";
            String str;
            BufferedReader in = new BufferedReader(new FileReader(inFileName));
            while (((str = in.readLine()) != null) && (!str.contains("<songs>")) && (!str.contains("<songs/>")))
                InPart1 += str + "\n";
            if(!str.contains("<songs/>"))
                while (((str = in.readLine()) != null) && (!str.contains("</songs>")));

            while (((str = in.readLine()) != null) && (!str.contains("<songlist>")) && (!str.contains("<songlist/>")))
                InPart1 += str + "\n";
            if(!str.contains("<songlist/>"))
                while (((str = in.readLine()) != null) && (!str.contains("</songlist>")));
            
            
            while (((str = in.readLine()) != null) && (!str.contains("<instruments>"))&& (!str.contains("<instruments/>")))
                InPart2 += str + "\n";
            if(!str.contains("<instruments/>"))
                while (((str = in.readLine()) != null) && (!str.contains("</instruments>")));
            while (((str = in.readLine()) != null))
                InPart3 += str + "\n";
            
            
            // -------------------------
            // export
            FileWriter outFile = new FileWriter(inFileName);
            PrintWriter out = new PrintWriter(outFile);

            out.println(InPart1);
            
            out.println("<songs>");
            out.println("<song bpm=\"" + bpm + "\" id=\"1\" name=\"New song\">");
            out.println("    <patterns>");
            // write used patterns
            LinkedList<PatternTrack> trackList = new LinkedList<PatternTrack>();
            trackList.addAll(trackMap.values());
            PatternTrack[] tracksArray = trackList.toArray(new PatternTrack[0]);
            // bubblesort
            boolean again = true;
            while(again) {
                again = false;
                for(int i = 0; i < tracksArray.length - 1; i++) {
                    if(tracksArray[i].m_numID > tracksArray[i + 1].m_numID) {
                        PatternTrack temp = tracksArray[i];
                        tracksArray[i] = tracksArray[i + 1];
                        tracksArray[i + 1] = temp;
                        again = true;
                    }
                }
            }
            
            for(int i = 0; i < tracksArray.length; i++){
                PatternTrack track = tracksArray[i];
                out.println(track.getXml());
            }
            out.println("    </patterns>");
            out.println("    <patterninstances>");
            out.println(patternInstancesXml);
            out.println("    </patterninstances>");
            out.println("</song>");
            out.println("</songs>");
            out.println("<songlist>");
            out.println("  <song id=\"1\" name=\"New song\"/>");
            out.println("</songlist>");
            
            out.println(InPart2);
            out.println("<instruments>");
            Iterator<Instrument> instIter = instruments.iterator();
            while(instIter.hasNext()) {
                Instrument inst = instIter.next();
                out.println(inst.getXml());
            }
            out.println("</instruments>");
            out.println(InPart3);
            
            
            out.close();

            xmlFile.delete();
            
            System.out.println("Conversion completed !");
	  } catch (IOException io) {
		System.out.println(io.getMessage());
	  } catch (JDOMException jdomex) {
		System.out.println(jdomex.getMessage());
	  }
        System.exit (0);
    }

    public static final int byteArrayToInt(byte [] b, int pos) {
        byte[] bb = new byte[4];
        bb[0] = b[pos++];
        bb[1] = b[pos++];
        bb[2] = b[pos++];
        bb[3] = b[pos++];
        return byteArrayToInt(bb);
    }
    public static final float byteArrayToFloat(byte [] b, int pos) {
        int intVal = byteArrayToInt(b, pos);
        return Float.intBitsToFloat(intVal);
    }    
    public static final int byteArrayToInt(byte [] b) {
/*        
            return (b[0] << 24)
                    + ((b[1] & 0xFF) << 16)
                    + ((b[2] & 0xFF) << 8)
                    + (b[3] & 0xFF);
                    * 
                    */
            return (b[3] << 24)
                    + ((b[2] & 0xFF) << 16)
                    + ((b[1] & 0xFF) << 8)
                    + (b[0] & 0xFF);
    }    
   
}
