/* Copyright 2017 Andrew Dawson
 *
 * This file is a part of Tusky.
 *
 * This program is free software; you can redistribute it and/or modify it under the terms of the
 * GNU General Public License as published by the Free Software Foundation; either version 3 of the
 * License, or (at your option) any later version.
 *
 * Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
 * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
 * Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with Tusky; if not,
 * see <http://www.gnu.org/licenses>. */

package com.keylesspalace.tusky;

import android.Manifest;
import android.content.ContentResolver;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.ActionBar;
import android.support.v7.widget.Toolbar;
import android.util.Base64;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.ProgressBar;

import com.keylesspalace.tusky.entity.Account;
import com.keylesspalace.tusky.entity.Profile;
import com.keylesspalace.tusky.util.IOUtils;
import com.pkmmte.view.CircularImageView;
import com.squareup.picasso.Picasso;
import com.theartofdev.edmodo.cropper.CropImage;

import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;

public class EditProfileActivity extends BaseActivity {
    private static final String TAG = "EditProfileActivity";
    private static final int AVATAR_PICK_RESULT = 1;
    private static final int HEADER_PICK_RESULT = 2;
    private static final int PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 1;
    private static final int AVATAR_WIDTH = 120;
    private static final int AVATAR_HEIGHT = 120;
    private static final int HEADER_WIDTH = 700;
    private static final int HEADER_HEIGHT = 335;

    private enum PickType {
        NOTHING,
        AVATAR,
        HEADER
    }

    private ImageView headerPreview;
    private ProgressBar headerProgress;
    private ImageButton avatarButton;
    private ImageView avatarPreview;
    private ProgressBar avatarProgress;
    private EditText displayNameEditText;
    private EditText noteEditText;
    private ProgressBar saveProgress;
    private String priorDisplayName;
    private String priorNote;
    private boolean isAlreadySaving;
    private PickType currentlyPicking;
    private String avatarBase64;
    private String headerBase64;

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

        ImageButton headerButton = findViewById(R.id.edit_profile_header);
        headerPreview = findViewById(R.id.edit_profile_header_preview);
        headerProgress = findViewById(R.id.edit_profile_header_progress);
        avatarButton = findViewById(R.id.edit_profile_avatar);
        avatarPreview = findViewById(R.id.edit_profile_avatar_preview);
        avatarProgress = findViewById(R.id.edit_profile_avatar_progress);
        displayNameEditText = findViewById(R.id.edit_profile_display_name);
        noteEditText = findViewById(R.id.edit_profile_note);
        saveProgress = findViewById(R.id.edit_profile_save_progress);

        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        ActionBar actionBar = getSupportActionBar();
        if (actionBar != null) {
            actionBar.setTitle(getString(R.string.title_edit_profile));
            actionBar.setDisplayHomeAsUpEnabled(true);
            actionBar.setDisplayShowHomeEnabled(true);
        }

        if (savedInstanceState != null) {
            priorDisplayName = savedInstanceState.getString("priorDisplayName");
            priorNote = savedInstanceState.getString("priorNote");
            isAlreadySaving = savedInstanceState.getBoolean("isAlreadySaving");
            currentlyPicking = (PickType) savedInstanceState.getSerializable("currentlyPicking");
            avatarBase64 = savedInstanceState.getString("avatarBase64");
            headerBase64 = savedInstanceState.getString("headerBase64");
        } else {
            priorDisplayName = null;
            priorNote = null;
            isAlreadySaving = false;
            currentlyPicking = PickType.NOTHING;
            avatarBase64 = null;
            headerBase64 = null;
        }

        avatarButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onMediaPick(PickType.AVATAR);
            }
        });
        headerButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onMediaPick(PickType.HEADER);
            }
        });

        avatarPreview.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                avatarPreview.setImageBitmap(null);
                avatarPreview.setVisibility(View.INVISIBLE);
                avatarBase64 = null;
            }
        });
        headerPreview.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                headerPreview.setImageBitmap(null);
                headerPreview.setVisibility(View.INVISIBLE);
                headerBase64 = null;
            }
        });

        mastodonApi.accountVerifyCredentials().enqueue(new Callback<Account>() {
            @Override
            public void onResponse(Call<Account> call, Response<Account> response) {
                if (!response.isSuccessful()) {
                    onAccountVerifyCredentialsFailed();
                    return;
                }
                Account me = response.body();
                priorDisplayName = me.getDisplayName();
                priorNote = me.note.toString();
                CircularImageView avatar =
                        findViewById(R.id.edit_profile_avatar_preview);
                ImageView header = findViewById(R.id.edit_profile_header_preview);

                displayNameEditText.setText(priorDisplayName);
                noteEditText.setText(priorNote);
                Picasso.with(avatar.getContext())
                        .load(me.avatar)
                        .placeholder(R.drawable.avatar_default)
                        .into(avatar);
                Picasso.with(header.getContext())
                        .load(me.header)
                        .placeholder(R.drawable.account_header_default)
                        .into(header);
            }

            @Override
            public void onFailure(Call<Account> call, Throwable t) {
                onAccountVerifyCredentialsFailed();
            }
        });
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        outState.putString("priorDisplayName", priorDisplayName);
        outState.putString("priorNote", priorNote);
        outState.putBoolean("isAlreadySaving", isAlreadySaving);
        outState.putSerializable("currentlyPicking", currentlyPicking);
        outState.putString("avatarBase64", avatarBase64);
        outState.putString("headerBase64", headerBase64);
        super.onSaveInstanceState(outState);
    }

    private void onAccountVerifyCredentialsFailed() {
        Log.e(TAG, "The account failed to load.");
    }

    private void onMediaPick(PickType pickType) {
        if (currentlyPicking != PickType.NOTHING) {
            // Ignore inputs if another pick operation is still occurring.
            return;
        }
        currentlyPicking = pickType;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN &&
                ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
                        != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this,
                    new String[] { Manifest.permission.READ_EXTERNAL_STORAGE },
                    PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE);
        } else {
            initiateMediaPicking();
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[],
            @NonNull int[] grantResults) {
        switch (requestCode) {
            case PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE: {
                if (grantResults.length > 0
                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    initiateMediaPicking();
                } else {
                    endMediaPicking();
                    Snackbar.make(avatarButton, R.string.error_media_upload_permission,
                            Snackbar.LENGTH_LONG).show();
                }
                break;
            }
        }
    }

    private void initiateMediaPicking() {
        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
        intent.addCategory(Intent.CATEGORY_OPENABLE);
        intent.setType("image/*");
        switch (currentlyPicking) {
            case AVATAR: { startActivityForResult(intent, AVATAR_PICK_RESULT); break; }
            case HEADER: { startActivityForResult(intent, HEADER_PICK_RESULT); break; }
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.edit_profile_toolbar, menu);
        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case android.R.id.home: {
                onBackPressed();
                return true;
            }
            case R.id.action_save: {
                save();
                return true;
            }
        }
        return super.onOptionsItemSelected(item);
    }

    private void save() {
        if (isAlreadySaving || currentlyPicking != PickType.NOTHING) {
            return;
        }
        String newDisplayName = displayNameEditText.getText().toString();
        if (newDisplayName.isEmpty()) {
            displayNameEditText.setError(getString(R.string.error_empty));
            return;
        }
        if (priorDisplayName != null && priorDisplayName.equals(newDisplayName)) {
            // If it's not any different, don't patch it.
            newDisplayName = null;
        }

        String newNote = noteEditText.getText().toString();
        if (newNote.isEmpty()) {
            noteEditText.setError(getString(R.string.error_empty));
            return;
        }
        if (priorNote != null && priorNote.equals(newNote)) {
            // If it's not any different, don't patch it.
            newNote = null;
        }
        if (newDisplayName == null && newNote == null && avatarBase64 == null
                && headerBase64 == null) {
            // If nothing is changed, then there's nothing to save.
            return;
        }

        saveProgress.setVisibility(View.VISIBLE);

        isAlreadySaving = true;

        Profile profile = new Profile();
        profile.displayName = newDisplayName;
        profile.note = newNote;
        profile.avatar = avatarBase64;
        profile.header = headerBase64;
        mastodonApi.accountUpdateCredentials(profile).enqueue(new Callback<Account>() {
            @Override
            public void onResponse(Call<Account> call, Response<Account> response) {
                if (!response.isSuccessful()) {
                    onSaveFailure();
                    return;
                }
                getPrivatePreferences().edit()
                        .putBoolean("refreshProfileHeader", true)
                        .apply();
                finish();
            }

            @Override
            public void onFailure(Call<Account> call, Throwable t) {
                onSaveFailure();
            }
        });
    }

    private void onSaveFailure() {
        isAlreadySaving = false;
        Snackbar.make(avatarButton, R.string.error_media_upload_sending, Snackbar.LENGTH_LONG)
                .show();
        saveProgress.setVisibility(View.GONE);
    }

    private void beginMediaPicking() {
        switch (currentlyPicking) {
            case AVATAR: {
                avatarProgress.setVisibility(View.VISIBLE);
                avatarPreview.setVisibility(View.INVISIBLE);
                break;
            }
            case HEADER: {
                headerProgress.setVisibility(View.VISIBLE);
                headerPreview.setVisibility(View.INVISIBLE);
                break;
            }
        }
    }

    private void endMediaPicking() {
        switch (currentlyPicking) {
            case AVATAR: {
                avatarProgress.setVisibility(View.GONE);
                break;
            }
            case HEADER: {
                headerProgress.setVisibility(View.GONE);
                break;
            }
        }
        currentlyPicking = PickType.NOTHING;
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        switch (requestCode) {
            case AVATAR_PICK_RESULT: {
                if (resultCode == RESULT_OK && data != null) {
                    CropImage.activity(data.getData())
                            .setInitialCropWindowPaddingRatio(0)
                            .setAspectRatio(AVATAR_WIDTH, AVATAR_HEIGHT)
                            .start(this);
                } else {
                    endMediaPicking();
                }
                break;
            }
            case HEADER_PICK_RESULT: {
                if (resultCode == RESULT_OK && data != null) {
                    CropImage.activity(data.getData())
                            .setInitialCropWindowPaddingRatio(0)
                            .setAspectRatio(HEADER_WIDTH, HEADER_HEIGHT)
                            .start(this);
                } else {
                    endMediaPicking();
                }
                break;
            }
            case CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE: {
                CropImage.ActivityResult result = CropImage.getActivityResult(data);
                if (resultCode == RESULT_OK) {
                    beginResize(result.getUri());
                } else if (resultCode == CropImage.CROP_IMAGE_ACTIVITY_RESULT_ERROR_CODE) {
                    onResizeFailure();
                }
                break;
            }
        }
    }

    private void beginResize(Uri uri) {
        beginMediaPicking();
        int width, height;
        switch (currentlyPicking) {
            default: {
                throw new AssertionError("PickType not set.");
            }
            case AVATAR: {
                width = AVATAR_WIDTH;
                height = AVATAR_HEIGHT;
                break;
            }
            case HEADER: {
                width = HEADER_WIDTH;
                height = HEADER_HEIGHT;
                break;
            }
        }
        new ResizeImageTask(getContentResolver(), width, height, new ResizeImageTask.Listener() {
                @Override
                public void onSuccess(List<Bitmap> contentList) {
                    Bitmap bitmap = contentList.get(0);
                    PickType pickType = currentlyPicking;
                    endMediaPicking();
                    switch (pickType) {
                        case AVATAR: {
                            avatarPreview.setImageBitmap(bitmap);
                            avatarPreview.setVisibility(View.VISIBLE);
                            avatarBase64 = bitmapToBase64(bitmap);
                            break;
                        }
                        case HEADER: {
                            headerPreview.setImageBitmap(bitmap);
                            headerPreview.setVisibility(View.VISIBLE);
                            headerBase64 = bitmapToBase64(bitmap);
                            break;
                        }
                    }
                }

                @Override
                public void onFailure() {
                    onResizeFailure();
                }
            }).execute(uri);
    }

    private void onResizeFailure() {
        Snackbar.make(avatarButton, R.string.error_media_upload_sending, Snackbar.LENGTH_LONG)
                .show();
        endMediaPicking();
    }

    private static String bitmapToBase64(Bitmap bitmap) {
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
        byte[] byteArray = stream.toByteArray();
        IOUtils.closeQuietly(stream);
        return "data:image/png;base64," + Base64.encodeToString(byteArray, Base64.DEFAULT);
    }

    private static class ResizeImageTask extends AsyncTask<Uri, Void, Boolean> {
        private ContentResolver contentResolver;
        private int resizeWidth;
        private int resizeHeight;
        private Listener listener;
        private List<Bitmap> resultList;

        ResizeImageTask(ContentResolver contentResolver, int width, int height, Listener listener) {
            this.contentResolver = contentResolver;
            this.resizeWidth = width;
            this.resizeHeight = height;
            this.listener = listener;
        }

        @Override
        protected Boolean doInBackground(Uri... uris) {
            resultList = new ArrayList<>();
            for (Uri uri : uris) {
                InputStream inputStream;
                try {
                    inputStream = contentResolver.openInputStream(uri);
                } catch (FileNotFoundException e) {
                    Log.d(TAG, Log.getStackTraceString(e));
                    return false;
                }
                Bitmap sourceBitmap;
                try {
                    sourceBitmap = BitmapFactory.decodeStream(inputStream, null, null);
                } catch (OutOfMemoryError error) {
                    Log.d(TAG, Log.getStackTraceString(error));
                    return false;
                } finally {
                    IOUtils.closeQuietly(inputStream);
                }
                if (sourceBitmap == null) {
                    return false;
                }
                Bitmap bitmap = Bitmap.createScaledBitmap(sourceBitmap, resizeWidth, resizeHeight,
                        false);
                sourceBitmap.recycle();
                if (bitmap == null) {
                    return false;
                }
                resultList.add(bitmap);
                if (isCancelled()) {
                    return false;
                }
            }
            return true;
        }

        @Override
        protected void onPostExecute(Boolean successful) {
            if (successful) {
                listener.onSuccess(resultList);
            } else {
                listener.onFailure();
            }
            super.onPostExecute(successful);
        }

        interface Listener {
            void onSuccess(List<Bitmap> contentList);
            void onFailure();
        }
    }
}
