package ibxm;

import java.io.*;

public class ProTracker
{
    public static boolean is_mod(byte[] header_1084_bytes)
    {
        boolean is_mod;
        is_mod = false;

        if (calculate_num_channels(header_1084_bytes) > 0)
        {
            is_mod = true;
        }

        return is_mod;
    }

    public static Module load_mod(byte[] header_1084_bytes, DataInput data_input) throws IOException
    {
        int num_channels, channel_idx, panning;
        int sequence_length, restart_idx, sequence_idx;
        int num_patterns, pattern_idx, instrument_idx;
        Module module;
        num_channels = calculate_num_channels(header_1084_bytes);

        if (num_channels < 1)
        {
            throw new IllegalArgumentException("ProTracker: Unrecognised module format!");
        }

        module = new Module();
        module.song_title = ascii_text(header_1084_bytes, 0, 20);
        module.pal = (num_channels == 4);
        module.global_volume = 64;
        module.channel_gain = IBXM.FP_ONE * 3 / 8;
        module.default_speed = 6;
        module.default_tempo = 125;
        module.set_num_channels(num_channels);

        for (channel_idx = 0; channel_idx < num_channels; channel_idx++)
        {
            panning = 64;

            if ((channel_idx & 0x03) == 0x01 || (channel_idx & 0x03) == 0x02)
            {
                panning = 192;
            }

            module.set_initial_panning(channel_idx, panning);
        }

        sequence_length = header_1084_bytes[ 950 ] & 0x7F;
        restart_idx = header_1084_bytes[ 951 ] & 0x7F;

        if (restart_idx >= sequence_length)
        {
            restart_idx = 0;
        }

        module.restart_sequence_index = restart_idx;
        module.set_sequence_length(sequence_length);

        for (sequence_idx = 0; sequence_idx < sequence_length; sequence_idx++)
        {
            module.set_sequence(sequence_idx, header_1084_bytes[ 952 + sequence_idx ] & 0x7F);
        }

        num_patterns = calculate_num_patterns(header_1084_bytes);
        module.set_num_patterns(num_patterns);

        for (pattern_idx = 0; pattern_idx < num_patterns; pattern_idx++)
        {
            module.set_pattern(pattern_idx, read_mod_pattern(data_input, num_channels));
        }

        module.set_num_instruments(31);

        for (instrument_idx = 1; instrument_idx <= 31; instrument_idx++)
        {
            module.set_instrument(instrument_idx, read_mod_instrument(header_1084_bytes, instrument_idx, data_input));
        }

        return module;
    }

    private static int calculate_num_patterns(byte[] module_header)
    {
        int num_patterns, order_entry, pattern_idx;
        num_patterns = 0;

        for (pattern_idx = 0; pattern_idx < 128; pattern_idx++)
        {
            order_entry = module_header[ 952 + pattern_idx ] & 0x7F;

            if (order_entry >= num_patterns)
            {
                num_patterns = order_entry + 1;
            }
        }

        return num_patterns;
    }

    private static int calculate_num_channels(byte[] module_header)
    {
        int num_channels;

        switch ((module_header[ 1082 ] << 8) | module_header[ 1083 ])
        {
            case 0x4b2e: /* M.K. */
            case 0x4b21: /* M!K! */
            case 0x542e: /* N.T. */
            case 0x5434: /* FLT4 */
                num_channels = 4;
                break;

            case 0x484e: /* xCHN */
                num_channels = module_header[ 1080 ] - 48;
                break;

            case 0x4348: /* xxCH */
                num_channels = ((module_header[ 1080 ] - 48) * 10) + (module_header[ 1081 ] - 48);
                break;

            default:
                /* Not recognised. */
                num_channels = 0;
                break;
        }

        return num_channels;
    }

    private static Pattern read_mod_pattern(DataInput data_input, int num_channels) throws IOException
    {
        int input_idx, output_idx;
        int period, instrument, effect, effect_param;
        Pattern pattern;
        byte[] input_pattern_data, output_pattern_data;
        pattern = new Pattern();
        pattern.num_rows = 64;
        input_pattern_data = new byte[ 64 * num_channels * 4 ];
        output_pattern_data = new byte[ 64 * num_channels * 5 ];
        data_input.readFully(input_pattern_data);
        input_idx = 0;
        output_idx = 0;

        while (input_idx < input_pattern_data.length)
        {
            period = (input_pattern_data[ input_idx ] & 0x0F) << 8;
            period = period | (input_pattern_data[ input_idx + 1 ] & 0xFF);
            output_pattern_data[ output_idx ] = to_key(period);
            instrument = input_pattern_data[ input_idx ] & 0x10;
            instrument = instrument | ((input_pattern_data[ input_idx + 2 ] & 0xF0) >> 4);
            output_pattern_data[ output_idx + 1 ] = (byte) instrument;
            effect = input_pattern_data[ input_idx + 2 ] & 0x0F;
            effect_param = input_pattern_data[ input_idx + 3 ] & 0xFF;

            if (effect == 0x01 && effect_param == 0)
            {
                /* Portamento up of zero has no effect. */
                effect = 0;
            }

            if (effect == 0x02 && effect_param == 0)
            {
                /* Portamento down of zero has no effect. */
                effect = 0;
            }

            if (effect == 0x08 && num_channels == 4)
            {
                /* Some Amiga mods use effect 0x08 for reasons other than panning.*/
                effect = 0;
                effect_param = 0;
            }

            if (effect == 0x0A && effect_param == 0)
            {
                /* Volume slide of zero has no effect.*/
                effect = 0;
            }

            if (effect == 0x05 && effect_param == 0)
            {
                /* Porta + Volume slide of zero has no effect.*/
                effect = 0x03;
            }

            if (effect == 0x06 && effect_param == 0)
            {
                /* Vibrato + Volume slide of zero has no effect.*/
                effect = 0x04;
            }

            output_pattern_data[ output_idx + 3 ] = (byte) effect;
            output_pattern_data[ output_idx + 4 ] = (byte) effect_param;
            input_idx += 4;
            output_idx += 5;
        }

        pattern.set_pattern_data(output_pattern_data);
        return pattern;
    }

    private static Instrument read_mod_instrument(byte[] mod_header, int idx, DataInput data_input) throws IOException
    {
        int header_offset, sample_data_length;
        int loop_start, loop_length, sample_idx, fine_tune;
        Instrument instrument;
        Sample sample;
        byte[] raw_sample_data;
        short[] sample_data;
        header_offset = (idx - 1) * 30 + 20;
        instrument = new Instrument();
        instrument.name = ascii_text(mod_header, header_offset, 22);
        sample = new Sample();
        sample_data_length = unsigned_short_be(mod_header, header_offset + 22) << 1;
        fine_tune = mod_header[ header_offset + 24 ] & 0x0F;

        if (fine_tune > 7)
        {
            fine_tune -= 16;
        }

        sample.transpose = (fine_tune << IBXM.FP_SHIFT) / 96;
        sample.volume = mod_header[ header_offset + 25 ] & 0x7F;
        loop_start = unsigned_short_be(mod_header, header_offset + 26) << 1;
        loop_length = unsigned_short_be(mod_header, header_offset + 28) << 1;

        if (loop_length < 4)
        {
            loop_length = 0;
        }

        raw_sample_data = new byte[ sample_data_length ];
        sample_data = new short[ sample_data_length ];

        try
        {
            data_input.readFully(raw_sample_data);
        }
        catch (EOFException e)
        {
            System.out.println("ProTracker: Instrument " + idx + " has samples missing.");
        }

        for (sample_idx = 0; sample_idx < raw_sample_data.length; sample_idx++)
        {
            sample_data[ sample_idx ] = (short)(raw_sample_data[ sample_idx ] << 8);
        }

        sample.set_sample_data(sample_data, loop_start, loop_length, false);
        instrument.set_num_samples(1);
        instrument.set_sample(0, sample);
        return instrument;
    }

    private static byte to_key(int period)
    {
        int oct, key;

        if (period < 32)
        {
            key = 0;
        }
        else
        {
            oct = LogTable.log_2(7256) - LogTable.log_2(period);

            if (oct < 0)
            {
                key = 0;
            }
            else
            {
                key = oct * 12;
                key = key >> (IBXM.FP_SHIFT - 1);
                key = (key >> 1) + (key & 1);
            }
        }

        return (byte) key;
    }

    private static int unsigned_short_be(byte[] buf, int offset)
    {
        int value;
        value = (buf[ offset ] & 0xFF) << 8;
        value = value | (buf[ offset + 1 ] & 0xFF);
        return value;
    }

    private static String ascii_text(byte[] buffer, int offset, int length)
    {
        int idx, chr;
        byte[] string_buffer;
        String string;
        string_buffer = new byte[ length ];

        for (idx = 0; idx < length; idx++)
        {
            chr = buffer[ offset + idx ];

            if (chr < 32)
            {
                chr = 32;
            }

            string_buffer[ idx ] = (byte) chr;
        }

        try
        {
            string = new String(string_buffer, 0, length, "ISO-8859-1");
        }
        catch (UnsupportedEncodingException e)
        {
            string = "";
        }

        return string;
    }
}
