/*
 * Project:  NextGIS Mobile
 * Purpose:  Mobile GIS for Android.
 * Author:   Dmitry Baryshnikov (aka Bishop), [email protected]
 * Author:   NikitaFeodonit, [email protected]
 * Author:   Stanislav Petriakov, [email protected]
 * *****************************************************************************
 * Copyright (c) 2015-2017, 2019 NextGIS, [email protected]
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser 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 Lesser Public License for more details.
 *
 * You should have received a copy of the GNU Lesser Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.nextgis.maplib.datasource.ngw;

import android.accounts.Account;
import android.accounts.AccountManager;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.AbstractThreadedSyncAdapter;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SyncResult;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;

import com.nextgis.maplib.R;
import com.nextgis.maplib.api.IGISApplication;
import com.nextgis.maplib.api.ILayer;
import com.nextgis.maplib.api.INGWLayer;
import com.nextgis.maplib.map.LayerGroup;
import com.nextgis.maplib.map.MapBase;
import com.nextgis.maplib.map.MapContentProviderHelper;
import com.nextgis.maplib.map.TrackLayer;
import com.nextgis.maplib.util.Constants;
import com.nextgis.maplib.util.NGWUtil;
import com.nextgis.maplib.util.SettingsConstants;

import java.util.HashMap;

import static android.content.Context.MODE_MULTI_PROCESS;
import static com.nextgis.maplib.util.Constants.TAG;

/* useful links
https://udinic.wordpress.com/2013/07/24/write-your-own-android-sync-adapter/#more-507
http://www.fussylogic.co.uk/blog/?p=1031
http://www.fussylogic.co.uk/blog/?p=1035
http://www.fussylogic.co.uk/blog/?p=1037
http://developer.android.com/training/sync-adapters/creating-sync-adapter.html
https://github.com/elegion/ghsync
http://habrahabr.ru/company/e-Legion/blog/206210/
http://habrahabr.ru/company/e-Legion/blog/216857/
http://stackoverflow.com/questions/5486228/how-do-we-control-an-android-sync-adapter-preference
https://books.google.ru/books?id=SXlMAQAAQBAJ&pg=PA158&lpg=PA158&dq=android:syncAdapterSettingsAction&source=bl&ots=T832S7VvKb&sig=vgNNDHfwyMzvINeHfdfDhu9tREs&hl=ru&sa=X&ei=YviqVIPMF9DgaPOUgOgP&ved=0CFUQ6AEwBw#v=onepage&q=android%3AsyncAdapterSettingsAction&f=false
*/


public class SyncAdapter
        extends AbstractThreadedSyncAdapter
{
    public static final String SYNC_START    = "com.nextgis.maplib.sync_start";
    public static final String SYNC_FINISH   = "com.nextgis.maplib.sync_finish";
    public static final String SYNC_CANCELED = "com.nextgis.maplib.sync_canceled";
    public static final String SYNC_CHANGES  = "com.nextgis.maplib.sync_changes";

    public static final String EXCEPTION = "exception";
    protected String mError;

    private HashMap<String, Pair<Integer, Integer>> mVersions;

    public SyncAdapter(
            Context context,
            boolean autoInitialize)
    {
        super(context, autoInitialize);
    }


    public SyncAdapter(
            Context context,
            boolean autoInitialize,
            boolean allowParallelSyncs)
    {
        super(context, autoInitialize, allowParallelSyncs);
    }


    /**
     * Warning! When you stop the sync service by ContentResolver.cancelSync() then onPerformSync
     * stops after end of syncing of current NGWVectorLayer. The data structure of the current
     * NGWVectorLayer will be saved.
     * <p/>
     * <b>Description copied from class:</b> AbstractThreadedSyncAdapter Perform a sync for this
     * account. SyncAdapter-specific parameters may be specified in extras, which is guaranteed to
     * not be null. Invocations of this method are guaranteed to be serialized.
     */
    @Override
    public void onPerformSync(
            Account account,
            Bundle bundle,
            String authority,
            ContentProviderClient contentProviderClient,
            SyncResult syncResult)
    {
        Log.d(TAG, "onPerformSync");

        MapContentProviderHelper mapContentProviderHelper =(MapContentProviderHelper) MapBase.getInstance();
        getContext().sendBroadcast(new Intent(SYNC_START));

        mVersions = new HashMap<>();
        if (null != mapContentProviderHelper) {
            // FIXME Temporary fix till 3.0
//            mapContentProviderHelper.load(); // reload map for deleted/added layers
            sync(mapContentProviderHelper, authority, syncResult);
        }

        if (isCanceled()) {
            Log.d(Constants.TAG, "onPerformSync - SYNC_CANCELED is sent");
            getContext().sendBroadcast(new Intent(SYNC_CANCELED));
            return;
        }

        final String accountNameHash = "_" + account.name.hashCode();
        SharedPreferences settings = getContext().getSharedPreferences(Constants.PREFERENCES, MODE_MULTI_PROCESS);
        SharedPreferences.Editor editor = settings.edit();
        editor.putLong(SettingsConstants.KEY_PREF_LAST_SYNC_TIMESTAMP + accountNameHash, System.currentTimeMillis());
        editor.putLong(SettingsConstants.KEY_PREF_LAST_SYNC_TIMESTAMP, System.currentTimeMillis());
        editor.apply();

        mError = "";
        if (syncResult.stats.numIoExceptions > 0)
            mError += getContext().getString(R.string.sync_error_io);
        if (syncResult.stats.numParseExceptions > 0) {
            if (mError.length() > 0)
                mError += "\r\n";
            mError += getContext().getString(R.string.sync_error_parse);
        }
        if (syncResult.stats.numAuthExceptions > 0) {
            if (mError.length() > 0)
                mError += "\r\n";
            mError += getContext().getString(R.string.error_auth);
        }
        if (syncResult.stats.numConflictDetectedExceptions > 0) {
            if (mError.length() > 0)
                mError += "\r\n";
            mError += getContext().getString(R.string.sync_error_conflict);
        }
        if (syncResult.stats.numInserts > 0) {
            if (mError.length() > 0)
                mError += "\r\n";
            mError += getContext().getString(R.string.sync_error_insert);
        }
        if (syncResult.stats.numUpdates > 0) {
            if (mError.length() > 0)
                mError += "\r\n";
            mError += getContext().getString(R.string.sync_error_change);
        }
        if (syncResult.stats.numDeletes > 0) {
            if (mError.length() > 0)
                mError += "\r\n";
            mError += getContext().getString(R.string.sync_error_delete);
        }
        if (syncResult.stats.numEntries > 0) {
            if (mError.length() > 0)
                mError += "\r\n";
            mError += getContext().getString(R.string.sync_error_server);
        }
        if (syncResult.stats.numSkippedEntries > 0) {
            if (mError.length() > 0)
                mError += "\r\n";
            mError += getContext().getString(R.string.sync_error_oom);
        }

        Intent finish = new Intent(SYNC_FINISH);
        if (!TextUtils.isEmpty(mError))
            finish.putExtra(EXCEPTION, mError);
        getContext().sendBroadcast(finish);
    }


    protected void sync(
            LayerGroup layerGroup,
            String authority,
            SyncResult syncResult)
    {
        for (int i = 0; i < layerGroup.getLayerCount(); i++) {
            if (isCanceled()) {
                return;
            }
            ILayer layer = layerGroup.getLayer(i);
            if (layer instanceof LayerGroup) {
                sync((LayerGroup) layer, authority, syncResult);
            } else if (layer instanceof INGWLayer) {
                INGWLayer ngwLayer = (INGWLayer) layer;
                String accountName = ngwLayer.getAccountName();
                if (!mVersions.containsKey(accountName))
                    mVersions.put(accountName, NGWUtil.getNgwVersion(getContext(), accountName));

                Pair<Integer, Integer> ver = mVersions.get(accountName);
                ngwLayer.sync(authority, ver, syncResult);
            } else if (layer instanceof TrackLayer) {
                ((TrackLayer) layer).sync();
            }
        }
    }

    @SuppressLint("MissingPermission")
    public static void setSyncPeriod(
            IGISApplication application,
            Bundle extras,
            long pollFrequency)
    {
        Context context = ((Context) application).getApplicationContext();
        final AccountManager accountManager = AccountManager.get(context);
        Log.d(TAG, "SyncAdapter: AccountManager.get(" + context + ")");

        for (Account account : accountManager.getAccountsByType(application.getAccountsType())) {
            ContentResolver.addPeriodicSync(account, application.getAuthority(), extras, pollFrequency);
        }
    }


    public boolean isCanceled()
    {
        return Thread.currentThread().isInterrupted();
    }
}