/*
 * DSBDirect
 * Copyright (C) 2019 Fynn Godau
 *
 * 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.
 *
 * This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * This software is not affiliated with heinekingmedia GmbH, the
 * developer of the DSB platform.
 */

package godau.fynn.dsbdirect;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.annotation.StringRes;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.AppCompatImageView;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.*;
import android.webkit.WebView;
import android.widget.*;

import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.ImageRequest;
import com.android.volley.toolbox.StringRequest;
import com.android.volley.toolbox.Volley;

import com.wefika.horizontalpicker.HorizontalPicker;
import humanize.Humanize;
import humanize.time.TimeMillis;
import org.json.JSONException;

import java.io.File;
import java.text.ParseException;
import java.util.*;

public class MainActivity extends AppCompatActivity {

    private Response.ErrorListener mErrorListener;
    private Table mCurrentTable;
    private String credentialsId;
    private String credentialsPass;

    private String mAuthToken;
    private Table[] mTables;
    private Date mTimetabledate;

    boolean mTimetabledateDisplayed = false;

    private Utility u;
    private FileManager mFileManager;
    private DownloadManager mDownloadManager;

    private TextView mTextView;
    private RequestQueue mQueue;

    private boolean mParse = true;

    private Menu mMenu;

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

        u = new Utility(MainActivity.this);
        final SharedPreferences sharedPreferences = u.getSharedPreferences();
        credentialsId = sharedPreferences.getString("id", "162161");
        credentialsPass = sharedPreferences.getString("pass", "avhMOBIL1App");
        mAuthToken = sharedPreferences.getString("token", "00000000-0000-0000-0000-000000000000");

        mParse = sharedPreferences.getBoolean("parse", true);
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
            // we can't parse below KitKat because dev is dumb
            sharedPreferences.edit().putBoolean("parse", false).apply();
        }


        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        setOverflowButtonColor(this, getResources().getColor(R.color.white));

        mTextView = findViewById(R.id.text);

        // Instantiate the RequestQueue
        mQueue = Volley.newRequestQueue(this);

        // Get a file manager
        mFileManager = new FileManager(MainActivity.this);

        // We are going to need the ErrorListener later
        mErrorListener = new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {

                Snackbar
                        .make(findViewById(R.id.root),
                                getString(R.string.you_are_offline),
                                Snackbar.LENGTH_SHORT)
                        .show();
                // Enter offline mode
                offlineMode();

                //mTextView.setText(getString(R.string.network_generic_error));
                Log.d("Network error", error.toString());
            }

        };

        mDownloadManager = new DownloadManager(this);

        initChain();

        // Update checker
        if (u.shouldCheckForUpdates()) {
            u.checkForUpdate(false);
        }

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            u.schedulePolling();
        }
    }

    private void initChain() {
        // only do so if credentials are actually given
        if (credentialsId.length() > 0 && credentialsPass.length() > 0) {
            getTimetables();
        } else {
            showAuthPrompt();
        }
    }

    private void getCredentials() {
        Handler credentialsInvalidHandler = new Handler(new Handler.Callback() {
            @Override
            public boolean handleMessage(Message msg) {
                // The credentials were not correct

                mTextView.setText(R.string.credentials_invalid);
                showAuthPrompt();

                return false;
            }
        });

        Handler credentialsCorrectHandler = new Handler(new Handler.Callback() {
            @Override
            public boolean handleMessage(Message msg) {
                // The credentials were correct

                // Display our super-duper hard-earned fact-checked auth token!
                mTextView.setText(getString(R.string.credentials_correct));

                String authToken = msg.getData().getString(DownloadManager.RESPONSE_TOKEN);

                // Store it for next launch
                u.saveAuthToken(authToken);

                // Next step: Fetch timetables
                mAuthToken = authToken;
                getTimetables();

                return false;
            }
        });

        mDownloadManager.downloadToken(credentialsId, credentialsPass, credentialsInvalidHandler,
                credentialsCorrectHandler, mErrorListener);
    }

    private void getTimetables() {

        Handler noContentHandler = new Handler(new Handler.Callback() {
            @Override
            public boolean handleMessage(Message msg) {
                switch (msg.what) {
                    case DownloadManager.RESPONSE_NO_CONTENT_REASON_NO_CONTENT:
                        // No content is available
                        // Under the assumption that there will never be no "content" except if token wrong
                        getCredentials();
                        // In case there truly is no content we loop in circles…

                        break;
                    case DownloadManager.RESPONSE_NO_CONTENT_REASON_INVALID_RESPONSE:
                        // The response returned by the server was invalid
                        mTextView.setText(R.string.network_invalid_response);
                        break;
                }

                return false;
            }
        });

        Handler onResponseHandler = new Handler(new Handler.Callback() {
            @Override
            public boolean handleMessage(Message msg) {
                // A response was returned successfully and there is not no content
                // Parse JSON
                try {
                    mTables = Reader.readTableList(msg.getData().getString(DownloadManager.RESPONSE_JSON));

                    mTextView.setText(getString(R.string.timetable_uri_acquired));

                    // Find date
                    mTimetabledate = mTables[0].getPublishedDate();

                    // Display the first table
                    mCurrentTable = mTables[0];
                    displayTimetable(mTables[0]);

                    // Let user view other tables if necessary
                    if (mTables.length > 1) {
                        HorizontalPicker pagePicker = (HorizontalPicker) findViewById(R.id.page);
                        pagePicker.setVisibility(View.VISIBLE);

                        // Initialize CharSequence[] for setting them as values
                        CharSequence[] values = new CharSequence[mTables.length];
                        for (int i = 0; i < mTables.length; i++) {
                            values[i] = String.valueOf(i + 1); // Because we can't assume users would start an array with 0
                        }

                        pagePicker.setValues(values);
                        pagePicker.setOnItemSelectedListener(new HorizontalPicker.OnItemSelected() {
                            @Override
                            public void onItemSelected(int index) {
                                displayTimetable(mTables[index]);
                            }
                        });

                        pagePicker.setOnItemClickedListener(new HorizontalPicker.OnItemClicked() {
                            @Override
                            public void onItemClicked(int index) {
                                displayTimetabledate(R.string.timetable_published, mTables[index].getPublishedDate());
                            }
                        });

                        colorNavbar();
                    }
                } catch (JSONException e) {
                    mTextView.setText(R.string.network_invalid_response);

                    e.printStackTrace();
                }

                return false;
            }
        });

        mDownloadManager.downloadTimetableList(mAuthToken, noContentHandler, onResponseHandler, mErrorListener);
    }

    private void displayTimetable(final Table table) {

        Handler fileAvailableHandler = new Handler(new Handler.Callback() {
            @Override
            public boolean handleMessage(Message msg) {
                displayTimetable(mFileManager.exists(Uri.parse(table.getUri()), table.getPublishedDate()), table.isHtml());

                return false;
            }
        });

        mFileManager.getFile(table, mDownloadManager, fileAvailableHandler, mErrorListener);

        /*if (mTable.isHtml()) {
            displayHtmlFile((HtmlTable) mTable);
        } else {
            displayImageTable((ImageTable) mTable);
        }*/
    }

    private void displayTimetable(File file, boolean isHtml) {
        if (isHtml) {
            displayHtmlFile(file);
        } else {
            displayImageFile(file);
        }
    }

    private void downloadHtmlTable(final Table table) {
        mCurrentTable = table;

        Handler onDownloadedHandler = new Handler(new Handler.Callback() {
            @Override
            public boolean handleMessage(Message msg) {
                // The file was downloaded
                displayHtmlFile(msg.getData().getString(DownloadManager.RESPONSE_HTML));

                return false;
            }
        });

        mDownloadManager.downloadHtmlTable(table, mFileManager, onDownloadedHandler, mErrorListener);
    }

    private void displayHtmlFile(File file) {
        displayHtmlFile(mFileManager.readHtmlFile(file));
    }

    private void displayHtmlFile(final String response) {
        // In case an image had been shown previously, hide it
        ImageView image = (ImageView) findViewById(R.id.tableimage);
        image.setVisibility(View.GONE);

        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT && mParse) {
            // Parse table



            new Thread(new Runnable() {
                @SuppressLint("NewApi")
                @Override
                public void run() {

                    Reader reader = u.getReader(HtmlTable.cleanupHtmlForParsing(response),  credentialsId);

                    final ArrayList<Entry> entries;
                    try {
                        entries = reader.filterUserFilters(reader.read(), MainActivity.this);
                        Log.d("ENTRIES", String.valueOf(entries.size()));

                        final ArrayAdapter adapter = new Adapter(MainActivity.this, entries);


                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                ((TextView) findViewById(R.id.date)).setText("");

                                ListView listView = findViewById(R.id.table);

                                if (entries.size() > 0) {
                                    listView.setVisibility(View.VISIBLE);
                                    mTextView.setVisibility(View.GONE);
                                    listView.setAdapter(adapter);

                                    final TextView dateView = findViewById(R.id.date);
                                    dateView.setVisibility(View.VISIBLE);

                                    listView.setOnScrollListener(new AbsListView.OnScrollListener() {
                                        @Override
                                        public void onScrollStateChanged(AbsListView view, int scrollState) {

                                        }

                                        @Override
                                        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
                                            try {
                                                dateView.setText(Utility.formatDate(entries.get(firstVisibleItem).getDate()));
                                            } catch (IndexOutOfBoundsException | NullPointerException e) {
                                                // does not matter because this happens when list is empty
                                            }

                                        }
                                    });
                                } else {
                                    mTextView.setText(R.string.empty);
                                    mTextView.setVisibility(View.VISIBLE);
                                    listView.setVisibility(View.GONE);
                                }


                            }
                        });

                    } catch (ParseException | NullPointerException e) {
                        e.printStackTrace();
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                mTextView.setText(getString(R.string.error_parsing));
                                mTextView.setVisibility(View.VISIBLE);
                            }
                        });
                    }
                }
            }).start();

            Reader reader = u.getReader(HtmlTable.cleanupHtmlForParsing(response), credentialsId);



        } else {
            ((TextView) findViewById(R.id.date)).setText("");

            // Render HTML code in WebView

            WebView webView = findViewById(R.id.webview);
            webView.setVisibility(View.VISIBLE);
            webView.getSettings().setJavaScriptEnabled(false);
            webView.loadData(HtmlTable.cleanupHtmlForRendering(response), "text/html", null);
        }

        potentiallyDisplayTimetabledate();
    }

    private void downloadImageTable(final Table table) {

        mCurrentTable = table;

        Handler onDownloadedHandler = new Handler(new Handler.Callback() {
            @Override
            public boolean handleMessage(Message msg) {
                // The file was downloaded, let file manager find it again
                displayTimetable(table);

                return false;
            }
        });

        mDownloadManager.downloadImageTable(table, mFileManager, onDownloadedHandler, mErrorListener);
    }

    private void displayImageFile(File file) {
        displayImageFile(mFileManager.readBitmapFile(file));
    }

    private void displayImageFile(Bitmap bitmap) {
        // Hide text
        mTextView.setVisibility(View.GONE);

        // Hide list and webView in case they had previously been displayed
        ListView listView = findViewById(R.id.table);
        listView.setVisibility(View.GONE);
        WebView webView = findViewById(R.id.webview);
        webView.setVisibility(View.GONE);

        // Show image
        ImageView image = (ImageView) findViewById(R.id.tableimage);
        image.setImageDrawable(new BitmapDrawable(bitmap));
        image.setVisibility(View.VISIBLE);

        potentiallyDisplayTimetabledate();

    }

    private void offlineMode() {

        // Change title to notify user offline mode is enabled
        getSupportActionBar().setTitle(R.string.app_name_offline);

        // Check checkbox
        if (mMenu != null) {
            mMenu.findItem(R.id.action_view_history).setChecked(true);
        }

        // Find every file
        final File[] files = mFileManager.listFiles();

        if (files.length == 0) {
            // We're done
            return;
        }

        // Sort files alphabetically descending
        Arrays.sort(files, new Comparator<File>() {
            @Override
            public int compare(File o1, File o2) {
                if (o1.lastModified() < o2.lastModified()) {
                    return 1;
                } else if (o1.lastModified() == o2.lastModified()) {
                    return 0;
                } else if (o1.lastModified() > o2.lastModified()) {
                    return -1;
                }
                // We should never get here but whatever
                return 0;
            }
        });

        // Read every file to a Table
        mTables = new Table[files.length];

        for (int i = 0; i < files.length; i++) {
            File file = files[i];

            String url = file.getName().replaceAll("\\d+-", "");

            Date date;
            // Who knows what files might be in our file system
            try {
                String time = file.getName().split("-")[0];
                date = new Date(Long.parseLong(time));
            } catch (NumberFormatException e) {
                e.printStackTrace();
                date = new Date();
            }

            boolean isHtml;
            try {
                String[] fileNameParts = file.getName().split("\\.");
                String suffix = fileNameParts[fileNameParts.length - 1];

                if (suffix.contains("htm")) {
                    isHtml = true;
                } else {
                    isHtml = false;
                }
            } catch (ArrayIndexOutOfBoundsException e) {
                // In this case, there was no dot in the filename

                isHtml = false;
            }

            mTables[i] = new Table(url, date, isHtml);
        }

        // Don't do it. It's dumb
        //mTimetabledate = mTables[0].getPublishedDate();

        // Display the first table
        HorizontalPicker pagePicker = (HorizontalPicker) findViewById(R.id.page);
        pagePicker.setSelectedItem(0);
        mCurrentTable = mTables[0];
        displayTimetable(files[0], mTables[0].isHtml());

        // Let user view other files
        if (mTables.length > 1) {
            pagePicker.setVisibility(View.VISIBLE);

            // Initialize CharSequence[] for setting them as values
            CharSequence[] values = new CharSequence[mTables.length];
            for (int i = 0; i < mTables.length; i++) {
                values[i] = Humanize.naturalTime(mTables[i].getPublishedDate());
            }

            pagePicker.setValues(values);
            pagePicker.setOnItemSelectedListener(new HorizontalPicker.OnItemSelected() {
                @Override
                public void onItemSelected(int index) {
                    displayTimetable(files[index], mTables[index].isHtml());
                }
            });

            pagePicker.setOnItemClickedListener(new HorizontalPicker.OnItemClicked() {
                @Override
                public void onItemClicked(int index) {
                    displayTimetabledate(R.string.timetable_published, mTables[index].getPublishedDate());
                }
            });

            colorNavbar();
        }
    }

    private void potentiallyDisplayTimetabledate() {
        // Only do this once
        if (!mTimetabledateDisplayed && mTimetabledate != null) {
            displayTimetabledate(R.string.timetable_last_changed, mTimetabledate);
            mTimetabledateDisplayed = true;
        }
    }

    private void displayTimetabledate(@StringRes int message, Date mTimetabledate) {
        String ago = Humanize.naturalTime(mTimetabledate, TimeMillis.HOUR);
        if (ago.length() <= 0) {
            ago = Humanize.naturalTime(mTimetabledate, TimeMillis.SECOND);
        }
        Log.d("TIMETABLEDATE", ago);
        Snackbar
                .make(findViewById(R.id.root),
                        getString(message, ago),
                        Snackbar.LENGTH_LONG)
                .show();
    }

    public static void setOverflowButtonColor(final Activity activity, final int color) {
        // from https://stackoverflow.com/a/36278375 by https://stackoverflow.com/users/5064289/barun-kumar
        final String overflowDescription = activity.getString(R.string.abc_action_menu_overflow_description);
        final ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView();
        final ViewTreeObserver viewTreeObserver = decorView.getViewTreeObserver();
        viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                final ArrayList<View> outViews = new ArrayList<View>();
                decorView.findViewsWithText(outViews, overflowDescription, View.FIND_VIEWS_WITH_CONTENT_DESCRIPTION);
                if (outViews.isEmpty()) {
                    return;
                }
                AppCompatImageView overflow = (AppCompatImageView) outViews.get(0);
                overflow.setColorFilter(color);
            }
        });
    }

    private void colorNavbar() {
        // Color Navbar in colorPrimaryDark
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            // as per https://stackoverflow.com/a/33507694
            Window window = getWindow();
            window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
            window.setNavigationBarColor(getResources().getColor(R.color.colorPrimaryDark));
        }
    }

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

        // Pass it around!
        mMenu = menu;

        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.action_reload:
                recreate();

                break;
            case R.id.action_view_history:
                if (item.isChecked()) {
                    recreate();
                } else {
                    item.setChecked(true);
                    offlineMode();
                }
                break;
            case R.id.action_settings:
                startActivityForResult(new Intent(MainActivity.this, SettingsActivity.class),
                        SettingsActivity.class.getName().length() // I needed a constant number, so I chose something simple & easy to remember
                );
                break;

            default:
                // If we got here, the user's action was not recognized.
                // Invoke the superclass to handle it.
                return super.onOptionsItemSelected(item);

        }
        return true;
    }

    private void showAuthPrompt() {
        new AlertDialog.Builder(MainActivity.this)
                .setTitle(R.string.credentials_popup_title)
                .setMessage(R.string.credentials_popup_message)
                .setNegativeButton(R.string.dismiss, null)
                .setPositiveButton(R.string.open, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        startActivity(new Intent(MainActivity.this, SettingsActivity.class));
                    }
                })
                .show();
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        int settingsActivityRequestCode = SettingsActivity.class.getName().length(); // A way to get a cool number
        if (requestCode == settingsActivityRequestCode && resultCode == RESULT_OK) {
            recreate(); // Because we can't really refresh very well
        }

        super.onActivityResult(requestCode, resultCode, data);
    }
}
