/*
 * Copyright (c) 2016 Uguisu-Anne Project
 *
 * This software is distributed under an MIT-style license.
 * See LICENSE file for more information.
 */

package com.github.kimikage.uguisuan;

import android.app.Activity;
import android.app.IntentService;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.AssetManager;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Arrays;

public class WaveReaderService extends IntentService {
    public static final String TAG = "WaveReaderService";
    private static final String ARG_FILE_PATH = "filePath";

    private LeDataInputStream mStream = null;
    private Wave mWave = new Wave();

    public WaveReaderService() {
        super(TAG);
    }

    public static void start(Activity activity, String filePath) {
        Intent intent = new Intent(activity, WaveReaderService.class);
        intent.putExtra(ARG_FILE_PATH, filePath);
        activity.startService(intent);
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        if (intent == null) {
            return;
        }
        String filePath = intent.getStringExtra(ARG_FILE_PATH);

        Log.d(TAG, "start");

        Intent bi = new Intent();
        bi.setAction(Receiver.ACTION_PRE);
        LocalBroadcastManager.getInstance(this).sendBroadcast(bi);

        try {
            if (filePath.equals("!")) {
                AssetManager am = getResources().getAssets();
                mStream = new LeDataInputStream(am.open("japanese.wav"));
            } else {
                mStream = new LeDataInputStream(new FileInputStream(filePath));
            }

            readHeader();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        bi.setAction(Receiver.ACTION_UPDATE);

        byte[] buffer = new byte[4];
        int remain = mWave.getRiffSize() - mWave.getFmtSize();
        try {
            while (remain >= 8) {
                mStream.read(buffer);
                int size = mStream.readInt();
                remain -= size + 8;
                if (Arrays.equals(buffer, Wave.CHUNK_ID_DATA)) {
                    mWave.setDataSize(size);
                    readDataChunk();
                } else {
                    mStream.skip(size);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        UguisuAnne app = (UguisuAnne) getApplicationContext();
        app.setSourceWave(mWave);

        bi.setAction(Receiver.ACTION_POST);
        LocalBroadcastManager.getInstance(this).sendBroadcast(bi);
    }

    private void readHeader() throws IOException {
        if (mStream == null) {
            return;
        }
        if (mStream.available() < Wave.MIN_SIZE_RIFF) {
            throw new IllegalArgumentException("The file is too short.");
        }
        byte[] buffer = new byte[4];
        mStream.read(buffer);
        if (!Arrays.equals(buffer, Wave.CHUNK_ID_RIFF)) {
            throw new IllegalArgumentException("RIFF chunk is not found.");
        }
        mWave.setRiffSize(mStream.readInt());
        mStream.read(buffer);
        if (!Arrays.equals(buffer, Wave.CHUNK_ID_WAVE)) {
            throw new IllegalArgumentException("WAVE chunk is not found.");
        }
        mStream.read(buffer);
        if (!Arrays.equals(buffer, Wave.CHUNK_ID_FMT)) {
            throw new IllegalArgumentException("fmt chunk is not found.");
        }
        mWave.setFmtSize(mStream.readInt());
        mWave.setFormatTag(mStream.readUnsignedShort());
        switch (mWave.getFormatTag()) {
            case Wave.WAVE_FORMAT_PCM:
                break;
            case Wave.WAVE_FORMAT_IEEE_FLOAT:
                throw new IllegalArgumentException("float format is not supported.");
            default:
                throw new IllegalArgumentException("The format is not supported.");
        }
        mWave.setNumberOfChannels(mStream.readUnsignedShort());
        switch (mWave.getNumberOfChannels()) {
            case 1:
                break;
            case 2:
                throw new IllegalArgumentException("stereo is not supported.");
            default:
                throw new IllegalArgumentException("multi-channel is not supported.");
        }
        mWave.setSamplesPerSec(mStream.readInt());
        switch (mWave.getSamplesPerSec()) {
            case 48000:
                break;
            default:
                throw new IllegalArgumentException("48 kHz is only available.");
        }
        mWave.setAvgBytesPerSec(mStream.readInt());
        mWave.setBlockAlign(mStream.readUnsignedShort());
        mWave.setBitsPerSample(mStream.readUnsignedShort());
        switch (mWave.getBitsPerSample()) {
            case 16:
                break;
            default:
                throw new IllegalArgumentException("16 bit/Sample is only available.");
        }
        mStream.skipBytes(mWave.getFmtSize() - Wave.MIN_SIZE_FMT);
    }

    private void readDataChunk() throws IOException {
        int size = mWave.getDataSize();
        byte[] pcm = new byte[mWave.getDataSize()];
        int read = 0;

        Intent bi = new Intent();
        bi.setAction(Receiver.ACTION_UPDATE);
        bi.putExtra(Receiver.ARG_WAVE, mWave);

        int segmentSize = 4096 * 2;
        while (read <= (size - segmentSize)) {
            Log.d(TAG, "read: " + read + "/" + size);
            bi.putExtra(Receiver.ARG_PROGRESS, read);
            LocalBroadcastManager.getInstance(this).sendBroadcast(bi);

            mStream.read(pcm, read, segmentSize);
            read += segmentSize;

            try {
                Thread.sleep(10); // for debug
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        mStream.read(pcm, read, size - read);
        bi.putExtra(Receiver.ARG_PROGRESS, size);
        LocalBroadcastManager.getInstance(this).sendBroadcast(bi);

        mWave.setPcm(pcm);
    }

    public interface Callbacks {
        void onPreExecute();

        void onProgressUpdate(Wave wave, int progress);

        void onPostExecute();
    }

    public static class Receiver extends BroadcastReceiver {
        public static final String ARG_PROGRESS = "progress";
        public static final String ARG_WAVE = "wave";
        public static final String ACTION_PRE = "WaveReaderService.onPreExecute";
        public static final String ACTION_UPDATE = "WaveReaderService.onProgressUpdate";
        public static final String ACTION_POST = "WaveReaderService.onPostExecute";

        private static IntentFilter sFilter;

        static {
            sFilter = new IntentFilter();
            sFilter.addAction(ACTION_PRE);
            sFilter.addAction(ACTION_UPDATE);
            sFilter.addAction(ACTION_POST);
        }

        private Callbacks mCallbacks;

        public Receiver(Callbacks callbacks) {
            mCallbacks = callbacks;
        }

        @Override
        public void onReceive(Context context, Intent intent) {
            if (mCallbacks == null) {
                return;
            }
            switch (intent.getAction()) {
                case ACTION_PRE:
                    mCallbacks.onPreExecute();
                    return;
                case ACTION_UPDATE:
                    Wave wave = intent.getParcelableExtra(ARG_WAVE);
                    int progress = intent.getIntExtra(ARG_PROGRESS, 0);
                    mCallbacks.onProgressUpdate(wave, progress);
                    return;
                case ACTION_POST:
                    mCallbacks.onPostExecute();
                    return;
            }
        }

        public void register(Context context) {
            LocalBroadcastManager.getInstance(context).registerReceiver(this, sFilter);
        }

        public void unregister(Context context) {
            LocalBroadcastManager.getInstance(context).unregisterReceiver(this);
        }
    }
}
