package com.sovworks.eds.android.filemanager.fragments;


import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;

import com.sovworks.eds.android.Logger;
import com.sovworks.eds.android.filemanager.DirectorySettings;
import com.sovworks.eds.android.filemanager.FileListViewAdapter;
import com.sovworks.eds.android.filemanager.activities.FileManagerActivity;
import com.sovworks.eds.android.filemanager.comparators.FileNamesComparator;
import com.sovworks.eds.android.filemanager.comparators.FileNamesNumericComparator;
import com.sovworks.eds.android.filemanager.comparators.FileSizesComparator;
import com.sovworks.eds.android.filemanager.comparators.ModDateComparator;
import com.sovworks.eds.android.filemanager.records.BrowserRecord;
import com.sovworks.eds.android.filemanager.tasks.CreateNewFile;
import com.sovworks.eds.android.filemanager.tasks.LoadDirSettingsObservable;
import com.sovworks.eds.android.filemanager.tasks.ReadDir;
import com.sovworks.eds.android.helpers.CachedPathInfo;
import com.sovworks.eds.android.helpers.CachedPathInfoBase;
import com.sovworks.eds.android.helpers.ExtendedFileInfoLoader;
import com.sovworks.eds.android.locations.activities.OpenLocationsActivity;
import com.sovworks.eds.android.settings.UserSettings;
import com.sovworks.eds.fs.Path;
import com.sovworks.eds.locations.Location;
import com.sovworks.eds.locations.LocationsManager;
import com.sovworks.eds.settings.GlobalConfig;
import com.sovworks.eds.settings.Settings;
import com.trello.rxlifecycle2.android.FragmentEvent;
import com.trello.rxlifecycle2.components.RxFragment;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.NavigableSet;
import java.util.Stack;
import java.util.TreeSet;
import java.util.concurrent.CancellationException;

import io.reactivex.Observable;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.subjects.BehaviorSubject;
import io.reactivex.subjects.PublishSubject;
import io.reactivex.subjects.Subject;

import static com.sovworks.eds.settings.SettingsCommon.FB_SORT_DATE_ASC;
import static com.sovworks.eds.settings.SettingsCommon.FB_SORT_DATE_DESC;
import static com.sovworks.eds.settings.SettingsCommon.FB_SORT_FILENAME_ASC;
import static com.sovworks.eds.settings.SettingsCommon.FB_SORT_FILENAME_DESC;
import static com.sovworks.eds.settings.SettingsCommon.FB_SORT_FILENAME_NUM_ASC;
import static com.sovworks.eds.settings.SettingsCommon.FB_SORT_FILENAME_NUM_DESC;
import static com.sovworks.eds.settings.SettingsCommon.FB_SORT_SIZE_ASC;
import static com.sovworks.eds.settings.SettingsCommon.FB_SORT_SIZE_DESC;

public class FileListDataFragment extends RxFragment
{
    public static FileListDataFragment newInstance()
    {
        return new FileListDataFragment();
    }

    static
    {
        if(GlobalConfig.isTest())
            TEST_READING_OBSERVABLE = BehaviorSubject.createDefault(false);

    }

    public static Subject<Boolean> TEST_READING_OBSERVABLE;

    public static <T extends CachedPathInfo> Comparator<T> getComparator(Settings settings)
    {
        switch (settings.getFilesSortMode())
        {
            case FB_SORT_FILENAME_ASC:
                return new FileNamesComparator<>(true);
            case FB_SORT_FILENAME_DESC:
                return new FileNamesComparator<>(false);
            case FB_SORT_FILENAME_NUM_ASC:
                return new FileNamesNumericComparator<>(true);
            case FB_SORT_FILENAME_NUM_DESC:
                return new FileNamesNumericComparator<>(false);
            case FB_SORT_SIZE_ASC:
                return new FileSizesComparator<>(true);
            case FB_SORT_SIZE_DESC:
                return new FileSizesComparator<>(false);
            case FB_SORT_DATE_ASC:
                return new ModDateComparator<>(true);
            case FB_SORT_DATE_DESC:
                return new ModDateComparator<>(false);
            default:
                return null;
        }
    }

    public static Uri getLocationUri(Intent intent, Bundle state)
    {
        Uri locUri;
        if(state!=null)
            locUri = state.getParcelable(LocationsManager.PARAM_LOCATION_URI);
        else
            locUri = intent.getData();
        return locUri;
    }

    public static final String TAG = "com.sovworks.eds.android.filemanager.fragments.FileListDataFragment";

    @Override
    public void onCreate(Bundle state)
    {
        super.onCreate(state);
        setRetainInstance(true);
        _location = getFallbackLocation();
        _locationsManager = LocationsManager.getLocationsManager(getActivity());
        _fileList = new TreeSet<>(initSorter());
        loadLocation(state, true);
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState)
    {
        super.onActivityCreated(savedInstanceState);
        //TODO remove dependency
        synchronized (_filesListSync)
        {
            if(_fileList!=null)
                for(BrowserRecord br: _fileList)
                    br.setHostActivity((FileManagerActivity) getActivity());
        }
    }


    @Override
	public void onActivityResult (int requestCode, int resultCode, Intent data)
	{
		if(requestCode == REQUEST_CODE_OPEN_LOCATION)
        {
            if(resultCode != Activity.RESULT_OK)
                getActivity().setIntent(new Intent());
            lifecycle().
                    filter(event -> event == FragmentEvent.RESUME).
                    firstElement().
                    subscribe(isResumed ->
                            loadLocation(null, false),
                            err ->
                    {
                        if(!(err instanceof CancellationException))
                            Logger.log(err);
                    });

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

    @Override
    public void onDetach ()
    {
        super.onDetach();
        //TODO remove dependency
        synchronized (_filesListSync)
        {
            if(_fileList!=null)
                for(BrowserRecord br: _fileList)
                    br.setHostActivity(null);
        }
    }

    @Override
    public void onDestroy()
    {
        cancelReadDirTask();
        _navigHistory.clear();
        synchronized (_filesListSync)
        {
            if(_fileList!=null)
            {
                _fileList.clear();
                _fileList = null;
            }
        }
        _locationsManager = null;
        super.onDestroy();
    }

    @Override
    public void onSaveInstanceState(Bundle state)
    {
        super.onSaveInstanceState(state);
        if(_location!=null)
        {
            ArrayList<Path> selectedPaths = getSelectedPaths();
            LocationsManager.storePathsInBundle(state, _location, selectedPaths);
        }
        state.putParcelableArrayList(STATE_NAVIG_HISTORY, new ArrayList<>(_navigHistory));
    }

    public ArrayList<BrowserRecord> getSelectedFiles()
    {
        ArrayList<BrowserRecord> res = new ArrayList<>();
        synchronized (_filesListSync)
        {
            if(_fileList!=null)
                for (BrowserRecord rec: _fileList)
                {
                    if (rec.isSelected())
                        res.add(rec);
                }
        }
        return res;
    }

    public boolean hasSelectedFiles()
    {
        synchronized (_filesListSync)
        {
            if(_fileList!=null)
                for (BrowserRecord rec: _fileList)
                {
                    if (rec.isSelected())
                        return true;
                }
        }
        return false;
    }

    public BrowserRecord findLoadedFileByPath(Path path)
    {
        synchronized (_filesListSync)
        {
        	if(_fileList!=null)
	            for(BrowserRecord f: _fileList)
		            if(path.equals(f.getPath()))
		                return f;        
        }
        return null;
    }

    public ArrayList<Path> getSelectedPaths()
    {
        return FileListViewFragment.getPathsFromRecords(getSelectedFiles());
    }

    public NavigableSet<BrowserRecord> getFileList()
    {
        return _fileList;
    }

    public Object getFilesListSync()
    {
        return _filesListSync;
    }

    public Location getLocation()
    {
        return _location;
    }

    public void copyToAdapter(FileListViewAdapter adapter)
    {
        synchronized (_filesListSync)
        {
            adapter.clear();
            if(_fileList!=null)
                adapter.addAll(_fileList);
        }
    }

    public Observable<LoadLocationInfo> getLocationLoadingObservable()
    {
        return _locationLoading;
    }

    public Observable<BrowserRecord> getLoadRecordObservable()
    {
        return _recordLoadedSubject;
    }

    static class LoadLocationInfo implements Cloneable
    {
        enum Stage
        {
            StartedLoading,
            Loading,
            FinishedLoading
        }
        Stage stage;
        CachedPathInfo folder;
        BrowserRecord file;
        DirectorySettings folderSettings;
        Location location;

        @Override
        public LoadLocationInfo clone()
        {
            try
            {
                return (LoadLocationInfo) super.clone();
            }
            catch (CloneNotSupportedException ignore)
            {
                return null;
            }
        }
    }

    public synchronized void readLocation(Location location, Collection<Path> selectedFiles)
    {
        Logger.debug(TAG + " readCurrentLocation");
        cancelReadDirTask();
        clearCurrentFiles();
        _location = location;
        if (_location == null)
            return;

        FileManagerActivity activity = (FileManagerActivity) getActivity();
        if(activity == null)
            return;
        Context context = activity.getApplicationContext();
        boolean showRootFolder = activity.getIntent().
                getBooleanExtra(
                        FileManagerActivity.EXTRA_ALLOW_SELECT_ROOT_FOLDER,
                        activity.isSelectAction() && activity.allowFolderSelect()
                );
        LoadLocationInfo startInfo = new LoadLocationInfo();
        startInfo.stage = LoadLocationInfo.Stage.StartedLoading;
        startInfo.location = location;
        Logger.debug(TAG + ": _locationLoading.onNext started loading");
        _locationLoading.onNext(startInfo);
        Observable<BrowserRecord> observable = LoadDirSettingsObservable.
                create(location).
                toSingle(new DirectorySettings()).
                onErrorReturn(err -> {
                    Logger.log(err);
                    return new DirectorySettings();
                }).
                map(dirSettings -> {
                    LoadLocationInfo loadLocationInfo = new LoadLocationInfo();
                    loadLocationInfo.stage = LoadLocationInfo.Stage.Loading;
                    loadLocationInfo.folderSettings = dirSettings;
                    if(location.getCurrentPath().isFile())
                    {
                        loadLocationInfo.file = ReadDir.getBrowserRecordFromFsRecord(
                                context,
                                location,
                                location.getCurrentPath(),
                                dirSettings
                        );
                        Location parentLocation = location.copy();
                        parentLocation.setCurrentPath(location.getCurrentPath().getParentPath());
                        loadLocationInfo.location = parentLocation;
                    }
                    else
                        loadLocationInfo.location = location;
                    CachedPathInfo cpi = new CachedPathInfoBase();
                    cpi.init(loadLocationInfo.location.getCurrentPath());
                    loadLocationInfo.folder = cpi;
                    return loadLocationInfo;
                }).
                observeOn(AndroidSchedulers.mainThread()).
                doOnSuccess(loadLocationInfo -> {
                    _directorySettings = loadLocationInfo.folderSettings;
                    Logger.debug(TAG + ": _locationLoading.onNext loading");
                    _locationLoading.onNext(loadLocationInfo);
                }).
                observeOn(Schedulers.io()).
                flatMapObservable(loadLocationInfo -> ReadDir.createObservable(
                    context,
                    loadLocationInfo.location,
                    selectedFiles,
                    loadLocationInfo.folderSettings,
                    showRootFolder

                ));
        if(TEST_READING_OBSERVABLE != null)
        {
            observable = observable.
                    doOnSubscribe(res -> TEST_READING_OBSERVABLE.onNext(true)).
                    doFinally(() -> TEST_READING_OBSERVABLE.onNext(false));
        }

        _readLocationObserver = observable.
                compose(bindUntilEvent(FragmentEvent.DESTROY)).
                subscribeOn(Schedulers.io()).
                observeOn(AndroidSchedulers.mainThread()).
                doFinally(() -> sendFinishedLoading(location)).
                subscribe(loadedRecord -> {
                            addRecordToList(loadedRecord);
                            _recordLoadedSubject.onNext(loadedRecord);
                        },
                        err ->
                        {
                            if(!(err instanceof CancellationException))
                                Logger.log(err);
                        }
                );

    }

    private void sendFinishedLoading(Location location)
    {
        Logger.debug(TAG + ": _locationLoading.onNext isLoading = false");
        LoadLocationInfo loadLocationInfo = new LoadLocationInfo();
        loadLocationInfo.location = location;
        loadLocationInfo.stage = LoadLocationInfo.Stage.FinishedLoading;
        _locationLoading.onNext(loadLocationInfo);
    }

    public Single<BrowserRecord> makeNewFile(String name, int type)
    {
        return Single.create(emitter -> CreateNewFile.createObservable(
                getActivity().getApplicationContext(),
                getLocation(),
                name,
                type,
                false
        ).compose(bindToLifecycle()).
                subscribeOn(Schedulers.io()).
                observeOn(AndroidSchedulers.mainThread()).
                subscribe(rec -> {
                            addRecordToList(rec);
                            if(!emitter.isDisposed())
                                emitter.onSuccess(rec);
                        },
                        err -> Logger.showAndLog(getActivity(), err)));
    }

    public Single<BrowserRecord> createOrFindFile(String name, int type)
    {
        return Single.create(emitter -> CreateNewFile.createObservable(
                getActivity().getApplicationContext(),
                getLocation(),
                name,
                type,
                true
        ).compose(bindToLifecycle()).
                subscribeOn(Schedulers.io()).
                observeOn(AndroidSchedulers.mainThread()).
                subscribe(rec -> {
                            if(findLoadedFileByPath(rec.getPath()) == null)
                                addRecordToList(rec);
                            if(!emitter.isDisposed())
                                emitter.onSuccess(rec);
                        },
                        err -> Logger.showAndLog(getActivity(), err)));

    }

    private void addRecordToList(BrowserRecord rec)
    {
        FileManagerActivity fm = (FileManagerActivity) getActivity();
        rec.setHostActivity(fm);
        synchronized (_filesListSync)
        {
            if(_fileList!=null)
                _fileList.add(rec);
        }
    }

    public void reSortFiles()
    {
        LoadLocationInfo loadInfo = new LoadLocationInfo();
        loadInfo.stage = LoadLocationInfo.Stage.StartedLoading;
        loadInfo.location = _location;
        Logger.debug(TAG + ": _locationLoading.onNext started loading (sorting)");
        _locationLoading.onNext(loadInfo);
        synchronized (_filesListSync)
        {
            TreeSet<BrowserRecord> n = new TreeSet<>(initSorter());
            if (_fileList != null)
                n.addAll(_fileList);
            _fileList = n;
        }
        loadInfo = loadInfo.clone();
        loadInfo.stage = LoadLocationInfo.Stage.FinishedLoading;
        Logger.debug(TAG + ": _locationLoading.onNext finished loading (sorting)");
        _locationLoading.onNext(loadInfo);
    }

    public Stack<HistoryItem> getNavigHistory()
    {
        return _navigHistory;
    }

    public void removeLocationFromHistory(Location loc)
    {
        String id = loc.getId();
        if(id!=null)
        {
            List<HistoryItem> cur = new ArrayList<>(_navigHistory);
            for(HistoryItem hi: cur)
                if(id.equals(hi.locationId))
                    _navigHistory.remove(hi);
        }
    }

    public DirectorySettings getDirectorySettings()
    {
        return _directorySettings;
    }

    public static class HistoryItem implements Parcelable
    {
        public static final Creator<HistoryItem> CREATOR = new Creator<HistoryItem>()
        {
            @Override
            public HistoryItem createFromParcel(Parcel in)
            {
                return new HistoryItem(in);
            }

            @Override
            public HistoryItem[] newArray(int size)
            {
                return new HistoryItem[size];
            }
        };

        @Override
        public int describeContents()
        {
            return 0;
        }

        @Override
        public void writeToParcel(Parcel parcel, int flags)
        {
            parcel.writeParcelable(locationUri, flags);
            parcel.writeInt(scrollPosition);
            parcel.writeString(locationId);
        }

        HistoryItem(){}

        public Uri locationUri;
        public int scrollPosition;
        public String locationId;

        HistoryItem(Parcel p)
        {
            locationUri = p.readParcelable(ClassLoader.getSystemClassLoader());
            scrollPosition = p.readInt();
            locationId = p.readString();
        }
    }

    private static final String STATE_NAVIG_HISTORY = "com.sovworks.eds.android.PATH_HISTORY";

    private static final int REQUEST_CODE_OPEN_LOCATION = 1;

    private LocationsManager _locationsManager;
    private Location _location;
    private DirectorySettings _directorySettings;
    private NavigableSet<BrowserRecord> _fileList;
    private final Object _filesListSync = new Object();
    private final Stack<HistoryItem> _navigHistory = new Stack<>();
    private final Subject<LoadLocationInfo> _locationLoading = BehaviorSubject.create();
    private final Subject<BrowserRecord> _recordLoadedSubject = PublishSubject.create();
    private Disposable _readLocationObserver;

    private synchronized void cancelReadDirTask()
    {
        if(_readLocationObserver != null)
        {
            _readLocationObserver.dispose();
            _readLocationObserver = null;
        }
    }

    private void restoreNavigHistory(Bundle state)
	{
        if (state.containsKey(STATE_NAVIG_HISTORY))
        {
            ArrayList<HistoryItem> l = state.getParcelableArrayList(STATE_NAVIG_HISTORY);
            if(l!=null)
                _navigHistory.addAll(l);
        }
	}

    public void loadLocation(final Bundle savedState, final boolean autoOpen)
	{
		final Uri uri = getLocationUri(getActivity().getIntent(), savedState);
        Location loc = null;
		try
		{
			loc = initLocationFromUri(uri);
		}
		catch(Exception e)
		{
			Logger.showAndLog(getActivity(), e);
		}
		if(loc == null)
            loc = getFallbackLocation();

        if(autoOpen && !LocationsManager.isOpen(loc))
		{
            Intent i = new Intent(getActivity(), OpenLocationsActivity.class);
            LocationsManager.storeLocationsInIntent(i, Collections.singletonList(loc));
            startActivityForResult(i, REQUEST_CODE_OPEN_LOCATION);
		}
		else if(savedState == null)
        {
            resetIntent();
            readLocation(loc, null);
        }
        else
            restoreState(savedState);
	}

    private void resetIntent()
    {
        Intent i = getActivity().getIntent();
        if(i.getAction() == null || Intent.ACTION_MAIN.equals(i.getAction()))
        {
            i.setData(null);
            getActivity().setIntent(i);
        }
    }

    private void clearCurrentFiles()
    {
        synchronized (_filesListSync)
        {
            if(_location!=null)
            {
                ExtendedFileInfoLoader loader = ExtendedFileInfoLoader.getInstance();
                for (BrowserRecord br: _fileList)
                    loader.detachRecord(_location.getId(), br);
            }
            _fileList.clear();
        }
        _directorySettings = null;
        _location = null;
    }

    private void restoreState(Bundle state)
    {
        restoreNavigHistory(state);
        ArrayList<Path> selectedFiles = new ArrayList<>();
        Location loc = _locationsManager.getFromBundle(state, selectedFiles);
        if(loc!=null)
            readLocation(loc, selectedFiles);
    }

    private Location initLocationFromUri(Uri locationUri) throws Exception
	{
		return locationUri != null ?
				_locationsManager.getLocation(locationUri)
			:
				null;
	}

	private Location getFallbackLocation()
	{
		return FileManagerActivity.getStartLocation(getActivity());
	}

    private Comparator<BrowserRecord> initSorter()
	{
        return getComparator(UserSettings.getSettings(getActivity()));
	}
}