package com.ibm.tuningfork.traceGenerationExample;

import java.io.File;
import java.io.IOException;

import com.ibm.tuningfork.tracegen.IBookmarkEvent;
import com.ibm.tuningfork.tracegen.IFeedlet;
import com.ibm.tuningfork.tracegen.ILogger;
import com.ibm.tuningfork.tracegen.ITimerEvent;
import com.ibm.tuningfork.tracegen.IValueEvent;
import com.ibm.tuningfork.tracegen.LoggerFactory;
import com.ibm.tuningfork.tracegen.types.EventAttribute;
import com.ibm.tuningfork.tracegen.types.EventType;
import com.ibm.tuningfork.tracegen.types.EventTypeSpaceVersion;
import com.ibm.tuningfork.tracegen.types.ScalarType;

public class InstrumentedClient {

    /* --------------------- INSTRUMENTATION START ------------------------- */
    static ILogger logger = null;
    static IFeedlet signalFeedlet, activityFeedlet, midiFeedlet;
    static IBookmarkEvent phaseBookmark;
    static ITimerEvent activityTimer;
    static IValueEvent signalET, loudnessET;
    static EventType midiEventET;

    static private void instrumentationInit(String fileNameOrPortNumber, int durationSeconds) {

        // Create a logger
        EventTypeSpaceVersion[] etsv = new EventTypeSpaceVersion[] { new EventTypeSpaceVersion("com.ibm.tuningfork.music", 1) };
        try {
            try {
                if (fileNameOrPortNumber.equals("none")) {
                    logger = LoggerFactory.makeNullLogger();
                } else {
                    int portNumber = Integer.parseInt(fileNameOrPortNumber);
                    logger = LoggerFactory.makeServerLogger(portNumber, etsv, true, LoggerFactory.NO_PROCESSOR_AFFINITY);
                }
            }
            catch (NumberFormatException exn) {
                logger = LoggerFactory.makeFileLogger(new File(fileNameOrPortNumber), etsv, true, LoggerFactory.NO_PROCESSOR_AFFINITY);
            }
        }
        catch (IOException ex) {
            ex.printStackTrace();
            System.exit(-1);
        }

        // Add some properties
        logger.addProperty("Program Run Length", durationSeconds + " sec");

        // Add some event types - two for each type of threads
        phaseBookmark = logger.makeBookmarkEvent("Iteration");
        activityTimer = logger.makeTimerEvent("Activity");
        signalET = logger.makeValueEvent("Signal Value");
        loudnessET = logger.makeValueEvent("Loudness");
        midiEventET =
                new EventType("MIDI Events", "MIDI Events", new EventAttribute[] { new EventAttribute("channel", "channel", ScalarType.INT), new EventAttribute("status", "status", ScalarType.INT), new EventAttribute("data1", "data1", ScalarType.INT),
                        new EventAttribute("data2", "data2", ScalarType.INT), });
        logger.addEventType(midiEventET);
        signalFeedlet = logger.makeFeedlet("signal feedlet", "Simulation of some amplitude modulated sine wave");
        activityFeedlet = logger.makeFeedlet("activity", "Simulation of a irregular task");
        midiFeedlet = logger.makeFeedlet("midi", "midi");

    }

    /* --------------------- INSTRUMENTATION END ------------------------- */

    static public void main(String[] args) {

        /* ------- INSTRUMENTATION MODIFICATION START --------- */
        if (args.length != 2) {
            System.err.println("Usage: Client  <'none' or traceFileName or portNumber>");
            return;
        }
        String duration = args[0];
        int durationSeconds = 60; // Default is 60 seconds
        try {
            durationSeconds = Integer.parseInt(duration);
        }
        catch (NumberFormatException exn) {
            System.err.println("Could not parse duration argument.  Using " + durationSeconds);
        }
        String fileNameOrPortNumber = args[1]; // INSRUMENTATION
        instrumentationInit(fileNameOrPortNumber, durationSeconds); // INSRUMENTATION
        /* ------- INSTRUMENTATION MODIFICATION END --------- */

        // Run 2 threads that make events for some time
        System.out.println("Running 3 threads for " + durationSeconds + " seconds.");
        runSignal(durationSeconds);
        runIntervals(durationSeconds);
        runMidi(durationSeconds);

        // This is only an approximation - not synchronized with event-generating threads
        try {
            Thread.sleep(durationSeconds * 1000);
        }
        catch (InterruptedException ie) {
        }

        // Flush buffered data
        System.out.println("Done.");

    }

    static void setSignal(double amplitude, double loudness) {
        signalET.addValue(amplitude); // ---------------- INSTRUMENTATION -------------------
        loudnessET.addValue(loudness); // ---------------- INSTRUMENTATION -------------------

        // Do some work related to setting signal - we simulate by sleeping briefly
        try {
            Thread.sleep(1);
        }
        catch (InterruptedException ie) {
        }
    }

    static void runOneInterval(long timeMs) {
        activityTimer.start(); // ---------------------- INSTRUMENTATION -------------------
        try {
            Thread.sleep(timeMs);
        }
        catch (InterruptedException ie) {
        }
        activityTimer.stop(); // --------------------- INSTRUMENTATION -------------------
    }

    static void runMidi(final long durationSec) {
        new Thread() {
            public void run() {

                midiFeedlet.bindToCurrentThread(); // --------------------- INSTRUMENTATION -------------------

                // Now make some MIDI events
                final int pitchOffset = 0x0c;
                final int startPitch = 0x30;
                final int endPitch = 0x40;
                final int onStatus = 0x90;
                final int offStatus = 0x80;
                int channel = 0;
                int status = onStatus;
                int curPitch = startPitch; // pitch
                int data2 = 64; // velocity
                int iteration = 0;

                long start = System.nanoTime();
                while (true) {
                    double timeSec = (System.nanoTime() - start) / 1e9;

                    if (curPitch == startPitch && status == onStatus) {
                        phaseBookmark.addBookmark("Iteration " + (++iteration));
                    }
                    midiFeedlet.addEvent(midiEventET, new int[] { channel, status, curPitch, data2 }, null, null, null);
                    midiFeedlet.addEvent(midiEventET, new int[] { channel, status, curPitch + pitchOffset, data2 }, null, null, null);
                    if (status == onStatus) {
                        status = offStatus;
                    } else {
                        status = onStatus;
                        if (++curPitch > endPitch) {
                            curPitch = startPitch;
                        }
                    }
                    if (timeSec > durationSec) {
                        break;
                    }

                    try {
                        Thread.sleep(200);
                    }
                    catch (InterruptedException ie) {
                    }
                }
            }
        }.start();
    }

    static void runSignal(final long durationSec) {
        new Thread() {
            public void run() {

                signalFeedlet.bindToCurrentThread(); // --------------------- INSTRUMENTATION -------------------

                // Now make some events
                double pitchFrequency = 5;
                double loudnessFrequency = 0.5;
                long start = System.nanoTime();
                while (true) {
                    double timeSec = (System.nanoTime() - start) / 1e9;
                    double pitchPosition = timeSec * pitchFrequency * 2 * Math.PI;
                    double loudnessPosition = timeSec * loudnessFrequency * 2 * Math.PI;
                    double loudness = 1 + 0.5 * Math.sin(loudnessPosition);
                    double amplitude = Math.sin(pitchPosition) * loudness;

                    // FIXME: HACK AROUND TIMESERIES y-AXIS SCALING BUG: Multiply real values by 1000.
                    double hackScale = 1000;
                    setSignal(hackScale*amplitude, hackScale*loudness);

                    if (timeSec > durationSec) {
                        break;
                    }
                }
            }
        }.start();
    }

    static void runIntervals(final long durationSec) {
        new Thread() {
            public void run() {

                activityFeedlet.bindToCurrentThread(); // --------------------- INSTRUMENTATION -------------------

                double onMaxLenMs = 200; // activity's length is from a uniform distribution from 0 to onMaxLen
                double offMaxLenMs = 500; // time until next activity is from a uniform distribution from 0 to onMaxLen
                long start = System.nanoTime();
                while (true) {
                    int onLenMs = (int) (onMaxLenMs * Math.random());
                    int offLenMs = (int) (offMaxLenMs * Math.random());

                    runOneInterval(onLenMs);
                    try {
                        Thread.sleep(offLenMs);
                    }
                    catch (InterruptedException ie) {
                    }

                    double timeSec = (System.nanoTime() - start) / 1e9;
                    if (timeSec > durationSec) {
                        break;
                    }
                }
            }
        }.start();
    }