package com.example.myapplication.ui;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbManager;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;

import android.os.Bundle;
import android.text.method.ScrollingMovementMethod;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

import com.example.myapplication.util.DfuRuntimeDetachRunnable;
import com.example.myapplication.R;
import com.example.myapplication.util.SenxorDevicesHelper;
import com.example.myapplication.util.SharedPreferencesHelper;
import com.example.myapplication.util.USBDevDiscoverHelper;
import com.example.myapplication.databinding.ActivityMainBinding;

/* TODO: check for context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_USB_HOST);
 *       and display a simple activity citing the lack of this feature.
 *       Hence this app could not function on this Android device. :-(
 */

public class fwUpgradeActivity extends AppCompatActivity
{


    private enum fwUpgradeUIMode{
        DEV_NOT_FOUND,
        DEV_NORMAL,
        DEV_DFU
    }
    private ActivityMainBinding mActivityMainBinding;
    private static final String NEWLINE = System.getProperty("line.separator");
    private static final String TAG = "MainActivity";
    /* Note: RECOGNISED_USB_VID and _PID should match res/xml/device_filter.xml */
    private static final int RECOGNISED_USB_VID[] = {
            1046,   // 0x0416
            1046,   // 0x0416
            1046,   // 0x0416
            1046,   // 0x0416
            1046,   // 0x0416
            1046,   // 0x0416
    };
    private static final int RECOGNISED_USB_PID[] = {
            20496,  // 0x5010
            20497,  // 0x5011
            20498,  // 0x5012
            45088,  // 0xb020
            45058,  // 0xB002
            48624,  // 0xBDF0
    };
    private MainBroadcastReceiver mainBroadcastReceiver;

    private SenxorDevicesHelper senxorDevices;
    private UsbDevice mDfuRunTimeDevice;
    private UsbDevice mDfuModeDevice;
    private IntentFilter mIntentFilter;
    private SharedPreferencesHelper mSharedPreferencesHelper;
    private USBDevDiscoverHelper mUsbDevDiscoverHelper;
    private int [][] mRdFwVer;

    private int [][] mSelPldFwVer;



    /**
     * onCreate
     * Android activity cycle
     * @param       savedInstanceState - Bundle contains saved state
     * @exception   None
     * @return      None
     */
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {

        super.onCreate(savedInstanceState);
        //Binding setup
        this.mActivityMainBinding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(this.mActivityMainBinding.getRoot());
        setSupportActionBar(this.mActivityMainBinding.materialToolbar);
        this.mActivityMainBinding.tvMain.setMovementMethod(new ScrollingMovementMethod());

        //UI event handler setup
        MainButtonHandler buttonHandler = new MainButtonHandler();
        FwSelectorChangeHandler spinnerItemChangesListener = new FwSelectorChangeHandler();
        this.mActivityMainBinding.btnClear.setOnClickListener(buttonHandler);
        this.mActivityMainBinding.btnDfuDetach.setOnClickListener(buttonHandler);
        this.mActivityMainBinding.btnUpdate.setOnClickListener(buttonHandler);
        this.mActivityMainBinding.menuVersionSelector.setOnItemSelectedListener(spinnerItemChangesListener);
        this.setUIMode(fwUpgradeUIMode.DEV_NOT_FOUND);

        this.mSharedPreferencesHelper = new SharedPreferencesHelper(this);

        this.mainBroadcastReceiver = new MainBroadcastReceiver();
        this.mIntentFilter = new IntentFilter();
        this.mIntentFilter.addAction("com.example.myapplication.findDev");
        this.mIntentFilter.addAction("com.example.myapplication.findDfu");
        this.mIntentFilter.addAction("com.example.myapplication.fwReadDone");
        this.mIntentFilter.addAction("com.example.myapplication.fwVerRdDone");
        this.mIntentFilter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
        this.mIntentFilter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);

        this.mUsbDevDiscoverHelper = new USBDevDiscoverHelper(this);
        this.senxorDevices = new SenxorDevicesHelper(this, this.mDfuHandler);
        this.mRdFwVer = new int[1][3];
        this.mSelPldFwVer = new int[1][3];

    }//End onCreate

    /**
     * onCreateOptionsMenu
     * Android activity lifecycle
     * @param       None
     * @exception   None
     * @return      None
     */
    @Override
    public boolean onCreateOptionsMenu(Menu menu)
    {
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return super.onCreateOptionsMenu(menu);
    }//End onCreateOptionsMenu

    /**
     * onOptionsItemSelected
     * @brief       Event handler when menu item is selected
     * @param       None
     * @exception   None
     * @return      None
     */
    @Override
    public boolean onOptionsItemSelected(MenuItem item)
    {
        final int selItemId = item.getItemId();
        if (item.getItemId() == R.id.main_menu_cfg)
        {
            startActivity(new Intent(this, dfuHostSettingsActivity.class));
            return true;
        }//End if

        if (selItemId == R.id.refresh_dev)
        {
            this.mUsbDevDiscoverHelper.discover();
            if (this.mUsbDevDiscoverHelper.getUsbDevList().size() == 0)
            {
                Toast.makeText(this, this.getString(R.string.MSG_INFO_NO_DEV), Toast.LENGTH_SHORT).show();
                return true;
            }
            this.senxorDevices.attach(this.mUsbDevDiscoverHelper.getUsbDevList());
            Toast.makeText(this, this.getString(R.string.MSG_INFO_DEV_RELD), Toast.LENGTH_SHORT).show();
            return true;
        }
        return super.onOptionsItemSelected(item);
    }//End onOptionsItemSelected

    @Override
    protected void onPause()
    {
        super.onPause();
    }//End onPause

    /**
     * onResume
     * Android activity cycle
     * @param       None
     * @exception   None
     * @return      None
     */
    @Override
    protected void onResume()
    {
        super.onResume();

        //Register receiver
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
        {
            registerReceiver(this.mainBroadcastReceiver, this.mIntentFilter, Context.RECEIVER_EXPORTED);
        }
        else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.TIRAMISU)
        {
            registerReceiver(this.mainBroadcastReceiver, this.mIntentFilter,RECEIVER_EXPORTED);
        }
        else
        {
            registerReceiver(this.mainBroadcastReceiver, this.mIntentFilter);
        }//End if-else

        //Check preferences
        final boolean isLogEn = (boolean) this.mSharedPreferencesHelper.getPref(getString(R.string.PREF_LOG_EN),false);

        if(isLogEn)
        {
            this.mActivityMainBinding.cardView.setVisibility(View.VISIBLE);
        }
        else
        {
            this.mActivityMainBinding.cardView.setVisibility(View.INVISIBLE);
        }//End if-else

        this.mUsbDevDiscoverHelper.discover();
        this.senxorDevices.attach(this.mUsbDevDiscoverHelper.getUsbDevList());

    }//End onResume

    /**
     * onStop
     * Android activity cycle
     * @param       None
     * @exception   None
     * @return      None
     */
    @Override
    protected void onStop()
    {
        super.onStop();
        unregisterReceiver(mainBroadcastReceiver);
    }//End onStop

    /**
     * compareFw
     * Compare read firmware with preloaded firmware. Change UI accordingly.
     * @param       None
     * @exception   None
     * @return      None
     */
    private void compareFw()
    {
        int updateDet = 0;           //-1 - invalid, 0 - Not necessary, 1 - Newer version is found , 2 - Downgradable

        //Do not compare when firmware version is not loaded
        if(mRdFwVer[0][0] == 0 && mRdFwVer[0][1] == 0 && mRdFwVer[0][2] == 0)
        {
            updateDet = -1;
            return;
        }

        if(this.mRdFwVer[0][0] < this.mSelPldFwVer[0][0])
        {
            updateDet = 1;
            //If the read firmware version and lower than select firmware
            //Minor and build version is not care
        }
        else if (this.mRdFwVer[0][0] > this.mSelPldFwVer[0][0])
        {
            updateDet = 2;
        }
        else
        {
            /*
                If the Major version of read firmware and selected firmware are the same
                Check build version
             */
            if(this.mRdFwVer[0][1] < this.mSelPldFwVer[0][1])
            {
                updateDet = 1;
            }
            else if(this.mRdFwVer[0][1] > this.mSelPldFwVer[0][1])
            {
                updateDet = 2;
            }
            else
            {
                /*
                    If the minor version of read firmware and selected firmware are the same
                    Check build version
                 */
                if(this.mRdFwVer[0][2] < this.mSelPldFwVer[0][2])
                {
                    updateDet = 1;
                }
                else if (this.mRdFwVer[0][2] > this.mSelPldFwVer[0][2])
                {
                    updateDet = 2;
                }//End if-else
            }//End if-else
        }//End if-else
        mActivityMainBinding.tvFwHint.setVisibility(View.VISIBLE);
        switch (updateDet)
        {
            case 0:
                mActivityMainBinding.tvFwHint.setText(getString(R.string.MSG_INFO_FW_UPDATE_SAME_VER));
                mActivityMainBinding.tvFwHint.setCompoundDrawablesWithIntrinsicBounds(getDrawable(R.drawable.icon_texthint_info_16),null,null,null);
            break;
            case 1:
                String str_sameVer = String.format(getString(R.string.MSG_INFO_FW_UPDATE_HIGH_VER),this.mSelPldFwVer[0][0],this.mSelPldFwVer[0][1],this.mSelPldFwVer[0][2]);
                mActivityMainBinding.tvFwHint.setText(str_sameVer);
                mActivityMainBinding.tvFwHint.setCompoundDrawablesWithIntrinsicBounds(getDrawable(R.drawable.icon_texthint_info_16),null,null,null);
            break;
            case 2:
                String str_lowVer = String.format(getString(R.string.MSG_INFO_FW_UPDATE_LOW_VER),this.mSelPldFwVer[0][0],this.mSelPldFwVer[0][1],this.mSelPldFwVer[0][2]);
                mActivityMainBinding.tvFwHint.setText(str_lowVer);
                mActivityMainBinding.tvFwHint.setCompoundDrawablesWithIntrinsicBounds(getDrawable(R.drawable.icon_texthint_warn_16),null,null,null);
            break;
            default:
                mActivityMainBinding.tvFwHint.setVisibility(View.INVISIBLE);
            break;
        }//End switch

    }

    /**
     * setUIMode
     * Change UI according to the app's status
     * @param       uiMode - App's status
     * @exception   None
     * @return      None
     */
    private void setUIMode(fwUpgradeUIMode uiMode)
    {
        this.mActivityMainBinding.iconStatus.setImageDrawable(getDrawable(R.drawable.round_fw_update));
        this.mActivityMainBinding.PBUPDATE.setProgress(0);
        this.mActivityMainBinding.PBUPDATE.setMax(100);
        this.mActivityMainBinding.PBUPDATE.setIndeterminate(false);
        switch (uiMode)
        {
            case DEV_NORMAL:
                this.mActivityMainBinding.btnDfuDetach.setEnabled(true);
                this.mActivityMainBinding.menuVersionSelector.setEnabled(true);
                this.mActivityMainBinding.btnUpdate.setEnabled(false);
                this.mActivityMainBinding.tvStatus.setText(getString(R.string.MSG_STA_RDY));
                this.mActivityMainBinding.PBUPDATE.setVisibility(View.VISIBLE);
                this.mActivityMainBinding.PBUPDATE.setProgress(0);
            break;
            case DEV_DFU:
                this.mActivityMainBinding.btnDfuDetach.setEnabled(false);
                this.mActivityMainBinding.menuVersionSelector.setEnabled(true);
                this.mActivityMainBinding.btnUpdate.setEnabled(true);
                this.mActivityMainBinding.tvStatus.setText(getString(R.string.MSG_STA_DFU));
                this.mActivityMainBinding.PBUPDATE.setVisibility(View.VISIBLE);
                this.mActivityMainBinding.tvFwHint.setVisibility(View.VISIBLE);
                this.mActivityMainBinding.tvFwHint.setText(getString(R.string.MSG_INFO_DFU_MODE));
                this.mActivityMainBinding.tvFwHint.setCompoundDrawablesWithIntrinsicBounds(getDrawable(R.drawable.icon_texthint_warn_16),null,null,null);
            break;
            default:
                this.mActivityMainBinding.btnDfuDetach.setEnabled(false);
                this.mActivityMainBinding.menuVersionSelector.setEnabled(false);
                this.mActivityMainBinding.btnUpdate.setEnabled(false);
                this.mActivityMainBinding.tvStatus.setText(getString(R.string.MSG_STA_NO_DEV));
                this.mActivityMainBinding.iconStatus.setImageDrawable(getDrawable(R.drawable.icon_dev_connect));
                this.mActivityMainBinding.PBUPDATE.setVisibility(View.INVISIBLE);
                this.mActivityMainBinding.tvFwVer.setVisibility(View.GONE);
                this.mActivityMainBinding.tvFwHint.setVisibility(View.INVISIBLE);
            break;
        }//End Switch
    }//End setUIMode

    /**
     * mTextAppendHandler
     * App looper
     * @param       None
     * @exception   None
     * @return      None
     */
    private Handler mDfuHandler = new Handler(Looper.getMainLooper())
    {
        @Override
        public void handleMessage(Message msg)
        {
            switch (msg.what)

            {
                case 0:
                    String textToAppend = (String)msg.obj;
                    mActivityMainBinding.tvMain.append(textToAppend);
                break;
                case 1:
                    int progress = Math.round((Float) msg.obj*100);
                    mActivityMainBinding.PBUPDATE.setMax(100);
                    if((Float) msg.obj < 0)
                    {
                        mActivityMainBinding.PBUPDATE.setIndeterminate(true);
                        mActivityMainBinding.tvStatus.setText(getString(R.string.MSG_STA_UPDATE_DONE));
                        mActivityMainBinding.iconStatus.setImageDrawable(getDrawable(R.drawable.round_fw_update_ok));
                    }
                    else
                    {
                        mActivityMainBinding.PBUPDATE.setIndeterminate(false);
                        mActivityMainBinding.tvStatus.setText(getString(R.string.MSG_STA_UPDATING)+ " "+ progress + "%");
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
                        {
                            mActivityMainBinding.PBUPDATE.setProgress(progress,true);
                        }
                        else
                        {
                            mActivityMainBinding.PBUPDATE.setProgress(progress);
                        }//End if-else
                    }//End if-else

                break;
                default:
                break;
            }//End Switch
        }
    };//End mTextAppendHandler

    /**
     * MainButtonHandler
     * Button event handler
     */
    private class MainButtonHandler implements View.OnClickListener
    {
        @Override
        public void onClick(View view)
        {
            final int viewId = view.getId();
            if(viewId == R.id.btnClear)
            {
                mActivityMainBinding.tvMain.setText("");
            }
            else if (viewId == R.id.btnDfuDetach)
            {
                if (mDfuRunTimeDevice != null)
                {
                    DfuRuntimeDetachRunnable runDetach = new DfuRuntimeDetachRunnable(getApplicationContext(), mDfuRunTimeDevice);
                    // TODO: thread management (ExecutorService?)
                    new Thread(runDetach).start();
                    mActivityMainBinding.PBUPDATE.setIndeterminate(true);
                }
                else
                {
                    Toast.makeText(fwUpgradeActivity.this, getString(R.string.ERR_DFU_NO_DEV), Toast.LENGTH_SHORT).show();
                }//End if-else
            }
            else if (viewId == R.id.btnUpdate)
            {
                mActivityMainBinding.iconStatus.setImageDrawable(getDrawable(R.drawable.round_fw_updating));
                mActivityMainBinding.btnUpdate.setEnabled(false);
                mActivityMainBinding.menuVersionSelector.setEnabled(false);
                switch (mActivityMainBinding.menuVersionSelector.getSelectedItemPosition())
                {
                    case 0:
                        senxorDevices.flashFirmware(R.raw.fw4426);
                    break;
                    default:
                    break;
                }//End Switch
            }//End if-else
        }//End onClick
    };//End MainButtonHandler

    /**
     * FwSelectorChangeHandler
     * Event handler when the selected item in spinner is changed
     */
    private class FwSelectorChangeHandler implements AdapterView.OnItemSelectedListener
    {

        @Override
        public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l)
        {
            final int id = adapterView.getId();
            final int length = getResources().getStringArray(R.array.FW_VER).length;
            if(id == R.id.menu_version_selector)
            {

                String str = getResources().getStringArray(R.array.FW_VER)[i];
                String[] selVer = str.replaceAll("[.]", " ").split(" ");
                try
                {
                    mSelPldFwVer[0][0] = Integer.parseInt(selVer[0]);
                    mSelPldFwVer[0][1] = Integer.parseInt(selVer[1]);
                    mSelPldFwVer[0][2] = Integer.parseInt(selVer[2]);
                    if(mDfuModeDevice == null)
                    {
                        compareFw();
                    }

                }
                catch (NumberFormatException e)
                {
                    e.printStackTrace();
                }

            }
        }//End FwSelectorChangeHandler

        @Override
        public void onNothingSelected(AdapterView<?> adapterView)
        {

        }
    }

    /**
     * MainBoardcastReceiver
     * Broadcast receiver for main activity
     */
    private class MainBroadcastReceiver extends BroadcastReceiver
    {
        /*
            Callback function

         */
        @Override
        public void onReceive(Context context, Intent intent)
        {
            final String action = intent.getAction();

            if (action.compareTo("com.example.myapplication.findDfu") == 0)
            {
                mDfuModeDevice = senxorDevices.getSenxorDFUDev();
                mDfuRunTimeDevice = null;
                setUIMode(fwUpgradeUIMode.DEV_DFU);
                Log.d("[BROADCAST]","DFU device found.");
            }
            else if (action.compareTo("com.example.myapplication.findDev") == 0)
            {
                mDfuRunTimeDevice = senxorDevices.getSenxorDev();
                mDfuModeDevice = null;
                setUIMode(fwUpgradeUIMode.DEV_NORMAL);
                senxorDevices.readDevFwVer();
            }
            else if (action.compareTo("com.example.myapplication.fwReadDone") == 0)
            {
                final int size = intent.getIntExtra("fwSize",0);
                final String name = intent.getStringExtra("fwName");
                mActivityMainBinding.tvMain.append("Read " + size + " byte(s) from " + name + NEWLINE);
                mActivityMainBinding.tvMain.append("Begin DFU programming..." + NEWLINE);
                mActivityMainBinding.PBUPDATE.setMax(100);
            }
            else if (action.compareTo("com.example.myapplication.fwVerRdDone") == 0 )
            {

                mRdFwVer = senxorDevices.getFwVersion();
                senxorDevices.disconenctSerial();
                final String fwVerStr = String.format(getString(R.string.MSG_FW_VER),mRdFwVer[0][0], mRdFwVer[0][1],mRdFwVer[0][2]);
                mActivityMainBinding.tvFwVer.setText(fwVerStr);
                mActivityMainBinding.tvFwVer.setVisibility(View.VISIBLE);
                compareFw();
            }
            else if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action))
            {
                Toast.makeText(getApplicationContext(),getString(R.string.MSG_INFO_DEV_FOUND),Toast.LENGTH_SHORT).show();
            }
            else if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action))
            {
                if(mDfuModeDevice == null)
                {
                    setUIMode(fwUpgradeUIMode.DEV_NOT_FOUND);
                }//End if

                senxorDevices.release();
            }//End if-else

        }//End onReceive
    };//End MainBroadcastReceiver
}
