package app.andrey_voroshkov.chorus_laptimer;

import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbManager;
import android.net.ConnectivityManager;
import android.net.DhcpInfo;
import android.net.Network;
import android.net.NetworkInfo;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import com.google.android.material.tabs.TabLayout;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.viewpager.widget.ViewPager;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;

import com.hoho.android.usbserial.driver.UsbSerialDriver;
import com.hoho.android.usbserial.driver.UsbSerialProber;

import java.io.File;
import java.util.Calendar;
import java.util.List;
import java.util.concurrent.TimeUnit;

import app.akexorcist.bluetotohspp.library.BluetoothState;
import app.akexorcist.bluetotohspp.library.DeviceList;

public class MainActivity extends AppCompatActivity implements ConnectionListener {

    private static final int REQUEST_WRITE_STORAGE_CODE = 315;
    /**
     * The {@link ViewPager} that will host the section contents.
     */
    private ViewPager mViewPager;
    private Menu menu;
    private BroadcastReceiver mUsbReceiver;
    private PendingIntent mPermissionIntent;
    BTService bt;
    UDPService udp;
    USBService usb;

    public void onDisconnected() {
        Toast.makeText(getApplicationContext(), getString(R.string.disconnected), Toast.LENGTH_SHORT).show();
        AppState.getInstance().speakMessage(R.string.disconnected);
        AppState.getInstance().onDisconnected();
    }

    public void onConnectionFailed(String errorMsg) {
        AppState.getInstance().conn = null;
        Toast.makeText(getApplicationContext(), getString(R.string.connection_failed), Toast.LENGTH_SHORT).show();
        AppState.getInstance().speakMessage(R.string.connection_failed);
    }


    public void onConnected(String name) {
        String txt = getString(R.string.connected_to, name);
        Toast.makeText(getApplicationContext(), txt, Toast.LENGTH_SHORT).show();
        AppState.getInstance().speakMessage(R.string.connected);
        AppState.getInstance().onConnected();
    }

    public void onDataReceived(String message) {
        String parsedMsg;
        try {
            parsedMsg = Utils.btDataChunkParser(message);
        } catch (Exception e) {
            parsedMsg = e.toString();
        }
        // Toast.makeText(getApplicationContext(), parsedMsg, Toast.LENGTH_SHORT).show();
    }

    void initUSB() {
        usb = new USBService((UsbManager) getSystemService(Context.USB_SERVICE));
        usb.setConnectionListener(this);
    }


    void initUDP() {
        udp = new UDPService();
        udp.setConnectionListener(this);
    }

    void initBluetooth() {
        bt = new BTService(this, AppState.DELIMITER);

        if (!bt.isBluetoothAvailable()) {
            Toast.makeText(getApplicationContext()
                    , "Bluetooth is not available"
                    , Toast.LENGTH_SHORT).show();
            finish();
        }

        bt.setConnectionListener(this);
    }

    void retrieveAndStoreAppVersion (Context context) {
        String version = "";
        try {
            PackageInfo pInfo = context.getPackageManager().getPackageInfo(getPackageName(), 0);
            version = pInfo.versionName;
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        AppState.getInstance().appVersion = version;
    }

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

        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        // Create the adapter that will return a fragment for each of the three
        // primary sections of the activity.
        /*
          The {@link android.support.v4.view.PagerAdapter} that will provide
        fragments for each of the sections. We use a
        {@link FragmentPagerAdapter} derivative, which will keep every
        loaded fragment in memory. If this becomes too memory intensive, it
        may be best to switch to a
        {@link android.support.v4.app.FragmentStatePagerAdapter}.
         */
        SectionsPagerAdapter mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager(), getResources());

        // Set up the ViewPager with the sections adapter.
        mViewPager = (ViewPager) findViewById(R.id.container);
        if (mViewPager == null) return;

        mViewPager.setOffscreenPageLimit(4);
        mViewPager.setAdapter(mSectionsPagerAdapter);

        TabLayout tabLayout = (TabLayout) findViewById(R.id.tabs);
        tabLayout.setupWithViewPager(mViewPager);

        initBluetooth();
        initUDP();
        initBroadcastReceiverForUsbPermissions();
        initUSB();
        retrieveAndStoreAppVersion(getApplicationContext());
        AppState.getInstance().textSpeaker = new TextSpeaker(getApplicationContext(),
                AppState.getInstance().shouldSpeakEnglishOnly);
        AppState.getInstance().preferences = getPreferences(MODE_PRIVATE);
        AppPreferences.applyAll();

        AppState.getInstance().addListener(new IDataListener() {
            @Override
            public void onDataChange(DataAction dataItemName) {
                switch (dataItemName) {
                    case WrongApiVersion:
                        MainActivity.this.showWrongApiDialog();
                }
            }
        });
        //Ensure permissions permissions before any disk IO
        ensurePermissions();
        //this will cleanup csv reports after 2 weeks (14 days)
        cleanUpCSVReports();
    }

    public void showWrongApiDialog() {
        String modulesWithWrongApi = AppState.getInstance().getModulesWithWrongApiVersion();
        new AlertDialog.Builder(MainActivity.this)
                .setTitle(getResources().getString(R.string.api_err_title))
                .setMessage(getResources().getString(R.string.api_err_message, modulesWithWrongApi, AppState.SUPPORTED_API_VERSION))
                .setCancelable(false)
                .setPositiveButton(getResources().getString(R.string.api_err_button), new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        if (AppState.getInstance().conn != null) {
                            AppState.getInstance().conn.disconnect();
                        }
                    }
                }).show();
    }

    public void onDestroy() {
        super.onDestroy();
        bt.stopService();
        AppState.getInstance().textSpeaker.shutdown();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        this.menu = menu;
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        Connection conn = AppState.getInstance().conn;
        if (conn != null) {
            for(int i = 0; i < menu.size(); i++) {
                menu.getItem(i).setVisible(false);
            }
            menu.findItem(R.id.menuDisconnect).setVisible(true);
        } else {
            UsbDevice device = getAvailableUsbDevice();
            menu.findItem(R.id.menuUSBConnect).setVisible(device != null);
            menu.findItem(R.id.menuBTConnect).setVisible(true);
            menu.findItem(R.id.menuUDPConnect).setVisible(true);
            menu.findItem(R.id.menuDisconnect).setVisible(false);
        }
        return super.onPrepareOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        Handler delayedDisconnect = new Handler() {
            public void handleMessage(Message msg) {
                if (AppState.getInstance().conn != null) {
                    AppState.getInstance().conn.disconnect();
                }
            }
        };

        switch (id) {
            case R.id.menuBTConnect:
                bt.setDeviceTarget(BluetoothState.DEVICE_OTHER);
                Intent intent = new Intent(getApplicationContext(), DeviceList.class);
                startActivityForResult(intent, BluetoothState.REQUEST_CONNECT_DEVICE);
                break;
            case R.id.menuUDPConnect:
                preferWifiAndConnectUDP();
                break;
            case R.id.menuUSBConnect:
                checkUSBPermissionsAndConnectIfAllowed();
                break;
            case R.id.menuDisconnect:
                AppState.getInstance().onBeforeDisconnect();
                delayedDisconnect.sendEmptyMessageDelayed(0, 100);
                break;
        }

        return super.onOptionsItemSelected(item);
    }

    private void preferWifiAndConnectUDP() {
        ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            for (Network net : cm.getAllNetworks()) {
                NetworkInfo networkInfo = cm.getNetworkInfo(net);
                if (networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
                    cm.bindProcessToNetwork(net);
                    break;
                }
            }
        }

        udp.connect(getGatewayIP(), 0);
        useUDP();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            cm.bindProcessToNetwork(null);
        }
    }

    private boolean checkIsWifiOnAndConnected() {
        WifiManager wifiMgr = (WifiManager) getApplicationContext().getSystemService(WIFI_SERVICE);
        if (!wifiMgr.isWifiEnabled()) return false;  // Wi-Fi adapter is OFF

        ConnectivityManager cm =
            (ConnectivityManager)getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
        // Deprecated in Android 10, but should still be fine
        NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
        boolean isConnected = activeNetwork != null && activeNetwork.isConnectedOrConnecting();
        return isConnected;
    }

    private String getGatewayIP() {
        if (!checkIsWifiOnAndConnected()) return "0.0.0.0";

        WifiManager wifi = (WifiManager) getApplicationContext().getSystemService(WIFI_SERVICE);
        DhcpInfo dhcp = wifi.getDhcpInfo();
        int ip = dhcp.gateway;
        return String.format("%d.%d.%d.%d",
                (ip & 0xff),
                (ip >> 8 & 0xff),
                (ip >> 16 & 0xff),
                (ip >> 24 & 0xff)
        );
    }

    public void onStart() {
        super.onStart();
        if (!bt.isBluetoothEnabled()) {
//            Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
//            startActivityForResult(intent, BluetoothState.REQUEST_ENABLE_BT);
        } else {
            if (!bt.isServiceAvailable()) {
                bt.runService();
            }
        }
    }

    /**
     * This function will cleanUp CSV Reports if it has been 2 weeks since file is last updated.
     */
    public void cleanUpCSVReports() {
        //use ChorusLapTimer directory
        String path = Utils.getReportPath();
        File file = new File(path);

        //get date today
        //TODO: check if it really works with Calendar, or use Date?
        Calendar calToday = Calendar.getInstance();
        long todayMillis = calToday.getTimeInMillis();

        //iterate from files inside the ChorusLapTimer directory
        if (file.list() != null) {
            for (int i = 0; i < file.list().length; i++) {
                File currFile = file.listFiles()[i];
                //check difference of file.lastModified compared to date today
                long diff = todayMillis - currFile.lastModified();
                //convert difference to number of days
                long numDays = TimeUnit.MILLISECONDS.toDays(diff);
                //if number of days are 14(2 weeks), delete the file
                if (numDays > 14) {
                    try {
                        currFile.delete();
                    } catch (Exception e) {
                        continue;
                    }
                }
            }
        }
    }

    public void useBT() {
        AppState.getInstance().conn = bt;
    }

    public void useUDP() {
        AppState.getInstance().conn = udp;
    }

    public void useUSB() {
        AppState.getInstance().conn = usb;
    }

    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == BluetoothState.REQUEST_CONNECT_DEVICE) {
            if (resultCode == Activity.RESULT_OK) {
                bt.connect(data);
                useBT();
            }
        } else if (requestCode == BluetoothState.REQUEST_ENABLE_BT) {
            if (resultCode == Activity.RESULT_OK) {
                bt.runService();
            } else {
                Toast.makeText(getApplicationContext()
                        , "Bluetooth was not enabled."
                        , Toast.LENGTH_SHORT).show();
                finish();
            }
        }
    }

    private void ensurePermissions() {
        if (ContextCompat.checkSelfPermission(this,
                Manifest.permission.WRITE_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this,
                    new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
                    REQUEST_WRITE_STORAGE_CODE);
        }
    }

    private void connectToUsbDevice() {
        usb.connect();
        useUSB();
    }

    private static final String ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION";

    private void initBroadcastReceiverForUsbPermissions() {
        mPermissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0);

        mUsbReceiver = new BroadcastReceiver() {
            public void onReceive(Context context, Intent intent) {
                String action = intent.getAction();
                if (ACTION_USB_PERMISSION.equals(action)) {
                    synchronized (this) {
                        UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);

                        if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
                            if (device != null) {
                                //call method to set up device communication
                                connectToUsbDevice();
                            }
                        } else {
    //                        Log.d(TAG, "permission denied for device " + device);
                            Toast.makeText(getApplicationContext(), getString(R.string.cannotAccessUsbDevice), Toast.LENGTH_SHORT).show();
                        }
                    }
                }
            }
        };
    }


    private void checkUSBPermissionsAndConnectIfAllowed() {
        UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
        UsbDevice device = getAvailableUsbDevice();
        if (device == null) return;

        if (manager.hasPermission(device)) {
            connectToUsbDevice();
            return;
        }

        IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
        registerReceiver(mUsbReceiver, filter);
        manager.requestPermission(device, mPermissionIntent);
    }

    private UsbDevice getAvailableUsbDevice() {
        UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);

        // Find all available drivers from attached devices.
        List<UsbSerialDriver> availableDrivers = UsbSerialProber.getDefaultProber().findAllDrivers(manager);
        if (availableDrivers.isEmpty()) {
            return null;
        }

        // Open a connection to the first available driver.
        UsbSerialDriver driver = availableDrivers.get(0);
        return driver.getDevice();
    }
}
