package com.netfluke.sergey.catpicker;

import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.graphics.drawable.GradientDrawable;
import android.media.AudioManager;
import android.media.SoundPool;
import android.os.Build;
import android.os.PersistableBundle;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;

import java.util.List;

/* NOTE: I started out using tags in FragmentTransaction replace() when adding fragments.
           I thought to use these tags to retrieve these fragments when I needed them,
            to update their contents or to remove them. I believed that findFragmentByTag(..)
            would give me the active visible fragment with that tag. That way I would not
            need to keep extra global references to fragments.

         Unfortunately, this failed. I would occasionally get fragments that have already
            been removed and were waiting to be destroyed, rather than the added visible
            fragment added under the same tag. I confirmed it by running
               "adb shell dumpsys activity com.netfluke.sergey.catpicker"; the output
            showed fragments with mRemoving=true mAdded=false alongside the visible
            fragments. Apparently, findFragmentByTag(..) returned these.

         I tried forcefully committing pending transactions with
               getFragmentManager().executePendingTransactions();
            but it did not eliminate the removed fragments from findFragmentByTag's output.
            See also the code snippet marked with "debug", which shows this is the case.

         Hence I opted to track active fragments explicitly in private class members; I left
            tags and code to show occasional defunct fragments in findFragmentByTag(..) results.
 */

public class MainActivity extends AppCompatActivity {

    private int catId;

    // Always start with the cat chosen at the previous run, even
    //   after the app has been quit.
    private SharedPreferences prefs;

    // keep the fragments explicitly for onChangeConfiguration
    private ImageFragment im;   // full screen or null in Portrait, always present in Landscape
    private PickerFragment pf;  // there can be only one, but can go on backstack in Portrait mode

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

        Log.d("MAIN", "onCreate( " +
                ((savedInstanceState == null) ? "null" : savedInstanceState.toString() + " )"));

        prefs = getPreferences(Context.MODE_PRIVATE);
        catId = prefs.getInt("CAT_ID", R.drawable.pusheen1);

        attachFragments();
    }

    private boolean isPortrait()
    {
        Configuration o = getResources().getConfiguration();
        return o.orientation == Configuration.ORIENTATION_PORTRAIT;
    }

    private void attachFragments()
    {

        FragmentManager fmgr = getFragmentManager();

        if( isPortrait() ) {

            setContentView(R.layout.activity_portrait);

            // create and add a Picker (vertical buttons)
            pf = new PickerFragment();

            FragmentTransaction t = fmgr.beginTransaction();
            t.replace(R.id.content_frame, pf, "picker");
            t.commit();
        }
        else {  // landscape

            setContentView(R.layout.activity_landscape);

            // add both new picker and image
            FragmentTransaction t = fmgr.beginTransaction();
            pf = new PickerFragment();
            im = new ImageFragment();

            im.catId = catId;

            t.replace(R.id.content_frameA, pf, "picker");
            t.replace(R.id.content_frameB, im, "image");
            t.commit();

            // this sometimes runs *before* onCreateView of the fragment,
            //    so looking up image by id in loadPicture(..) fails to find it:
            //
            // im.loadPicture(catId);


            //  Calling
            //      fmgr.executePendingTransactions();
            //  right after commit does not actually help: although the
            //    ImageFragment gets attached right away, views would still take
            //    some time to load and may still be absent when looked for.
            //
            //  By contrast, setting the property instead and having the fragment
            //    load the picture when its views are ready in onResume() worked.
        }

    }

    @Override
    protected void onPause() {
        super.onPause();
        // save the cat's state, to start with the same cat when app is next launched in landscape
        SharedPreferences.Editor ed = prefs.edit();
        ed.putInt("CAT_ID", catId);
        ed.commit();
    }

    /* We need to suppress calling super's methods to avoid automatic restoration of views and
     *   thus duplication of fragments. Compare the contents of the savedInstanceState bundle
     *   on entry to onCreate(..) when these functions are and are not called.
     */
    @Override
    public void onSaveInstanceState(Bundle outState) {
        //super.onSaveInstanceState(outState);
        Log.d("STATE", "onSaveInstanceState");

        outState.putInt("CAT_ID", catId);
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        //super.onRestoreInstanceState(savedInstanceState);
        Log.d("STATE", "onRestoreInstanceState" + savedInstanceState.toString());

        catId = savedInstanceState.getInt("CAT_ID");
    }


    /* This function is called in response to all three button clicks in Picker.
     *   It finds out which button was clicked by checking the ID of the View
     *   passed in, i.e., the View that accepted the click.
     */
    public void click(View v) {

        // which button?
        switch (v.getId()) {
            case R.id.button1:
                catId = R.drawable.manul1;
                break;
            case R.id.button2:
                catId = R.drawable.manul2;
                break;
            case R.id.button3:
                catId = R.drawable.manul3;
                break;
            default:
                Log.d("MAIN", "no such cat");
                catId = R.drawable.pusheen1;
        }

        // debug: Finding fragment by tag will occasionally return a fragement
        //   already removed, and waiting to be destroyed. Thus finding fragments
        //   by tag is not always useful: it may return a "dead" fragment detached
        //   from an activity; a live fragment with the same tag could be hidden.
        //   Hence this activity keeps explicit references to it fragments.
        Fragment imgByTag = getFragmentManager().findFragmentByTag("image");
        if (imgByTag != null && imgByTag.isRemoving() ) {
            Log.d("FRAG", "ImageFragment retrieved by tag is in removing stage");
        }
        // end debug

        // We want to do different things in Portrait and Landscape:
        //   portrait:  push PickerFragment on backstack, replace content frame with new ImageFragment
        //   landscape: set image in existing ImageFragment
        if (isPortrait()) {
            // there should be no active ImageFragment
            //   im will retain a pointer to a previous one called from Picker, though.

            im = new ImageFragment();
            im.catId = catId;

            FragmentTransaction t = getFragmentManager().beginTransaction();
            t.replace(R.id.content_frame, im, "image");

            // but keep the obscured PickerFragment on the backstack, so that
            //  the "Back" button brings us back to it (so we can select more cats!)
            t.addToBackStack(null);

            t.commit();

        } else {  // landscape . Image fragment should exist and be visible
            if (im == null)
                Log.d("MAIN", "no image fragment in Landscape!");
            else if (im.isVisible())
                im.loadPicture(catId);
            else {
                Log.d("MAIN", "image fragment is not visible. This should not happen.");
            }
        }
    }

    int soundId;

    // For this function to get called, the Manifest must specify
    //     android:configChanges="orientation|screenSize"  on the activity element.
    // When this is the case, the activity does _not_ get destroyed on flip,
    //   but rather this function is called.
    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);  // exception is thrown if this isn't called!
        Log.d("MAIN", "onConfigurationChange");

        // play a meow
        SoundPool soundPool;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            soundPool = new SoundPool.Builder().build();
        } else {
            soundPool = new SoundPool(10, AudioManager.STREAM_MUSIC, 0);
        }
        // Observe the usual pattern for longer actions (reading, parsing,
        //   and loading a sound file can be long, compared to the time
        //   frame in which we expect UI responses). So we kick off the
        //   loading, and set a listener to run when it completes; we cannot
        //   wait on the UI thread until the loading is complete. Instead,
        //   load(..) queues the task and gets an ID.
        //
        // Note that the callback we create is closed over soundId (the
        //   SoundPool's internal id that a decoded sound gets in the pool).
        //   So the ID is passed to play(..) via this closure.
        soundPool.setOnLoadCompleteListener(new SoundPool.OnLoadCompleteListener() {
            @Override
            public void onLoadComplete(SoundPool soundPool, int i, int i1) {
                soundPool.play(soundId, 1f, 1f, 1, 0, 1f);
            }
        });

        soundId = soundPool.load(this, R.raw.meow, 1);

        // since the activity will not get destroyed, we must manually remove active
        //   fragments
        removeActiveFragments();

        attachFragments();
    }

    private void removeActiveFragments(){

        // print the list of existing fragments
        // This works only in API 26 (e.g., on the emulator)
        if (Build.VERSION.SDK_INT >= 26 ) {
            List<Fragment> frags = getFragmentManager().getFragments();
            for (Fragment f : frags) {
                Log.d("FRAG", f.toString() + " (removing: " + f.isRemoving() + ")");
            }
        }

        // remove active fragments
        FragmentTransaction t = getFragmentManager().beginTransaction();
        if( im != null )
            t.remove(im);  // im can be null, e.g., when in portrait no button has been pressed
        if( pf != null )
            t.remove(pf);
        else
            Log.d("FRAG", "null Picker fragment. This should never happen");
        t.commit();

        im = null;
        pf = null;

        // to make sure this transition goes through before we start adding fragments:
        // getFragmentManager().executePendingTransactions();
    }
}
