package com.adafruit.bluefruit.le.connect.app.imagetransfer;

import android.Manifest;
import android.app.Activity;
import android.bluetooth.BluetoothGatt;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.drawable.BitmapDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.provider.MediaStore;
import android.text.InputType;
import android.util.Log;
import android.util.Size;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.Button;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.core.app.ActivityCompat;
import androidx.core.content.FileProvider;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;

import com.adafruit.bluefruit.le.connect.R;
import com.adafruit.bluefruit.le.connect.app.CommonHelpFragment;
import com.adafruit.bluefruit.le.connect.app.ConnectedPeripheralFragment;
import com.adafruit.bluefruit.le.connect.ble.central.BlePeripheralUart;
import com.adafruit.bluefruit.le.connect.ble.central.UartPacketManager;
import com.adafruit.bluefruit.le.connect.dfu.ProgressFragmentDialog;
import com.adafruit.bluefruit.le.connect.utils.DialogUtils;
import com.adafruit.bluefruit.le.connect.utils.ImageMagickUtils;
import com.adafruit.bluefruit.le.connect.utils.ImageUtils;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.nio.ByteBuffer;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;


public class ImageTransferFragment extends ConnectedPeripheralFragment implements ImageCropFragment.OnImageCropListener, ImageTransferFormatSelectorDialogFragment.FormatSelectorListener {
    // Log
    private final static String TAG = ImageTransferFragment.class.getSimpleName();

    // Config
    private static final boolean kShowInterleaveControls = true;
    private static final int kDefaultInterlavedWithoutResponseCount = 50;
    private static final String kAuthorityField = ".fileprovider";          // Same as the authority field on the manifest provider

    // Constants
    private static final int kActivityRequestCode_pickFromGallery = 1;
    private static final int kActivityRequestCode_takePicture = 2;
    private static final int kActivityRequestCode_cropPicture = 3;
    private static final int kActivityRequestCode_requestCameraPermission = 4;
    private static final int kActivityRequestCode_requestReadExternalStoragePermission = 5;

    private final static String kPreferences = "ImageTransferFragment_prefs";
    private final static String kPreferences_resolutionWidth = "resolution_width";
    private final static String kPreferences_resolutionHeight = "resolution_height";
    private final static String kPreferences_interleavedWithoutResponseCount = "interleaved_withoutresponse_count";
    private final static String kPreferences_isColorSpace24Bits = "is_color_space_24_bits";
    private final static String kPreferences_isEInkModeEnabled = "is_eink_mode_enabled";

    private Size kDefaultResolution = new Size(64, 64);

    // UI
    private TextView mUartWaitingTextView;
    private ImageView mCameraImageView;
    private Button mResolutionButton;
    private ViewGroup mResolutionViewGroup;
    private ViewGroup mResolutionContainerViewGroup;
    private Button mTransferModeButton;
    private Button mColorSpaceButton;

    // Data
    private UartPacketManager mUartManager;
    private final Handler mMainHandler = new Handler(Looper.getMainLooper());
    private BlePeripheralUart mBlePeripheralUart;
    private Size mResolution;
    private float mImageRotationDegrees;
    private int mInterleavedWithoutResponseCount;
    private boolean mIsColorSpace24Bits;
    private boolean mIsEInkModeEnabled;
    private Bitmap mBitmap;
    private ProgressFragmentDialog mProgressDialog;

    // Data - photo
    private String mTemporalPhotoPath;

    public static ImageTransferFragment newInstance(@Nullable String singlePeripheralIdentifier) {
        ImageTransferFragment fragment = new ImageTransferFragment();
        fragment.setArguments(createFragmentArgs(singlePeripheralIdentifier));
        return fragment;
    }

    public ImageTransferFragment() {
        // Required empty public constructor
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_imagetransfer, container, false);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        Context context = getContext();
        if (context == null) return;

        // Update ActionBar
        setActionBarTitle(R.string.imagetransfer_tab_title);


        // set image cache temp directory in ImageMagick:
        FragmentActivity activity = getActivity();
        if (activity != null) {
            ImageMagickUtils.setCacheDir(activity);
        }

        // Init Data
        SharedPreferences preferences = context.getSharedPreferences(kPreferences, Context.MODE_PRIVATE);
        final int resolutionWidth = preferences.getInt(kPreferences_resolutionWidth, kDefaultResolution.getWidth());
        final int resolutionHeight = preferences.getInt(kPreferences_resolutionHeight, kDefaultResolution.getHeight());
        mResolution = new Size(resolutionWidth, resolutionHeight);
        mInterleavedWithoutResponseCount = preferences.getInt(kPreferences_interleavedWithoutResponseCount, kDefaultInterlavedWithoutResponseCount);
        mIsColorSpace24Bits = preferences.getBoolean(kPreferences_isColorSpace24Bits, false);
        mIsEInkModeEnabled = preferences.getBoolean(kPreferences_isEInkModeEnabled, false);

        // UI
        mUartWaitingTextView = view.findViewById(R.id.uartWaitingTextView);
        mUartWaitingTextView.setVisibility(mBlePeripheralUart != null && mBlePeripheralUart.isUartEnabled() ? View.GONE : View.VISIBLE);

        mCameraImageView = view.findViewById(R.id.cameraImageView);
        mCameraImageView.setImageBitmap(null);      // Clear any image
        mResolutionViewGroup = view.findViewById(R.id.resolutionViewGroup);
        mResolutionContainerViewGroup = view.findViewById(R.id.resolutionContainerViewGroup);

        mResolutionButton = view.findViewById(R.id.resolutionButton);
        mResolutionButton.setOnClickListener(v -> chooseResolution());
        Button imageButton = view.findViewById(R.id.imageButton);
        imageButton.setOnClickListener(v -> chooseImage());

        mTransferModeButton = view.findViewById(R.id.transferModeButton);
        mTransferModeButton.setOnClickListener(v -> {
            SharedPreferences settings = context.getSharedPreferences(kPreferences, Context.MODE_PRIVATE);
            SharedPreferences.Editor editor = settings.edit();

            if (kShowInterleaveControls) {
                AlertDialog.Builder builder = new AlertDialog.Builder(context);
                builder.setTitle(R.string.imagetransfer_transfermode_title);

                String[] items = {getString(R.string.imagetransfer_transfermode_value_withoutresponse), getString(R.string.imagetransfer_transfermode_value_withresponse), getString(R.string.imagetransfer_transfermode_value_interleaved)};

                builder.setItems(items, (dialog, which) -> {
                    switch (which) {
                        case 0:
                            mInterleavedWithoutResponseCount = Integer.MAX_VALUE;
                            updateTransferModeUI();

                            // Save selected mode
                            editor.putInt(kPreferences_interleavedWithoutResponseCount, mInterleavedWithoutResponseCount);
                            editor.apply();
                            break;

                        case 1:
                            mInterleavedWithoutResponseCount = 0;
                            updateTransferModeUI();

                            // Save selected mode
                            editor.putInt(kPreferences_interleavedWithoutResponseCount, mInterleavedWithoutResponseCount);
                            editor.apply();
                            break;

                        case 2: {

                            AlertDialog.Builder alert = new AlertDialog.Builder(context);
                            alert.setTitle(R.string.imagetransfer_transfermode_interleavedcount_title);
                            alert.setMessage(R.string.imagetransfer_transfermode_interleavedcount_message);
                            final EditText input = new EditText(context);
                            input.setHint(R.string.imagetransfer_transfermode_interleavedcount_hint);
                            input.setInputType(InputType.TYPE_CLASS_NUMBER);
                            input.setRawInputType(Configuration.KEYBOARD_12KEY);

                            // Add horizontal margin (https://stackoverflow.com/questions/27774414/add-bigger-margin-to-edittext-in-android-alertdialog)
                            FrameLayout container = new FrameLayout(context);
                            FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
                            params.leftMargin = getResources().getDimensionPixelSize(R.dimen.alertview_embedded_edittext_horizontalmargin);
                            params.rightMargin = params.leftMargin;
                            input.setLayoutParams(params);
                            container.addView(input);

                            alert.setView(container);
                            alert.setPositiveButton(R.string.dialog_ok, (dialog2, whichButton) -> {
                                String valueString = String.valueOf(input.getText());
                                int value = 0;
                                try {
                                    value = Integer.parseInt(valueString);
                                } catch (Exception e) {
                                    Log.d(TAG, "Cannot parse value");
                                }


                                // Set selected value
                                mInterleavedWithoutResponseCount = value;
                                updateTransferModeUI();
                                editor.putInt(kPreferences_interleavedWithoutResponseCount, mInterleavedWithoutResponseCount);
                                editor.apply();

                            });
                            alert.setNegativeButton(android.R.string.cancel, (dialog2, whichButton) -> {
                            });
                            alert.show();

                            break;
                        }
                    }
                    dialog.dismiss();
                });

                builder.setNegativeButton(R.string.dialog_cancel, null);

                AlertDialog dialog = builder.create();
                dialog.show();

            } else {
                mInterleavedWithoutResponseCount = mInterleavedWithoutResponseCount == 0 ? Integer.MAX_VALUE : 0;
                updateTransferModeUI();

                // Save selected mode
                editor.putInt(kPreferences_interleavedWithoutResponseCount, mInterleavedWithoutResponseCount);
                editor.apply();
            }


        });

        mColorSpaceButton = view.findViewById(R.id.colorSpaceButton);
        updateColorSpaceUI();
        mColorSpaceButton.setOnClickListener(v -> {
            mIsColorSpace24Bits = !mIsColorSpace24Bits;
            updateColorSpaceUI();

            // Save to preferences
            SharedPreferences settings = context.getSharedPreferences(kPreferences, Context.MODE_PRIVATE);
            SharedPreferences.Editor editor = settings.edit();
            editor.putBoolean(kPreferences_isColorSpace24Bits, mIsColorSpace24Bits);
            editor.apply();
        });

        ImageButton rotateLeftButton = view.findViewById(R.id.rotateLeftButton);
        rotateLeftButton.setOnClickListener(v -> {
            final float rotation = (mImageRotationDegrees - 90) % 360;
            updateImage(mResolution, mIsEInkModeEnabled, rotation);
        });

        ImageButton rotateRightButton = view.findViewById(R.id.rotateRightButton);
        rotateRightButton.setOnClickListener(v -> {
            final float rotation = (mImageRotationDegrees + 90) % 360;
            updateImage(mResolution, mIsEInkModeEnabled, rotation);
        });

        Button sendButton = view.findViewById(R.id.sendButton);
        sendButton.setOnClickListener(view1 -> sendImage(mInterleavedWithoutResponseCount, mIsColorSpace24Bits));

        mResolutionContainerViewGroup.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                mResolutionContainerViewGroup.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                updateImage(mResolution, mIsEInkModeEnabled, mImageRotationDegrees);
            }
        });

        updateTransferModeUI();

        // Setup
        if (mUartManager == null) {      // Don't setup if already init (because fragment was recreated)
            // UartManager
            mUartManager = new UartPacketManager(context, null, false, null);
            start();
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();

        stop();
    }

    @Override
    public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
        super.onCreateOptionsMenu(menu, inflater);
        inflater.inflate(R.menu.menu_help, menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        FragmentActivity activity = getActivity();

        //noinspection SwitchStatementWithTooFewBranches
        switch (item.getItemId()) {
            case R.id.action_help:
                if (activity != null) {
                    FragmentManager fragmentManager = activity.getSupportFragmentManager();
                    CommonHelpFragment helpFragment = CommonHelpFragment.newInstance(getString(R.string.imagetransfer_help_title), getString(R.string.imagetransfer_help_text));
                    FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction()
                            .setCustomAnimations(R.anim.slide_in_left, R.anim.slide_out_right, R.anim.slide_in_right, R.anim.slide_out_left)
                            .replace(R.id.contentLayout, helpFragment, "Help");
                    fragmentTransaction.addToBackStack(null);
                    fragmentTransaction.commit();
                }
                return true;

            default:
                return super.onOptionsItemSelected(item);
        }
    }


    // region Uart
    private void start() {
        Log.d(TAG, "ImageTransfer start");

        // Enable Uart
        mBlePeripheralUart = new BlePeripheralUart(mBlePeripheral);
        mBlePeripheralUart.uartEnable(mUartManager, status -> mMainHandler.post(() -> {
            mUartWaitingTextView.setVisibility(status == BluetoothGatt.GATT_SUCCESS ? View.GONE : View.VISIBLE);

            if (status == BluetoothGatt.GATT_SUCCESS) {
                // Done
                Log.d(TAG, "Uart enabled");

                // Set the default image
                Context context = getContext();
                if (context != null) {
                    Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.imagetransfer_default);
                    setImage(bitmap);
                }

            } else {
                Log.d(TAG, "Uart error");
                Context context = getContext();
                if (context != null) {
                    WeakReference<BlePeripheralUart> weakBlePeripheralUart = new WeakReference<>(mBlePeripheralUart);
                    AlertDialog.Builder builder = new AlertDialog.Builder(context);
                    AlertDialog dialog = builder.setMessage(R.string.uart_error_peripheralinit)
                            .setPositiveButton(android.R.string.ok, (dialogInterface, which) -> {
                                BlePeripheralUart strongBlePeripheralUart = weakBlePeripheralUart.get();
                                if (strongBlePeripheralUart != null) {
                                    strongBlePeripheralUart.disconnect();
                                }
                            })
                            .show();
                    DialogUtils.keepDialogOnOrientationChanges(dialog);
                }
            }
        }));
    }

    private void stop() {
        Log.d(TAG, "ImageTransfer stop");
        dismissProgressDialog();
        mBlePeripheral.reset();
        mBlePeripheralUart = null;
    }

    // endregion

    // region UI
    private void updateTransferModeUI() {
        String text;

        if (mInterleavedWithoutResponseCount == 0) {
            text = getString(R.string.imagetransfer_transfermode_value_withresponse);
        } else if (mInterleavedWithoutResponseCount == Integer.MAX_VALUE) {
            text = getString(R.string.imagetransfer_transfermode_value_withoutresponse);

        } else {
            text = String.format(getString(R.string.imagetransfer_transfermode_value_interleaved_format), mInterleavedWithoutResponseCount);
        }

        mTransferModeButton.setText(text);
    }

    private void updateColorSpaceUI() {
        mColorSpaceButton.setText(mIsColorSpace24Bits ? R.string.imagetransfer_colorspace_24bit : R.string.imagetransfer_colorspace_16bit);
    }

    private void updateResolutionUI() {
        final String format = mIsEInkModeEnabled ? getString(R.string.imagetransfer_resolution_einkprefix) + " " + "%d x %d" : "%d x %d";
        final String text = String.format(Locale.US, format, mResolution.getWidth(), mResolution.getHeight());
        mResolutionButton.setText(text);
    }

    private void updateImage(Size resolution, boolean isEInkModeEnabled, float rotation) {
        Context context = getContext();
        if (context == null) return;

        mResolution = resolution;
        mIsEInkModeEnabled = isEInkModeEnabled;
        mImageRotationDegrees = rotation;
        final int width = mResolution.getWidth();
        final int height = mResolution.getHeight();

        // Save selected resolution
        SharedPreferences settings = context.getSharedPreferences(kPreferences, Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = settings.edit();
        editor.putInt(kPreferences_resolutionWidth, width);
        editor.putInt(kPreferences_resolutionHeight, height);
        editor.putBoolean(kPreferences_isEInkModeEnabled, isEInkModeEnabled);
        editor.apply();

        // Change UI to adjust aspect ratio of the displayed image
        final float resolutionAspectRatio = resolution.getWidth() / (float) resolution.getHeight();

        final int maxWidth = mResolutionContainerViewGroup.getWidth();
        final int maxHeight = mResolutionContainerViewGroup.getHeight();

        RelativeLayout.LayoutParams relativeLayoutParams = (RelativeLayout.LayoutParams) mResolutionViewGroup.getLayoutParams();
        if (maxWidth > maxHeight * resolutionAspectRatio) {
            relativeLayoutParams.height = maxHeight;//(int) MetricsUtils.convertPixelsToDp(context, maxHeight);
            relativeLayoutParams.width = (int) (relativeLayoutParams.height * resolutionAspectRatio);
        } else {
            relativeLayoutParams.width = maxWidth;//(int) MetricsUtils.convertPixelsToDp(context, maxWidth);
            relativeLayoutParams.height = (int) (relativeLayoutParams.width * (1.f / resolutionAspectRatio));

        }
        mResolutionViewGroup.requestLayout();

        // Calculate transformed image
        if (mBitmap != null) {
            Bitmap transformedBitmap = ImageUtils.scaleAndRotateImage(mBitmap, mResolution, mImageRotationDegrees, Color.BLACK);

            if (isEInkModeEnabled) {
                transformedBitmap = ImageUtils.applyEInkModeToImage(context, transformedBitmap);
            }

            BitmapDrawable bitmapDrawable = new BitmapDrawable(getResources(), transformedBitmap);        // Create bitmap drawable to control filtering method
            bitmapDrawable.setFilterBitmap(false);

            mCameraImageView.setImageDrawable(bitmapDrawable);
        }

        //
        updateResolutionUI();
    }


    // endregion

    // region Actions

    private void chooseResolution() {
        Context context = getContext();
        if (context == null) return;

        FragmentManager fragmentManager = getFragmentManager();
        if (fragmentManager == null) return;

        ImageTransferFormatSelectorDialogFragment dialogFragment = ImageTransferFormatSelectorDialogFragment.newInstance(mIsEInkModeEnabled, mResolution);
        dialogFragment.setTargetFragment(this, 0);
        dialogFragment.show(fragmentManager, ImageTransferFormatSelectorDialogFragment.class.getSimpleName());
    }

    private void chooseImage() {
        Context context = getContext();
        if (context == null) return;

        PackageManager packageManager = context.getPackageManager();
        final boolean hasCamera = packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY);

        boolean isCameraAvailable = false;
        if (hasCamera) {
            Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
            if (takePictureIntent.resolveActivity(packageManager) != null) {
                isCameraAvailable = true;
            }
        }

        // Show image picker choices
        String[] imageChoices;
        if (isCameraAvailable) {
            imageChoices = new String[]{getString(R.string.imagetransfer_imagepicker_camera), getString(R.string.imagetransfer_imagepicker_photolibrary)};
        } else {
            imageChoices = new String[]{getString(R.string.imagetransfer_imagepicker_photolibrary)};
        }

        AlertDialog.Builder builder = new AlertDialog.Builder(context);
        boolean finalIsCameraAvailable = isCameraAvailable;
        builder.setTitle(R.string.imagetransfer_imageorigin_choose)
                .setItems(imageChoices, (dialog, which) -> {
                    boolean isCameraSelected = which == 0 && finalIsCameraAvailable;

                    if (isCameraSelected) {     // Get image from camera
                        chooseFromCameraAskingPermissionIfNeeded(context);
                    } else {            // Get image from gallery
                        chooseFromLibraryAskingPermissionIfNeeded(context);
                    }
                });
        builder.show();
    }

    private void chooseFromCameraAskingPermissionIfNeeded(@NonNull Context context) {
        int rc = ActivityCompat.checkSelfPermission(context, Manifest.permission.CAMERA);
        if (rc == PackageManager.PERMISSION_GRANTED) {
            chooseFromCamera(context);
        } else {
            requestCameraPermission();
        }
    }

    private void chooseFromCamera(@NonNull Context context) {
        PackageManager packageManager = context.getPackageManager();

        Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        if (takePictureIntent.resolveActivity(packageManager) != null) {

            File photoFile = null;
            try {
                photoFile = createImageFile(context);
            } catch (IOException ex) {
                // Error occurred while creating the File
                Log.w(TAG, "Could not create file to save picture");
                new AlertDialog.Builder(context)
                        .setMessage(R.string.imagetransfer_cameranotavailable)
                        .setPositiveButton(android.R.string.ok, null)
                        .show();
            }
            // Continue only if the File was successfully created
            if (photoFile != null) {
                final String authority = context.getApplicationContext().getPackageName() + kAuthorityField;
                Uri photoUri = FileProvider.getUriForFile(context.getApplicationContext(), authority, photoFile);
                takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);

                Log.d(TAG, "Start takePictureIntent");
                startActivityForResult(takePictureIntent, kActivityRequestCode_takePicture);
                Log.d(TAG, "Started takePictureIntent");
            }

        } else {
            Log.w(TAG, "Image capture not available");
        }
    }

    private void chooseFromLibraryAskingPermissionIfNeeded(@NonNull Context context) {
        int rc = ActivityCompat.checkSelfPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE);
        if (rc == PackageManager.PERMISSION_GRANTED) {
            chooseFromLibrary(context);
        } else {
            requestExternalReadPermission();
        }
    }

    private void chooseFromLibrary(@NonNull Context context) {
        Intent intent = new Intent();
        intent.setType("image/*");
        intent.setAction(Intent.ACTION_GET_CONTENT);
        Intent pickPictureIntent = Intent.createChooser(intent, getString(R.string.imagetransfer_imageorigin_choose));
        if (pickPictureIntent.resolveActivity(context.getPackageManager()) != null) {
            startActivityForResult(pickPictureIntent, kActivityRequestCode_pickFromGallery);
        } else {
            Log.w(TAG, "There is no photo picker available");
        }
    }

    private void setImage(Bitmap bitmap) {
        mImageRotationDegrees = 0;      // reset rotation
        mBitmap = bitmap;
        updateImage(mResolution, mIsEInkModeEnabled, mImageRotationDegrees);
    }
    // endregion

    // region FormatSelectorListener
    @Override
    public void onResolutionSelected(Size resolution, boolean isEInkMode) {
        Log.d(TAG, "Resolution selected: " + resolution.getWidth() + ", " + resolution.getHeight() + " isEInk: " + isEInkMode);

        updateImage(resolution, isEInkMode, mImageRotationDegrees);
    }
    // endregion

    // region Image Picker

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        Context context = getContext();
        if (context == null) return;

        if (requestCode == kActivityRequestCode_pickFromGallery && resultCode == Activity.RESULT_OK) {
            if (data != null && data.getData() != null) {

                // Copy image to temporary file
                try {
                    InputStream input = context.getContentResolver().openInputStream(data.getData());
                    if (input != null) {
                        final File temporaryFile = File.createTempFile("imagetransfer_picture", null);
                        temporaryFile.deleteOnExit();
                        try (FileOutputStream output = new FileOutputStream(temporaryFile)) {
                            byte[] buffer = new byte[4 * 1024];
                            int read;
                            while ((read = input.read(buffer)) != -1) {
                                output.write(buffer, 0, read);
                            }

                            output.flush();
                        }

                        cropImage(temporaryFile.getPath());
                    }

                } catch (FileNotFoundException e) {
                    Log.e(TAG, "Error opening image: " + e);
                } catch (IOException e) {
                    Log.e(TAG, "Error creating temporary image: " + e);
                }

            } else {
                Log.w(TAG, "Couldn't pick a photo");
            }
        } else if (requestCode == kActivityRequestCode_takePicture && resultCode == Activity.RESULT_OK) {
            Log.d(TAG, "Picture taken");

            addPictureToGallery(context, mTemporalPhotoPath);
            Uri photoUri = Uri.parse(mTemporalPhotoPath);
            String photoPath = photoUri.getPath();
            if (photoPath != null) {
                cropImage(photoPath);
            }
        } else if (requestCode == kActivityRequestCode_cropPicture && resultCode == Activity.RESULT_OK) {
            Log.d(TAG, "kActivityRequestCode_cropPicture");
        }

        super.onActivityResult(requestCode, resultCode, data);

    }

    private File createImageFile(@NonNull Context context) throws IOException {
        // Create an image file name
        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(new Date());
        String imageFileName = "JPEG_" + timeStamp + "_";
        File storageDir = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
        File image = File.createTempFile(imageFileName, ".jpg", storageDir);

        // Save a file: path for use with ACTION_VIEW intents
        mTemporalPhotoPath = "file:" + image.getAbsolutePath();
        return image;
    }

    private void addPictureToGallery(@NonNull Context context, @NonNull String photoPath) {
        Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
        try {
            File f = new File(photoPath);
            Uri contentUri = Uri.fromFile(f);
            mediaScanIntent.setData(contentUri);
            context.sendBroadcast(mediaScanIntent);
        } catch (NullPointerException e) {
            Log.e(TAG, "Error opening file: " + photoPath);
        }
    }
    // endregion


    // region Permissions
    private void requestCameraPermission() {
        FragmentActivity activity = getActivity();
        if (activity == null) {
            return;
        }

        Log.w(TAG, "Camera permission is not granted. Requesting permission");

        final String[] permissions = new String[]{Manifest.permission.CAMERA};

        requestPermissions(permissions, kActivityRequestCode_requestCameraPermission);

        /* No need to explain rationale
        if (!shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) {
            requestPermissions(permissions, kActivityRequestCode_requestCameraPermission);
            return;
        }
        Toast.makeText(activity, R.string.imagetransfer_cameraneeded, Toast.LENGTH_LONG).show();
         */
    }

    private void requestExternalReadPermission() {
        FragmentActivity activity = getActivity();
        if (activity == null) {
            return;
        }

        Log.w(TAG, "External read permission is not granted. Requesting permission");

        final String[] permissions = new String[]{Manifest.permission.READ_EXTERNAL_STORAGE};
        requestPermissions(permissions, kActivityRequestCode_requestReadExternalStoragePermission);

        /* No need to explain rationale
        if (!shouldShowRequestPermissionRationale(Manifest.permission.READ_EXTERNAL_STORAGE)) {
            requestPermissions(permissions, kActivityRequestCode_requestReadExternalStoragePermission);
            return;
        }
        Toast.makeText(activity, R.string.imagetransfer_readexternalneeded, Toast.LENGTH_LONG).show();
        */
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        Context context = getContext();
        if (context == null) return;

        if (requestCode == kActivityRequestCode_requestCameraPermission) {
            if (grantResults.length != 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                Log.d(TAG, "Camera permission granted - initialize the camera source");
                chooseFromCamera(context);
            } else {

                Log.e(TAG, "Permission not granted: results len = " + grantResults.length + " Result code = " + (grantResults.length > 0 ? grantResults[0] : "(empty)"));

                AlertDialog.Builder builder = new AlertDialog.Builder(context);
                builder.setMessage(R.string.imagetransfer_cameraneeded)
                        .setPositiveButton(android.R.string.ok, null)
                        .show();
            }
        } else if (requestCode == kActivityRequestCode_requestReadExternalStoragePermission) {
            if (grantResults.length != 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                Log.d(TAG, "External read permission granted");
                chooseFromLibrary(context);
            } else {
                Log.e(TAG, "Permission not granted: results len = " + grantResults.length + " Result code = " + (grantResults.length > 0 ? grantResults[0] : "(empty)"));

                AlertDialog.Builder builder = new AlertDialog.Builder(context);
                builder.setMessage(R.string.imagetransfer_readexternalneeded)
                        .setPositiveButton(android.R.string.ok, null)
                        .show();
            }
        } else {
            Log.d(TAG, "Got unexpected permission result: " + requestCode);
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }

    // endregion

    // region Transform Image

    private void cropImage(@NonNull String imagePath) {
        FragmentManager fragmentManager = getFragmentManager();
        if (fragmentManager != null) {
            ImageCropFragment imageCropFragment = ImageCropFragment.newInstance(imagePath, mResolution.getWidth(), mResolution.getHeight());
            imageCropFragment.setTargetFragment(ImageTransferFragment.this, 0);

            fragmentManager.beginTransaction()
                    .add(R.id.contentLayout, imageCropFragment)
                    .addToBackStack(ImageCropFragment.TAG)
                    .commitAllowingStateLoss();
        }
    }

    // endregion

    // region Send Image

    private void sendImage(int packetWithResponseEveryPacketCount, boolean isColorSpace24Bits) {
        Bitmap bitmap = ((BitmapDrawable) mCameraImageView.getDrawable()).getBitmap();
        //Bitmap bitmap = mBitmapDrawable.getBitmap();

        // Create 32bits
        final int height = bitmap.getHeight();
        final int width = bitmap.getWidth();
        Bitmap rgbaBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(rgbaBitmap);
        Paint paint = new Paint();
        canvas.drawBitmap(bitmap, 0, 0, paint);

        // get bytes
        final int rgbaSize = rgbaBitmap.getRowBytes() * rgbaBitmap.getHeight();
        ByteBuffer byteBuffer = ByteBuffer.allocate(rgbaSize);
        bitmap.copyPixelsToBuffer(byteBuffer);
        byte[] rgbaBytes = byteBuffer.array();

        //bitmap.recycle();
        byteBuffer = null;

        int rgbSize;
        byte[] rgbBytes;
        if (isColorSpace24Bits) {
            // Convert 32bit color data to 24bit (888)
            rgbSize = width * height * 3;
            rgbBytes = new byte[rgbSize];
            int k = 0;
            for (int i = 0; i < rgbaSize; i++) {
                if (i % 4 != 3) {
                    rgbBytes[k++] = rgbaBytes[i];
                }
            }
        } else {
            // Convert 32bit color data to 16bit (565)
            byte r = 0, g = 0;
            rgbSize = width * height * 2;
            rgbBytes = new byte[rgbSize];
            int k = 0;
            for (int i = 0; i < rgbaSize; i++) {
                int j = i % 4;
                if (j == 0) {
                    r = rgbaBytes[i];
                } else if (j == 1) {
                    g = rgbaBytes[i];

                } else if (j == 2) {
                    byte b = rgbaBytes[i];

                    int rShort = (r & 0xF8) & 0xffff;
                    int gShort = (g & 0xFC) & 0xffff;
                    int bShort = b & 0xff;
                    int rgb16 = (rShort << 8) | (gShort << 3) | (bShort >>> 3);

                    byte high = (byte) ((rgb16 >> 8) & 0xff);
                    byte low = (byte) (rgb16 & 0xff);

                    // Add as little endian
                    rgbBytes[k++] = low;
                    rgbBytes[k++] = high;
                }
            }
        }

        rgbaBitmap.recycle();

        // Send command
        ByteBuffer buffer = ByteBuffer.allocate(2 + 1 + 2 + 2 + rgbSize).order(java.nio.ByteOrder.LITTLE_ENDIAN);

        // Command: '!I'
        String prefix = "!I";
        buffer.put(prefix.getBytes());
        buffer.put((byte) (isColorSpace24Bits ? 24 : 16));
        buffer.putShort((short) width);
        buffer.putShort((short) height);
        buffer.put(rgbBytes);

        byte[] result = buffer.array();

        sendCrcData(result, packetWithResponseEveryPacketCount);

        rgbaBytes = null;
    }

    private void sendCrcData(byte[] data, int packetWithResponseEveryPacketCount) {
        Context context = getContext();
        if (context == null) return;

        if (mUartManager == null) {
            return;
        }

        /*
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            mBlePeripheral.readPhy();
        }*/

        // Progress fragment
        FragmentManager fragmentManager = getFragmentManager();
        if (fragmentManager != null) {
            dismissProgressDialog();

            mProgressDialog = ProgressFragmentDialog.newInstance(context.getString(R.string.imagetransfer_transferring));
            mProgressDialog.show(fragmentManager, null);
            fragmentManager.executePendingTransactions();

            mProgressDialog.setIndeterminate(false);
            mProgressDialog.setOnCancelListener(dialog -> {
                cancelCurrentSendCommand();
                dismissProgressDialog();
            });
        }

        final byte[] crcData = BlePeripheralUart.appendCrc(data);

        mUartManager.sendEachPacketSequentially(mBlePeripheralUart, crcData, packetWithResponseEveryPacketCount, progress -> {
            if (mProgressDialog != null) {
                //Log.d(TAG, "progress: " + ((int) (progress * 100)));
                mProgressDialog.setProgress((int) (progress * 100));
            }
        }, status -> {
            dismissProgressDialog();

            if (status != BluetoothGatt.GATT_SUCCESS) {
                AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
                AlertDialog dialog = builder.setMessage(R.string.imagetransfer_senddata_error)
                        .setPositiveButton(android.R.string.ok, null)
                        .show();
                DialogUtils.keepDialogOnOrientationChanges(dialog);
            }
        });
    }

    // endregion

    // region Progress

    private void cancelCurrentSendCommand() {
        if (mBlePeripheralUart != null) {     // mBlePeripheralUart could be null if the peripheral disconnected
            mUartManager.cancelOngoingSendPacketSequentiallyInThread(mBlePeripheralUart);
        }
    }

    private void dismissProgressDialog() {
        if (mProgressDialog != null) {
            mProgressDialog.dismiss();
            mProgressDialog = null;
        }
    }
    // endregion

    // region ImageCropFragment
    public void onCropFinished(Bitmap bitmap) {
        Log.d(TAG, "onCropFinished");
        setImage(bitmap);

    }
    // endregion
}