package com.reactlibrary;


import java.io.*;

public class WavFile2
{
    private enum IOState {READING, WRITING, CLOSED};
    private final static int BUFFER_SIZE = 4096;

    private final static int FMT_CHUNK_ID = 0x20746D66;
    private final static int DATA_CHUNK_ID = 0x61746164;
    private final static int RIFF_CHUNK_ID = 0x46464952;
    private final static int RIFF_TYPE_ID = 0x45564157;

    private File file;						// File that will be read from or written to
    private IOState ioState;				// Specifies the IO State of the Wav File (used for snaity checking)
    private int bytesPerSample;			// Number of bytes required to store a single sample
    private long numFrames;					// Number of frames within the data section
    private FileOutputStream oStream;	// Output stream used for writting data
    private FileInputStream iStream;		// Input stream used for reading data
    private double floatScale;				// Scaling factor used for int <-> float conversion
    private double floatOffset;			// Offset factor used for int <-> float conversion
    private boolean wordAlignAdjust;		// Specify if an extra byte at the end of the data chunk is required for word alignment

    // Wav Header
    private int numChannels;				// 2 bytes unsigned, 0x0001 (1) to 0xFFFF (65,535)
    private long sampleRate;				// 4 bytes unsigned, 0x00000001 (1) to 0xFFFFFFFF (4,294,967,295)
    // Although a java int is 4 bytes, it is signed, so need to use a long
    private int blockAlign;					// 2 bytes unsigned, 0x0001 (1) to 0xFFFF (65,535)
    private int validBits;					// 2 bytes unsigned, 0x0002 (2) to 0xFFFF (65,535)

    // Buffering
    private byte[] buffer;					// Local buffer used for IO
    private int bufferPointer;				// Points to the current position in local buffer
    private int bytesRead;					// Bytes read after last read into local buffer
    private long frameCounter;				// Current number of frames read or written

    // Cannot instantiate WavFile2 directly, must either use newWavFile2() or openWavFile2()
    private WavFile2()
    {
        buffer = new byte[BUFFER_SIZE];
    }

    public int getNumChannels()
    {
        return numChannels;
    }

    public long getNumFrames()
    {
        return numFrames;
    }

    public long getFramesRemaining()
    {
        return numFrames - frameCounter;
    }

    public long getSampleRate()
    {
        return sampleRate;
    }

    public int getValidBits()
    {
        return validBits;
    }

    public static WavFile2 newWavFile2(File file, int numChannels, long numFrames, int validBits, long sampleRate) throws IOException, WavFileException
    {
        // Instantiate new Wavfile2 and initialise
        WavFile2 wavFile2 = new WavFile2();
        wavFile2.file = file;
        wavFile2.numChannels = numChannels;
        wavFile2.numFrames = numFrames;
        wavFile2.sampleRate = sampleRate;
        wavFile2.bytesPerSample = (validBits + 7) / 8;
        wavFile2.blockAlign = wavFile2.bytesPerSample * numChannels;
        wavFile2.validBits = validBits;

        // Sanity check arguments
        if (numChannels < 1 || numChannels > 65535) throw new WavFileException("Illegal number of channels, valid range 1 to 65536");
        if (numFrames < 0) throw new WavFileException("Number of frames must be positive");
        if (validBits < 2 || validBits > 65535) throw new WavFileException("Illegal number of valid bits, valid range 2 to 65536");
        if (sampleRate < 0) throw new WavFileException("Sample rate must be positive");

        // Create output stream for writing data
        wavFile2.oStream = new FileOutputStream(file);

        // Calculate the chunk sizes
        long dataChunkSize = wavFile2.blockAlign * numFrames;
        long mainChunkSize =	4 +	// Riff Type
                8 +	// Format ID and size
                16 +	// Format data
                8 + 	// Data ID and size
                dataChunkSize;

        // Chunks must be word aligned, so if odd number of audio data bytes
        // adjust the main chunk size
        if (dataChunkSize % 2 == 1) {
            mainChunkSize += 1;
            wavFile2.wordAlignAdjust = true;
        }
        else {
            wavFile2.wordAlignAdjust = false;
        }

        // Set the main chunk size
        putLE(RIFF_CHUNK_ID,	wavFile2.buffer, 0, 4);
        putLE(mainChunkSize,	wavFile2.buffer, 4, 4);
        putLE(RIFF_TYPE_ID,	wavFile2.buffer, 8, 4);

        // Write out the header
        wavFile2.oStream.write(wavFile2.buffer, 0, 12);

        // Put format data in buffer
        long averageBytesPerSecond = sampleRate * wavFile2.blockAlign;

        putLE(FMT_CHUNK_ID,				wavFile2.buffer, 0, 4);		// Chunk ID
        putLE(16,							wavFile2.buffer, 4, 4);		// Chunk Data Size
        putLE(1,								wavFile2.buffer, 8, 2);		// Compression Code (Uncompressed)
        putLE(numChannels,				wavFile2.buffer, 10, 2);		// Number of channels
        putLE(sampleRate,					wavFile2.buffer, 12, 4);		// Sample Rate
        putLE(averageBytesPerSecond,	wavFile2.buffer, 16, 4);		// Average Bytes Per Second
        putLE(wavFile2.blockAlign,		wavFile2.buffer, 20, 2);		// Block Align
        putLE(validBits,					wavFile2.buffer, 22, 2);		// Valid Bits

        // Write Format Chunk
        wavFile2.oStream.write(wavFile2.buffer, 0, 24);

        // Start Data Chunk
        putLE(DATA_CHUNK_ID,				wavFile2.buffer, 0, 4);		// Chunk ID
        putLE(dataChunkSize,				wavFile2.buffer, 4, 4);		// Chunk Data Size

        // Write Format Chunk
        wavFile2.oStream.write(wavFile2.buffer, 0, 8);

        // Calculate the scaling factor for converting to a normalised double
        if (wavFile2.validBits > 8)
        {
            // If more than 8 validBits, data is signed
            // Conversion required multiplying by magnitude of max positive value
            wavFile2.floatOffset = 0;
            wavFile2.floatScale = Long.MAX_VALUE >> (64 - wavFile2.validBits);
        }
        else
        {
            // Else if 8 or less validBits, data is unsigned
            // Conversion required dividing by max positive value
            wavFile2.floatOffset = 1;
            wavFile2.floatScale = 0.5 * ((1 << wavFile2.validBits) - 1);
        }

        // Finally, set the IO State
        wavFile2.bufferPointer = 0;
        wavFile2.bytesRead = 0;
        wavFile2.frameCounter = 0;
        wavFile2.ioState = IOState.WRITING;

        return wavFile2;
    }

    public static WavFile2 openWavFile2(File file) throws IOException, WavFileException
    {
        // Instantiate new Wavfile2 and store the file reference
        WavFile2 wavFile2 = new WavFile2();
        wavFile2.file = file;

        // Create a new file input stream for reading file data
        wavFile2.iStream = new FileInputStream(file);

        // Read the first 12 bytes of the file
        int bytesRead = wavFile2.iStream.read(wavFile2.buffer, 0, 12);
        if (bytesRead != 12) throw new WavFileException("Not enough wav file bytes for header");

        // Extract parts from the header
        long riffChunkID = getLE(wavFile2.buffer, 0, 4);
        long chunkSize = getLE(wavFile2.buffer, 4, 4);
        long riffTypeID = getLE(wavFile2.buffer, 8, 4);

        // Check the header bytes contains the correct signature
        if (riffChunkID != RIFF_CHUNK_ID) throw new WavFileException("Invalid Wav Header data, incorrect riff chunk ID");
        if (riffTypeID != RIFF_TYPE_ID) throw new WavFileException("Invalid Wav Header data, incorrect riff type ID");

        // Check that the file size matches the number of bytes listed in header
        if (file.length() != chunkSize+8) {
            throw new WavFileException("Header chunk size (" + chunkSize + ") does not match file size (" + file.length() + ")");
        }

        boolean foundFormat = false;
        boolean foundData = false;

        // Search for the Format and Data Chunks
        while (true)
        {
            // Read the first 8 bytes of the chunk (ID and chunk size)
            bytesRead = wavFile2.iStream.read(wavFile2.buffer, 0, 8);
            if (bytesRead == -1) throw new WavFileException("Reached end of file without finding format chunk");
            if (bytesRead != 8) throw new WavFileException("Could not read chunk header");

            // Extract the chunk ID and Size
            long chunkID = getLE(wavFile2.buffer, 0, 4);
            chunkSize = getLE(wavFile2.buffer, 4, 4);

            // Word align the chunk size
            // chunkSize specifies the number of bytes holding data. However,
            // the data should be word aligned (2 bytes) so we need to calculate
            // the actual number of bytes in the chunk
            long numChunkBytes = (chunkSize%2 == 1) ? chunkSize+1 : chunkSize;

            if (chunkID == FMT_CHUNK_ID)
            {
                // Flag that the format chunk has been found
                foundFormat = true;

                // Read in the header info
                bytesRead = wavFile2.iStream.read(wavFile2.buffer, 0, 16);

                // Check this is uncompressed data
                int compressionCode = (int) getLE(wavFile2.buffer, 0, 2);
                if (compressionCode != 1) throw new WavFileException("Compression Code " + compressionCode + " not supported");

                // Extract the format information
                wavFile2.numChannels = (int) getLE(wavFile2.buffer, 2, 2);
                wavFile2.sampleRate = getLE(wavFile2.buffer, 4, 4);
                wavFile2.blockAlign = (int) getLE(wavFile2.buffer, 12, 2);
                wavFile2.validBits = (int) getLE(wavFile2.buffer, 14, 2);

                if (wavFile2.numChannels == 0) throw new WavFileException("Number of channels specified in header is equal to zero");
                if (wavFile2.blockAlign == 0) throw new WavFileException("Block Align specified in header is equal to zero");
                if (wavFile2.validBits < 2) throw new WavFileException("Valid Bits specified in header is less than 2");
                if (wavFile2.validBits > 64) throw new WavFileException("Valid Bits specified in header is greater than 64, this is greater than a long can hold");

                // Calculate the number of bytes required to hold 1 sample
                wavFile2.bytesPerSample = (wavFile2.validBits + 7) / 8;
                if (wavFile2.bytesPerSample * wavFile2.numChannels != wavFile2.blockAlign)
                    throw new WavFileException("Block Align does not agree with bytes required for validBits and number of channels");

                // Account for number of format bytes and then skip over
                // any extra format bytes
                numChunkBytes -= 16;
                if (numChunkBytes > 0) wavFile2.iStream.skip(numChunkBytes);
            }
            else if (chunkID == DATA_CHUNK_ID)
            {
                // Check if we've found the format chunk,
                // If not, throw an exception as we need the format information
                // before we can read the data chunk
                if (foundFormat == false) throw new WavFileException("Data chunk found before Format chunk");

                // Check that the chunkSize (wav data length) is a multiple of the
                // block align (bytes per frame)
                if (chunkSize % wavFile2.blockAlign != 0) throw new WavFileException("Data Chunk size is not multiple of Block Align");

                // Calculate the number of frames
                wavFile2.numFrames = chunkSize / wavFile2.blockAlign;

                // Flag that we've found the wave data chunk
                foundData = true;

                break;
            }
            else
            {
                // If an unknown chunk ID is found, just skip over the chunk data
                wavFile2.iStream.skip(numChunkBytes);
            }
        }

        // Throw an exception if no data chunk has been found
        if (foundData == false) throw new WavFileException("Did not find a data chunk");

        // Calculate the scaling factor for converting to a normalised double
        if (wavFile2.validBits > 8)
        {
            // If more than 8 validBits, data is signed
            // Conversion required dividing by magnitude of max negative value
            wavFile2.floatOffset = 0;
            wavFile2.floatScale = 1 << (wavFile2.validBits - 1);
        }
        else
        {
            // Else if 8 or less validBits, data is unsigned
            // Conversion required dividing by max positive value
            wavFile2.floatOffset = -1;
            wavFile2.floatScale = 0.5 * ((1 << wavFile2.validBits) - 1);
        }

        wavFile2.bufferPointer = 0;
        wavFile2.bytesRead = 0;
        wavFile2.frameCounter = 0;
        wavFile2.ioState = IOState.READING;

        return wavFile2;
    }

    // Get and Put little endian data from local buffer
    // ------------------------------------------------
    private static long getLE(byte[] buffer, int pos, int numBytes)
    {
        numBytes --;
        pos += numBytes;

        long val = buffer[pos] & 0xFF;
        for (int b=0 ; b<numBytes ; b++) val = (val << 8) + (buffer[--pos] & 0xFF);

        return val;
    }

    private static void putLE(long val, byte[] buffer, int pos, int numBytes)
    {
        for (int b=0 ; b<numBytes ; b++)
        {
            buffer[pos] = (byte) (val & 0xFF);
            val >>= 8;
            pos ++;
        }
    }

    // Sample Writing and Reading
    // --------------------------
    private void writeSample(long val) throws IOException
    {
        for (int b=0 ; b<bytesPerSample ; b++)
        {
            if (bufferPointer == BUFFER_SIZE)
            {
                oStream.write(buffer, 0, BUFFER_SIZE);
                bufferPointer = 0;
            }

            buffer[bufferPointer] = (byte) (val & 0xFF);
            val >>= 8;
            bufferPointer ++;
        }
    }

    private long readSample() throws IOException, WavFileException
    {
        long val = 0;

        for (int b=0 ; b<bytesPerSample ; b++)
        {
            if (bufferPointer == bytesRead)
            {
                int read = iStream.read(buffer, 0, BUFFER_SIZE);
                if (read == -1) throw new WavFileException("Not enough data available");
                bytesRead = read;
                bufferPointer = 0;
            }

            int v = buffer[bufferPointer];
            if (b < bytesPerSample-1 || bytesPerSample == 1) v &= 0xFF;
            val += v << (b * 8);

            bufferPointer ++;
        }

        return val;
    }

    // Integer
    // -------
    public int readFrames(int[] sampleBuffer, int numFramesToRead) throws IOException, WavFileException
    {
        return readFrames(sampleBuffer, 0, numFramesToRead);
    }

    public int readFrames(int[] sampleBuffer, int offset, int numFramesToRead) throws IOException, WavFileException
    {
        if (ioState != IOState.READING) throw new IOException("Cannot read from WavFile2 instance");

        for (int f=0 ; f<numFramesToRead ; f++)
        {
            if (frameCounter == numFrames) return f;

            for (int c=0 ; c<numChannels ; c++)
            {
                sampleBuffer[offset] = (int) readSample();
                offset ++;
            }

            frameCounter ++;
        }

        return numFramesToRead;
    }

    public int readFrames(int[][] sampleBuffer, int numFramesToRead) throws IOException, WavFileException
    {
        return readFrames(sampleBuffer, 0, numFramesToRead);
    }

    public int readFrames(int[][] sampleBuffer, int offset, int numFramesToRead) throws IOException, WavFileException
    {
        if (ioState != IOState.READING) throw new IOException("Cannot read from WavFile2 instance");

        for (int f=0 ; f<numFramesToRead ; f++)
        {
            if (frameCounter == numFrames) return f;

            for (int c=0 ; c<numChannels ; c++) sampleBuffer[c][offset] = (int) readSample();

            offset ++;
            frameCounter ++;
        }

        return numFramesToRead;
    }

    public int writeFrames(int[] sampleBuffer, int numFramesToWrite) throws IOException, WavFileException
    {
        return writeFrames(sampleBuffer, 0, numFramesToWrite);
    }

    public int writeFrames(int[] sampleBuffer, int offset, int numFramesToWrite) throws IOException, WavFileException
    {
        if (ioState != IOState.WRITING) throw new IOException("Cannot write to WavFile2 instance");

        for (int f=0 ; f<numFramesToWrite ; f++)
        {
            if (frameCounter == numFrames) return f;

            for (int c=0 ; c<numChannels ; c++)
            {
                writeSample(sampleBuffer[offset]);
                offset ++;
            }

            frameCounter ++;
        }

        return numFramesToWrite;
    }

    public int writeFrames(int[][] sampleBuffer, int numFramesToWrite) throws IOException, WavFileException
    {
        return writeFrames(sampleBuffer, 0, numFramesToWrite);
    }

    public int writeFrames(int[][] sampleBuffer, int offset, int numFramesToWrite) throws IOException, WavFileException
    {
        if (ioState != IOState.WRITING) throw new IOException("Cannot write to WavFile2 instance");

        for (int f=0 ; f<numFramesToWrite ; f++)
        {
            if (frameCounter == numFrames) return f;

            for (int c=0 ; c<numChannels ; c++) writeSample(sampleBuffer[c][offset]);

            offset ++;
            frameCounter ++;
        }

        return numFramesToWrite;
    }

    // Long
    // ----
    public int readFrames(long[] sampleBuffer, int numFramesToRead) throws IOException, WavFileException
    {
        return readFrames(sampleBuffer, 0, numFramesToRead);
    }

    public int readFrames(long[] sampleBuffer, int offset, int numFramesToRead) throws IOException, WavFileException
    {
        if (ioState != IOState.READING) throw new IOException("Cannot read from WavFile2 instance");

        for (int f=0 ; f<numFramesToRead ; f++)
        {
            if (frameCounter == numFrames) return f;

            for (int c=0 ; c<numChannels ; c++)
            {
                sampleBuffer[offset] = readSample();
                offset ++;
            }

            frameCounter ++;
        }

        return numFramesToRead;
    }

    public int readFrames(long[][] sampleBuffer, int numFramesToRead) throws IOException, WavFileException
    {
        return readFrames(sampleBuffer, 0, numFramesToRead);
    }

    public int readFrames(long[][] sampleBuffer, int offset, int numFramesToRead) throws IOException, WavFileException
    {
        if (ioState != IOState.READING) throw new IOException("Cannot read from WavFile2 instance");

        for (int f=0 ; f<numFramesToRead ; f++)
        {
            if (frameCounter == numFrames) return f;

            for (int c=0 ; c<numChannels ; c++) sampleBuffer[c][offset] = readSample();

            offset ++;
            frameCounter ++;
        }

        return numFramesToRead;
    }

    public int writeFrames(long[] sampleBuffer, int numFramesToWrite) throws IOException, WavFileException
    {
        return writeFrames(sampleBuffer, 0, numFramesToWrite);
    }

    public int writeFrames(long[] sampleBuffer, int offset, int numFramesToWrite) throws IOException, WavFileException
    {
        if (ioState != IOState.WRITING) throw new IOException("Cannot write to WavFile2 instance");

        for (int f=0 ; f<numFramesToWrite ; f++)
        {
            if (frameCounter == numFrames) return f;

            for (int c=0 ; c<numChannels ; c++)
            {
                writeSample(sampleBuffer[offset]);
                offset ++;
            }

            frameCounter ++;
        }

        return numFramesToWrite;
    }

    public int writeFrames(long[][] sampleBuffer, int numFramesToWrite) throws IOException, WavFileException
    {
        return writeFrames(sampleBuffer, 0, numFramesToWrite);
    }

    public int writeFrames(long[][] sampleBuffer, int offset, int numFramesToWrite) throws IOException, WavFileException
    {
        if (ioState != IOState.WRITING) throw new IOException("Cannot write to WavFile2 instance");

        for (int f=0 ; f<numFramesToWrite ; f++)
        {
            if (frameCounter == numFrames) return f;

            for (int c=0 ; c<numChannels ; c++) writeSample(sampleBuffer[c][offset]);

            offset ++;
            frameCounter ++;
        }

        return numFramesToWrite;
    }

    // Double
    // ------
    public int readFrames(double[] sampleBuffer, int numFramesToRead) throws IOException, WavFileException
    {
        return readFrames(sampleBuffer, 0, numFramesToRead);
    }

    public int readFrames(double[] sampleBuffer, int offset, int numFramesToRead) throws IOException, WavFileException
    {
        if (ioState != IOState.READING) throw new IOException("Cannot read from WavFile2 instance");

        for (int f=0 ; f<numFramesToRead ; f++)
        {
            if (frameCounter == numFrames) return f;

            for (int c=0 ; c<numChannels ; c++)
            {
                sampleBuffer[offset] = floatOffset + (double) readSample() / floatScale;
                offset ++;
            }

            frameCounter ++;
        }

        return numFramesToRead;
    }

    public int readFrames(double[][] sampleBuffer, int numFramesToRead) throws IOException, WavFileException
    {
        return readFrames(sampleBuffer, 0, numFramesToRead);
    }

    public int readFrames(double[][] sampleBuffer, int offset, int numFramesToRead) throws IOException, WavFileException
    {
        if (ioState != IOState.READING) throw new IOException("Cannot read from WavFile2 instance");

        for (int f=0 ; f<numFramesToRead ; f++)
        {
            if (frameCounter == numFrames) return f;

            for (int c=0 ; c<numChannels ; c++) sampleBuffer[c][offset] = floatOffset + (double) readSample() / floatScale;

            offset ++;
            frameCounter ++;
        }

        return numFramesToRead;
    }

    public int writeFrames(double[] sampleBuffer, int numFramesToWrite) throws IOException, WavFileException
    {
        return writeFrames(sampleBuffer, 0, numFramesToWrite);
    }

    public int writeFrames(double[] sampleBuffer, int offset, int numFramesToWrite) throws IOException, WavFileException
    {
        if (ioState != IOState.WRITING) throw new IOException("Cannot write to WavFile2 instance");

        for (int f=0 ; f<numFramesToWrite ; f++)
        {
            if (frameCounter == numFrames) return f;

            for (int c=0 ; c<numChannels ; c++)
            {
                writeSample((long) (floatScale * (floatOffset + sampleBuffer[offset])));
                offset ++;
            }

            frameCounter ++;
        }

        return numFramesToWrite;
    }

    public int writeFrames(double[][] sampleBuffer, int numFramesToWrite) throws IOException, WavFileException
    {
        return writeFrames(sampleBuffer, 0, numFramesToWrite);
    }

    public int writeFrames(double[][] sampleBuffer, int offset, int numFramesToWrite) throws IOException, WavFileException
    {
        if (ioState != IOState.WRITING) throw new IOException("Cannot write to WavFile2 instance");

        for (int f=0 ; f<numFramesToWrite ; f++)
        {
            if (frameCounter == numFrames) return f;

            for (int c=0 ; c<numChannels ; c++) writeSample((long) (floatScale * (floatOffset + sampleBuffer[c][offset])));

            offset ++;
            frameCounter ++;
        }

        return numFramesToWrite;
    }


    public void close() throws IOException
    {
        // Close the input stream and set to null
        if (iStream != null)
        {
            iStream.close();
            iStream = null;
        }

        if (oStream != null)
        {
            // Write out anything still in the local buffer
            if (bufferPointer > 0) oStream.write(buffer, 0, bufferPointer);

            // If an extra byte is required for word alignment, add it to the end
            if (wordAlignAdjust) oStream.write(0);

            // Close the stream and set to null
            oStream.close();
            oStream = null;
        }

        // Flag that the stream is closed
        ioState = IOState.CLOSED;
    }

    public void display()
    {
        display(System.out);
    }

    public void display(PrintStream out)
    {
        out.printf("File: %s\n", file);
        out.printf("Channels: %d, Frames: %d\n", numChannels, numFrames);
        out.printf("IO State: %s\n", ioState);
        out.printf("Sample Rate: %d, Block Align: %d\n", sampleRate, blockAlign);
        out.printf("Valid Bits: %d, Bytes per sample: %d\n", validBits, bytesPerSample);
    }

    public static void main(String[] args)
    {
        if (args.length < 1)
        {
            System.err.println("Must supply filename");
            System.exit(1);
        }

        try
        {
            for (String filename : args)
            {
                WavFile2 readWavFile2 = openWavFile2(new File(filename));
                readWavFile2.display();

                long numFrames = readWavFile2.getNumFrames();
                int numChannels = readWavFile2.getNumChannels();
                int validBits = readWavFile2.getValidBits();
                long sampleRate = readWavFile2.getSampleRate();

                WavFile2 writeWavFile2 = newWavFile2(new File("out.wav"), numChannels, numFrames, validBits, sampleRate);

                final int BUF_SIZE = 5001;

//				int[] buffer = new int[BUF_SIZE * numChannels];
//				long[] buffer = new long[BUF_SIZE * numChannels];
                double[] buffer = new double[BUF_SIZE * numChannels];

                int framesRead = 0;
                int framesWritten = 0;

                do
                {
                    framesRead = readWavFile2.readFrames(buffer, BUF_SIZE);
                    framesWritten = writeWavFile2.writeFrames(buffer, BUF_SIZE);
                    System.out.printf("%d %d\n", framesRead, framesWritten);
                }
                while (framesRead != 0);

                readWavFile2.close();
                writeWavFile2.close();
            }

            WavFile2 writeWavFile2 = newWavFile2(new File("out2.wav"), 1, 10, 23, 44100);
            double[] buffer = new double[10];
            writeWavFile2.writeFrames(buffer, 10);
            writeWavFile2.close();
        }
        catch (Exception e)
        {
            System.err.println(e);
            e.printStackTrace();
        }
    }
}