/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.rs.image;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.graphics.BitmapFactory;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.view.SurfaceView;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.Spinner;
import android.widget.TextView;
import android.view.View;
import android.util.Log;
import android.renderscript.ScriptC;
import android.renderscript.RenderScript;
import android.renderscript.Type;
import android.renderscript.Allocation;
import android.renderscript.Element;
import android.renderscript.Script;

import android.os.Environment;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

public class ImageProcessingActivity extends Activity
                                       implements SeekBar.OnSeekBarChangeListener {
    private final String TAG = "Img";
    public final String RESULT_FILE = "image_processing_result.csv";

    RenderScript mRS;
    Allocation mInPixelsAllocation;
    Allocation mInPixelsAllocation2;
    Allocation mOutPixelsAllocation;

    static class DVFSWorkaround {
        static class spinner extends Thread {
            boolean mRun = true;
            long mNextSleep;

            spinner() {
                setPriority(MIN_PRIORITY);
                start();
            }

            public void run() {
                while (mRun) {
                    Thread.yield();
                    synchronized(this) {
                        long t = java.lang.System.currentTimeMillis();
                        if (t > mNextSleep) {
                            try {
                                this.wait();
                            } catch(InterruptedException e) {
                            }
                        }
                    }
                }
            }

            public void go(long t) {
                synchronized(this) {
                    mNextSleep = t;
                    notifyAll();
                }
            }
        }

        spinner s1;
        DVFSWorkaround() {
            s1 = new spinner();
        }

        void go() {
            long t = java.lang.System.currentTimeMillis() + 2000;
            s1.go(t);
        }

        void destroy() {
            synchronized(this) {
                s1.mRun = false;
                notifyAll();
            }
        }
    }
    DVFSWorkaround mDvfsWar = new DVFSWorkaround();


    /**
     * Define enum type for test names
     */
    public enum TestName {
        // totally there are 38 test cases
        LEVELS_VEC3_RELAXED ("Levels Vec3 Relaxed"),
        LEVELS_VEC4_RELAXED ("Levels Vec4 Relaxed"),
        LEVELS_VEC3_FULL ("Levels Vec3 Full"),
        LEVELS_VEC4_FULL ("Levels Vec4 Full"),
        BLUR_RADIUS_25 ("Blur radius 25"),
        INTRINSIC_BLUE_RADIUS_25 ("Intrinsic Blur radius 25"),
        GREYSCALE ("Greyscale"),
        GRAIN ("Grain"),
        FISHEYE_FULL ("Fisheye Full"),
        FISHEYE_RELAXED ("Fisheye Relaxed"),
        FISHEYE_APPROXIMATE_FULL ("Fisheye Approximate Full"),
        FISHEYE_APPROXIMATE_RELAXED ("Fisheye Approximate Relaxed"),
        VIGNETTE_FULL ("Vignette Full"),
        VIGNETTE_RELAXED ("Vignette Relaxed"),
        VIGNETTE_APPROXIMATE_FULL ("Vignette Approximate Full"),
        VIGNETTE_APPROXIMATE_RELAXED ("Vignette Approximate Relaxed"),
        GROUP_TEST_EMULATED ("Group Test (emulated)"),
        GROUP_TEST_NATIVE ("Group Test (native)"),
        CONVOLVE_3X3 ("Convolve 3x3"),
        INTRINSICS_CONVOLVE_3X3 ("Intrinsics Convolve 3x3"),
        COLOR_MATRIX ("ColorMatrix"),
        INTRINSICS_COLOR_MATRIX ("Intrinsics ColorMatrix"),
        INTRINSICS_COLOR_MATRIX_GREY ("Intrinsics ColorMatrix Grey"),
        COPY ("Copy"),
        CROSS_PROCESS_USING_LUT ("CrossProcess (using LUT)"),
        CONVOLVE_5X5 ("Convolve 5x5"),
        INTRINSICS_CONVOLVE_5X5 ("Intrinsics Convolve 5x5"),
        MANDELBROT_FLOAT ("Mandelbrot fp32"),
        INTRINSICS_BLEND ("Intrinsics Blend"),
        INTRINSICS_BLUR_25G ("Intrinsics Blur 25 uchar"),
        VIBRANCE ("Vibrance"),
        BW_FILTER ("BW Filter"),
        SHADOWS ("Shadows"),
        CONTRAST ("Contrast"),
        EXPOSURE ("Exposure"),
        WHITE_BALANCE ("White Balance"),
        COLOR_CUBE ("Color Cube"),
        COLOR_CUBE_3D_INTRINSIC ("Color Cube (3D LUT intrinsic)"),
        USAGE_IO ("Usage io"),
        ARTISTIC_1("Artistic 1"),
        HISTOGRAM ("Histogram"),
        MANDELBROT_DOUBLE ("Mandelbrot fp64"),
        RESIZE_BICUBIC_SCRIPT ("Resize BiCubic Script"),
        RESIZE_BICUBIC_INTRINSIC ("Resize BiCubic Intrinsic");


        private final String name;

        private TestName(String s) {
            name = s;
        }

        // return quoted string as displayed test name
        public String toString() {
            return name;
        }
    }

    Bitmap mBitmapIn;
    Bitmap mBitmapIn2;
    Bitmap mBitmapOut;

    private Spinner mSpinner;
    private SeekBar mBar1;
    private SeekBar mBar2;
    private SeekBar mBar3;
    private SeekBar mBar4;
    private SeekBar mBar5;
    private TextView mText1;
    private TextView mText2;
    private TextView mText3;
    private TextView mText4;
    private TextView mText5;

    private TextView mBenchmarkResult;
    private Spinner mTestSpinner;

    private ImageView mDisplayView;

    private boolean mDoingBenchmark;

    private TestBase mTest;
    private int mRunCount;

    public void updateDisplay() {
        mHandler.sendMessage(Message.obtain());
    }

    private Handler mHandler = new Handler() {
        // Allow the filter to complete without blocking the UI
        // thread.  When the message arrives that the op is complete
        // we will either mark completion or start a new filter if
        // more work is ready.  Either way, display the result.
        @Override
        public void handleMessage(Message msg) {
            boolean doTest = false;
            synchronized(this) {
                if (mRS == null) {
                    return;
                }
                mTest.updateBitmap(mBitmapOut);
                mDisplayView.invalidate();
                if (mRunCount > 0) {
                    mRunCount--;
                    if (mRunCount > 0) {
                        doTest = true;
                    }
                }

                if (doTest) {
                    mTest.runTestSendMessage();
                }
            }
        }

    };

    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
        if (fromUser) {

            if (seekBar == mBar1) {
                mTest.onBar1Changed(progress);
            } else if (seekBar == mBar2) {
                mTest.onBar2Changed(progress);
            } else if (seekBar == mBar3) {
                mTest.onBar3Changed(progress);
            } else if (seekBar == mBar4) {
                mTest.onBar4Changed(progress);
            } else if (seekBar == mBar5) {
                mTest.onBar5Changed(progress);
            }

            boolean doTest = false;
            synchronized(this) {
                if (mRunCount == 0) {
                    doTest = true;
                    mRunCount = 1;
                } else {
                    mRunCount = 2;
                }
            }
            if (doTest) {
                mTest.runTestSendMessage();
            }
        }
    }

    public void onStartTrackingTouch(SeekBar seekBar) {
    }

    public void onStopTrackingTouch(SeekBar seekBar) {
    }

    void setupBars() {
        mSpinner.setVisibility(View.VISIBLE);
        mTest.onSpinner1Setup(mSpinner);

        mBar1.setVisibility(View.VISIBLE);
        mText1.setVisibility(View.VISIBLE);
        mTest.onBar1Setup(mBar1, mText1);

        mBar2.setVisibility(View.VISIBLE);
        mText2.setVisibility(View.VISIBLE);
        mTest.onBar2Setup(mBar2, mText2);

        mBar3.setVisibility(View.VISIBLE);
        mText3.setVisibility(View.VISIBLE);
        mTest.onBar3Setup(mBar3, mText3);

        mBar4.setVisibility(View.VISIBLE);
        mText4.setVisibility(View.VISIBLE);
        mTest.onBar4Setup(mBar4, mText4);

        mBar5.setVisibility(View.VISIBLE);
        mText5.setVisibility(View.VISIBLE);
        mTest.onBar5Setup(mBar5, mText5);
    }


    void changeTest(TestName testName) {
        if (mTest != null) {
            mTest.destroy();
        }
        switch(testName) {
        case LEVELS_VEC3_RELAXED:
            mTest = new LevelsV4(false, false);
            break;
        case LEVELS_VEC4_RELAXED:
            mTest = new LevelsV4(false, true);
            break;
        case LEVELS_VEC3_FULL:
            mTest = new LevelsV4(true, false);
            break;
        case LEVELS_VEC4_FULL:
            mTest = new LevelsV4(true, true);
            break;
        case BLUR_RADIUS_25:
            mTest = new Blur25(false);
            break;
        case INTRINSIC_BLUE_RADIUS_25:
            mTest = new Blur25(true);
            break;
        case GREYSCALE:
            mTest = new Greyscale();
            break;
        case GRAIN:
            mTest = new Grain();
            break;
        case FISHEYE_FULL:
            mTest = new Fisheye(false, false);
            break;
        case FISHEYE_RELAXED:
            mTest = new Fisheye(false, true);
            break;
        case FISHEYE_APPROXIMATE_FULL:
            mTest = new Fisheye(true, false);
            break;
        case FISHEYE_APPROXIMATE_RELAXED:
            mTest = new Fisheye(true, true);
            break;
        case VIGNETTE_FULL:
            mTest = new Vignette(false, false);
            break;
        case VIGNETTE_RELAXED:
            mTest = new Vignette(false, true);
            break;
        case VIGNETTE_APPROXIMATE_FULL:
            mTest = new Vignette(true, false);
            break;
        case VIGNETTE_APPROXIMATE_RELAXED:
            mTest = new Vignette(true, true);
            break;
        case GROUP_TEST_EMULATED:
            mTest = new GroupTest(false);
            break;
        case GROUP_TEST_NATIVE:
            mTest = new GroupTest(true);
            break;
        case CONVOLVE_3X3:
            mTest = new Convolve3x3(false);
            break;
        case INTRINSICS_CONVOLVE_3X3:
            mTest = new Convolve3x3(true);
            break;
        case COLOR_MATRIX:
            mTest = new ColorMatrix(false, false);
            break;
        case INTRINSICS_COLOR_MATRIX:
            mTest = new ColorMatrix(true, false);
            break;
        case INTRINSICS_COLOR_MATRIX_GREY:
            mTest = new ColorMatrix(true, true);
            break;
        case COPY:
            mTest = new Copy();
            break;
        case CROSS_PROCESS_USING_LUT:
            mTest = new CrossProcess();
            break;
        case CONVOLVE_5X5:
            mTest = new Convolve5x5(false);
            break;
        case INTRINSICS_CONVOLVE_5X5:
            mTest = new Convolve5x5(true);
            break;
        case MANDELBROT_FLOAT:
            mTest = new Mandelbrot(false);
            break;
        case INTRINSICS_BLEND:
            mTest = new Blend();
            break;
        case INTRINSICS_BLUR_25G:
            mTest = new Blur25G();
            break;
        case VIBRANCE:
            mTest = new Vibrance();
            break;
        case BW_FILTER:
            mTest = new BWFilter();
            break;
        case SHADOWS:
            mTest = new Shadows();
            break;
        case CONTRAST:
            mTest = new Contrast();
            break;
        case EXPOSURE:
            mTest = new Exposure();
            break;
        case WHITE_BALANCE:
            mTest = new WhiteBalance();
            break;
        case COLOR_CUBE:
            mTest = new ColorCube(false);
            break;
        case COLOR_CUBE_3D_INTRINSIC:
            mTest = new ColorCube(true);
            break;
        case USAGE_IO:
            mTest = new UsageIO();
            break;
        case ARTISTIC_1:
            mTest = new Artistic1();
            break;
        case HISTOGRAM:
            mTest = new Histogram();
            break;
        case MANDELBROT_DOUBLE:
            mTest = new Mandelbrot(true);
            break;
        case RESIZE_BICUBIC_SCRIPT:
            mTest = new Resize(false);
            break;
        case RESIZE_BICUBIC_INTRINSIC:
            mTest = new Resize(true);
            break;
        }

        mTest.createBaseTest(this, mBitmapIn, mBitmapIn2, mBitmapOut);
        setupBars();

        mTest.runTest();
        updateDisplay();
        mBenchmarkResult.setText("Result: not run");
    }

    void setupTests() {
        mTestSpinner.setAdapter(new ArrayAdapter<TestName>(
            this, R.layout.spinner_layout, TestName.values()));
    }

    private AdapterView.OnItemSelectedListener mTestSpinnerListener =
            new AdapterView.OnItemSelectedListener() {
                public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
                    changeTest(TestName.values()[pos]);
                }

                public void onNothingSelected(AdapterView parent) {

                }
            };

    void init() {
        mBitmapIn = loadBitmap(R.drawable.img1600x1067);
        mBitmapIn2 = loadBitmap(R.drawable.img1600x1067b);
        mBitmapOut = Bitmap.createBitmap(mBitmapIn.getWidth(), mBitmapIn.getHeight(),
                                         mBitmapIn.getConfig());

        mDisplayView = (ImageView) findViewById(R.id.display);
        mDisplayView.setImageBitmap(mBitmapOut);

        mSpinner = (Spinner) findViewById(R.id.spinner1);

        mBar1 = (SeekBar) findViewById(R.id.slider1);
        mBar2 = (SeekBar) findViewById(R.id.slider2);
        mBar3 = (SeekBar) findViewById(R.id.slider3);
        mBar4 = (SeekBar) findViewById(R.id.slider4);
        mBar5 = (SeekBar) findViewById(R.id.slider5);

        mBar1.setOnSeekBarChangeListener(this);
        mBar2.setOnSeekBarChangeListener(this);
        mBar3.setOnSeekBarChangeListener(this);
        mBar4.setOnSeekBarChangeListener(this);
        mBar5.setOnSeekBarChangeListener(this);

        mText1 = (TextView) findViewById(R.id.slider1Text);
        mText2 = (TextView) findViewById(R.id.slider2Text);
        mText3 = (TextView) findViewById(R.id.slider3Text);
        mText4 = (TextView) findViewById(R.id.slider4Text);
        mText5 = (TextView) findViewById(R.id.slider5Text);

        mTestSpinner = (Spinner) findViewById(R.id.filterselection);
        mTestSpinner.setOnItemSelectedListener(mTestSpinnerListener);

        mBenchmarkResult = (TextView) findViewById(R.id.benchmarkText);
        mBenchmarkResult.setText("Result: not run");


        mRS = RenderScript.create(this);
        mInPixelsAllocation = Allocation.createFromBitmap(mRS, mBitmapIn,
                                                          Allocation.MipmapControl.MIPMAP_NONE,
                                                          Allocation.USAGE_SHARED |
                                                          Allocation.USAGE_GRAPHICS_TEXTURE |
                                                          Allocation.USAGE_SCRIPT);
        mInPixelsAllocation2 = Allocation.createFromBitmap(mRS, mBitmapIn2,
                                                           Allocation.MipmapControl.MIPMAP_NONE,
                                                           Allocation.USAGE_SHARED |
                                                           Allocation.USAGE_GRAPHICS_TEXTURE |
                                                           Allocation.USAGE_SCRIPT);
        mOutPixelsAllocation = Allocation.createFromBitmap(mRS, mBitmapOut);


        setupTests();
        changeTest(TestName.LEVELS_VEC3_RELAXED);
    }

    void cleanup() {
        synchronized(this) {
            RenderScript rs = mRS;
            mRS = null;
            while(mDoingBenchmark) {
                try {
                    Thread.sleep(1, 0);
                } catch(InterruptedException e) {
                }

            }
            rs.destroy();
        }

        mInPixelsAllocation = null;
        mInPixelsAllocation2 = null;
        mOutPixelsAllocation = null;
        mBitmapIn = null;
        mBitmapIn2 = null;
        mBitmapOut = null;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        init();
    }

    @Override
    protected void onPause() {
        super.onPause();

        cleanup();
    }


    @Override
    protected void onResume() {
        super.onResume();

        init();
    }

    private Bitmap loadBitmap(int resource) {
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inPreferredConfig = Bitmap.Config.ARGB_8888;
        return BitmapFactory.decodeResource(getResources(), resource, options);
    }

    // button hook
    public void benchmark(View v) {
        float t = getBenchmark();
        //long javaTime = javaFilter();
        //mBenchmarkResult.setText("RS: " + t + " ms  Java: " + javaTime + " ms");
        mBenchmarkResult.setText("Result: " + t + " ms");
        Log.v(TAG, "getBenchmark: Renderscript frame time core ms " + t);
    }

    public void benchmark_all(View v) {
        // write result into a file
        File externalStorage = Environment.getExternalStorageDirectory();
        if (!externalStorage.canWrite()) {
            Log.v(TAG, "sdcard is not writable");
            return;
        }
        File resultFile = new File(externalStorage, RESULT_FILE);
        resultFile.setWritable(true, false);
        try {
            BufferedWriter rsWriter = new BufferedWriter(new FileWriter(resultFile));
            Log.v(TAG, "Saved results in: " + resultFile.getAbsolutePath());
            for (TestName tn: TestName.values()) {
                changeTest(tn);
                float t = getBenchmark();
                String s = new String("" + tn.toString() + ", " + t);
                rsWriter.write(s + "\n");
                Log.v(TAG, "Test " + s + "ms\n");
            }
            rsWriter.close();
        } catch (IOException e) {
            Log.v(TAG, "Unable to write result file " + e.getMessage());
        }
        changeTest(TestName.LEVELS_VEC3_RELAXED);
    }



    // For benchmark test
    public float getBenchmark() {
        if (mRS == null) {
            return 0;
        }
        mDoingBenchmark = true;

        mDvfsWar.go();
        mTest.setupBenchmark();
        long result = 0;

        //Log.v(TAG, "Warming");
        long t = java.lang.System.currentTimeMillis() + 250;
        do {
            mTest.runTest();
            mTest.finish();
        } while (t > java.lang.System.currentTimeMillis());

        //Log.v(TAG, "Benchmarking");
        int ct = 0;
        t = java.lang.System.currentTimeMillis();
        do {
            mTest.runTest();
            mTest.finish();
            ct++;
        } while ((t+1000) > java.lang.System.currentTimeMillis());
        t = java.lang.System.currentTimeMillis() - t;
        float ft = (float)t;
        ft /= ct;

        mTest.exitBenchmark();
        mDoingBenchmark = false;

        return ft;
    }
}
