package com.supermap.interfaces.collector;

import android.net.Uri;
import android.view.MotionEvent;
import android.view.View;

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.supermap.RNUtils.DateUtil;
import com.supermap.RNUtils.MediaUtil;
import com.supermap.component.MapWrapView;
import com.supermap.containts.EventConst;
import com.supermap.data.CoordSysTransMethod;
import com.supermap.data.CoordSysTransParameter;
import com.supermap.data.CoordSysTranslator;
import com.supermap.data.CursorType;
import com.supermap.data.DatasetVector;
import com.supermap.data.DatasetVectorInfo;
import com.supermap.data.Datasource;
import com.supermap.data.Geometry;
import com.supermap.data.Point2D;
import com.supermap.data.Point2Ds;
import com.supermap.data.PrjCoordSys;
import com.supermap.data.PrjCoordSysType;
import com.supermap.data.QueryParameter;
import com.supermap.data.Recordset;
import com.supermap.interfaces.mapping.SMap;
import com.supermap.interfaces.utils.SMFileUtil;
import com.supermap.mapping.CallOut;
import com.supermap.mapping.Layer;
import com.supermap.mapping.Map;
import com.supermap.mapping.MapControl;
import com.supermap.smNative.SMLayer;
import com.supermap.smNative.SMMapWC;
import com.supermap.smNative.collector.SMMedia;
import com.supermap.smNative.collector.SMMediaCollector;
import com.supermap.smNative.components.InfoCallout;

import org.apache.http.cookie.SM;

import java.text.DateFormat;
import java.text.FieldPosition;
import java.text.ParsePosition;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * @Author: shanglongyang
 * Date:        2019/5/17
 * project:     iTablet
 * package:     iTablet
 * class:
 * description:
 */
public class SMediaCollector extends ReactContextBaseJavaModule {
    public static final String REACT_CLASS = "SMediaCollector";
    private static SMediaCollector collector = null;
    private static ReactApplicationContext context;
    private static Layer mediaLayer;
    private static View.OnTouchListener calloutListner;

    public SMediaCollector(ReactApplicationContext context) {
        super(context);
        this.context = context;
    }

    @Override
    public String getName() {
        return REACT_CLASS;
    }

    public static View.OnTouchListener getCalloutListner() {
        if (calloutListner == null) {
            calloutListner = new View.OnTouchListener() {
                @Override
                public boolean onTouch(View view, MotionEvent motionEvent) {
                    switch (motionEvent.getAction()) {
                        case MotionEvent.ACTION_UP: {
                            InfoCallout infoCallout = (InfoCallout)view;

                            WritableMap data = getCalloutData(infoCallout);

                            context.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                                    .emit(EventConst.MEDIA_CAPTURE_TAP_ACTION, data);
                            break;
                        }
                    }

                    return true;
                }
            };
        }
        return calloutListner;
    }

    public static WritableMap getCalloutData(InfoCallout infoCallout) {
        Point2D pt = new Point2D(infoCallout.getLocationX(), infoCallout.getLocationY());
        return getCalloutData(infoCallout.getID(), infoCallout.getLayerName(), infoCallout.getGeoID(), pt);
    }

    public static WritableMap getCalloutData(String id, String layerName, int geoID, Point2D pt) {
        Map map = SMap.getInstance().getSmMapWC().getMapControl().getMap();

        WritableArray medium = Arguments.createArray();

        Layer tapLayer = SMLayer.findLayerWithName(layerName);
        DatasetVector dv = (DatasetVector) tapLayer.getDataset();

        QueryParameter qp = new QueryParameter();
        qp.setAttributeFilter("SmID=" + geoID);
        qp.setCursorType(CursorType.STATIC);
        Recordset recordset = dv.query(qp);

//        if (pt == null) {
              pt = recordset.getGeometry().getInnerPoint();
//        }
        if (!SMap.safeGetType(recordset.getDataset().getPrjCoordSys(), PrjCoordSysType.PCS_EARTH_LONGITUDE_LATITUDE)) {
            Point2Ds points = new Point2Ds();
            points.add(pt);
            PrjCoordSys desPrjCoorSys = new PrjCoordSys();
            desPrjCoorSys.setType(PrjCoordSysType.PCS_EARTH_LONGITUDE_LATITUDE);
            CoordSysTranslator.convert(points, recordset.getDataset().getPrjCoordSys(), desPrjCoorSys,
                    new CoordSysTransParameter(),
                    CoordSysTransMethod.MTH_GEOCENTRIC_TRANSLATION);

            pt.setX(points.getItem(0).getX());
            pt.setY(points.getItem(0).getY());
        }

        WritableMap point = Arguments.createMap();
        point.putDouble("x", pt.getX());
        point.putDouble("y", pt.getY());

        String modifiedDate = recordset.getString("ModifiedDate");
        String mediaName = recordset.getString("MediaName");
        String httpAddress = recordset.getString("HttpAddress");
        String description = recordset.getString("Description");

        String mediaFilePaths = recordset.getString("MediaFilePaths");
        WritableArray paths = Arguments.createArray();
        String[] pathArr = mediaFilePaths != null && !mediaFilePaths.equals("") ? mediaFilePaths.split(",") : new String[0];
        for (String path : pathArr) {
//                                if (path.indexOf("file://") != 0) {
//                                    path = "file://" + path;
//                                }
            paths.pushString(path);
        }

        recordset.dispose();

        WritableMap data = Arguments.createMap();
        data.putString("id", id);
        data.putMap("coordinate", point);
        data.putString("layerName", layerName);
        data.putInt("geoID", geoID);
        data.putArray("medium", medium);
        data.putString("modifiedDate", modifiedDate);
        data.putString("mediaName", mediaName);
        data.putArray("mediaFilePaths", paths);
        data.putString("httpAddress", httpAddress);
        data.putString("description", description);

        return data;
    }

    @ReactMethod
    public void initMediaCollector(String path, Promise promise) {
        try {
            SMMediaCollector collector = SMMediaCollector.getInstance();
            collector.setMediaPath(path);

            promise.resolve(true);
        } catch (Exception e) {
            promise.reject(e);
        }
    }

    /**
     * 把拍摄后的结果添加到数据集中
     * @param info
     * @param addToMap
     * @param promise
     */
    @ReactMethod
    public void addMedia(ReadableMap info, boolean addToMap, Promise promise) {
        try {
            boolean result = false;
            SMMediaCollector collector = SMMediaCollector.getInstance();

            if (collector == null || collector.getMediaPath().equals("")) {
                promise.resolve(new Error("MediaCollector should be initialized"));
            } else {
                MapControl mapControl = SMap.getInstance().getSmMapWC().getMapControl();
                String datasourceName = info.getString("datasourceName");
                String datasetName = info.getString("datasetName");
                ReadableArray sourcePaths = info.getArray("mediaPaths");

                String mediaName = new Date().getTime() + "";
                if (sourcePaths.size() > 0 && sourcePaths.getString(0).lastIndexOf(".") > -1 && sourcePaths.getString(0).lastIndexOf("/") > -1) {
                    mediaName = sourcePaths.getString(0).substring(
                            sourcePaths.getString(0).lastIndexOf("/") + 1,
                            sourcePaths.getString(0).lastIndexOf(".")
                            );
                }
                SMMedia media = new SMMedia(mediaName);

                Datasource ds = mapControl.getMap().getWorkspace().getDatasources().get(datasourceName);
                if (media.setMediaDataset(ds, datasetName)) {
                    if (addToMap) {
                        mediaLayer = SMLayer.findLayerByDatasetName(datasetName);

                        if (mediaLayer == null) {
                            mediaLayer = SMLayer.addLayerByName(datasourceName, datasetName);
                        }
                        ArrayList<String> paths = new ArrayList<>();
                        for (int i = 0; i < sourcePaths.size(); i++) {
                            paths.add(sourcePaths.getString(i));
                        }
                        result = media.saveMedia(this.getReactApplicationContext(), paths, collector.getMediaPath(), true);

                        if (result) addCallout(media, mediaLayer);
                    }
                }
                promise.resolve(result);
            }

        } catch (Exception e) {
            promise.reject(e);
        }
    }

    /**
     * 把AI识别的结果添加到数据集中(图片直接保存在Media文件夹中)
     * @param info
     * @param addToMap
     * @param promise
     */
    @ReactMethod
    public void addArMedia(ReadableMap info, boolean addToMap, Promise promise) {
        try {
            boolean result = false;
            SMMediaCollector collector = SMMediaCollector.getInstance();

            if (collector == null || collector.getMediaPath().equals("")) {
                promise.resolve(new Error("MediaCollector should be initialized"));
            } else {
                MapControl mapControl = SMap.getInstance().getSmMapWC().getMapControl();
                String datasourceName = info.getString("datasourceName");
                String datasetName = info.getString("datasetName");
                String mediaName = info.getString("mediaName");

                SMMedia media = new SMMedia(mediaName);

                Datasource ds = mapControl.getMap().getWorkspace().getDatasources().get(datasourceName);
                if (media.setMediaDataset(ds, datasetName)) {
                    if (addToMap) {
                        mediaLayer = SMLayer.findLayerByDatasetName(datasetName);

                        if (mediaLayer == null) {
                            mediaLayer = SMLayer.addLayerByName(datasourceName, datasetName);
                        }
                        result = media.saveArMedia(mediaName, collector.getMediaPath(), true);

                        if (result) addCallout(media, mediaLayer);
                    }
                }
                promise.resolve(result);
            }

        } catch (Exception e) {
            promise.reject(e);
        }
    }

    /**
     * 把AI分类的结果添加到数据集中(图片直接保存在Media文件夹中)
     * @param info
     * @param addToMap
     * @param promise
     */
    @ReactMethod
    public void addAIClassifyMedia(ReadableMap info, boolean addToMap, Promise promise) {
        try {
            boolean result = false;
            SMMediaCollector collector = SMMediaCollector.getInstance();

            if (collector == null || collector.getMediaPath().equals("")) {
                promise.resolve(new Error("MediaCollector should be initialized"));
            } else {
                MapControl mapControl = SMap.getInstance().getSmMapWC().getMapControl();
                String datasourceName = info.getString("datasourceName");
                String datasetName = info.getString("datasetName");
                String mediaName = info.getString("mediaName");

                SMMedia media = new SMMedia(mediaName);

                Datasource ds = mapControl.getMap().getWorkspace().getDatasources().get(datasourceName);
                if (media.setMediaDataset(ds, datasetName)) {
                    if (addToMap) {
                        mediaLayer = SMLayer.findLayerByDatasetName(datasetName);

                        if (mediaLayer == null) {
                            mediaLayer = SMLayer.addLayerByName(datasourceName, datasetName);
                        }
                        result = media.saveAIClassifyMedia(mediaName, collector.getMediaPath(), true);

                        if (result) addCallout(media, mediaLayer);
                    }
                }
                promise.resolve(result);
            }

        } catch (Exception e) {
            promise.reject(e);
        }
    }

    @ReactMethod
    public void saveMediaByLayer(String layerName, int geoID, String toPath, ReadableArray fieldInfos, boolean addToMap, Promise promise) {
        try {
            Layer layer = SMLayer.findLayerWithName(layerName);

            boolean saveResult = saveMedia(layer, geoID, toPath, fieldInfos, addToMap);

            promise.resolve(saveResult);
        } catch (Exception e) {
            promise.reject(e);
        }
    }

    @ReactMethod
    public void saveMediaByDataset(String datasetName, int geoID, String toPath, ReadableArray fieldInfos, boolean addToMap, Promise promise) {
        try {
            Layer layer = SMLayer.findLayerByDatasetName(datasetName);
            if (layer == null) {
                layer = SMLayer.findLayerWithName(datasetName);
            }

            boolean saveResult = saveMedia(layer, geoID, toPath, fieldInfos, addToMap);

            promise.resolve(saveResult);
        } catch (Exception e) {
            promise.reject(e);
        }
    }

    @ReactMethod
    public void updateMedia(String layerName, ReadableArray geoIDs, Promise promise) {
        try {
            MapWrapView mapWrapView = (MapWrapView)SMap.getInstance().getSmMapWC().getMapControl().getMap().getMapView();
            Layer layer = SMLayer.findLayerWithName(layerName);

            int[] ids = new int[geoIDs.size()];
            for (int i = 0; i < geoIDs.size(); i++) {
                ids[i] = geoIDs.getInt(i);
            }
            Recordset recordset = ((DatasetVector)layer.getDataset()).query(ids, CursorType.STATIC);

            ArrayList<InfoCallout> list = new ArrayList<>();
            while (!recordset.isEOF()) {
                Geometry geometry = recordset.getGeometry();
                Point2D point2D = geometry.getInnerPoint();
                InfoCallout callout = (InfoCallout) mapWrapView.getCallOut(layerName + "-" + recordset.getID());
                callout.setLocation(point2D.getX(), point2D.getY());
                list.add(0, callout);
                recordset.moveNext();
            }

            SMMediaCollector.updateCallouts(list);
            recordset.dispose();

            promise.resolve(true);
        } catch (Exception e) {
            promise.reject(e);
        }
    }

    public boolean saveMedia(final Layer layer, int geoID, String toPath, ReadableArray fieldInfos, boolean addToMap) {
        try {
            WritableArray infos = Arguments.createArray();
            SMMedia media = SMMediaCollector.findMediaByLayer(layer, geoID);

            for (int i = 0; i < fieldInfos.size(); i++) {
                WritableMap dic = Arguments.createMap();
                dic.putString("name", fieldInfos.getMap(i).getString("name"));
                if (dic.getString("name").equals("MediaFilePaths")) {
                    String mediaPaths = "";
                    ReadableArray files = fieldInfos.getMap(i).getArray("value");

//                    if (files.size() > 0) {
                        ArrayList<String> fileArr = new ArrayList<>();
                        for (int j = 0; j < files.size(); j++) {
                            fileArr.add(files.getString(j));
                        }

                        media.saveMedia(this.getReactApplicationContext(), fileArr, toPath, false);
                        ArrayList<String> paths = media.getPaths();
                        for (int j = 0; j < paths.size(); j++) {
                            mediaPaths += paths.get(j) + (j == paths.size() - 1 ? "" : ",");
                        }
//                    }

                    dic.putString("value", mediaPaths);
                } else {
                    // TODO 根据类型来存储数据，目前除了MediaFilePaths，都是字符串
                    dic.putString("value", fieldInfos.getMap(i).getString("value"));
                }
                infos.pushMap(dic);
            }

            // 若多媒体没有名字，则自动赋值
            if (media.getFileName() == null) {
                String fileName = new Date().getTime() + "";
                media.setFileName(fileName);
                WritableMap dic = Arguments.createMap();
                dic.putString("name", "MediaName");
                dic.putString("value", fileName);
                infos.pushMap(dic);
            }

            boolean saveResult = false;
            if (infos.size() > 0) {
                WritableMap params = Arguments.createMap();
                String filter = "SmID=" + geoID;
                params.putString("filter", filter);
                saveResult = SMLayer.setLayerFieldInfo(layer, infos, params);
            }

            if (saveResult && addToMap) {
                final SMMedia media1 = SMMediaCollector.findMediaByLayer(layer, geoID);
                MapWrapView mapView = (MapWrapView)SMap.getInstance().getSmMapWC().getMapControl().getMap().getMapView();
                List<CallOut> callouts = mapView.getCallouts();
                boolean isExist = false;
                if (callouts != null) {
                    for (int i = 0; i < callouts.size(); i++) {
                        final InfoCallout callout = (InfoCallout)callouts.get(i);
                        final String firstMediaFile = media1.getPaths().size() > 0 ?  media1.getPaths().get(0) : "";
                        String firstCalloutFile = callout.getMediaFilePaths().size() > 0 ?  callout.getMediaFilePaths().get(0) : "";
                        // TODO 修改判断
                        if (
                                callout.getLayerName().equals(layer.getName()) &&
                                        (media1.getFileName() != null && callout.getMediaName() != null && callout.getMediaName().equals(media1.getFileName())) &&
                                        (!firstCalloutFile.equals(firstMediaFile) || firstMediaFile.equals(""))
                                ) {
                            isExist = true;
                            SMap.getInstance().getActivity().runOnUiThread(new Runnable(){
                                @Override
                                public void run(){
                                    MapWrapView mapView = (MapWrapView)SMap.getInstance().getSmMapWC().getMapControl().getMap().getMapView();

                                    mapView.removeCallOut(callout.getID());
                                    if (!firstMediaFile.equals("")) addCallout(media1, layer);
                                }
                            });
                        }
                    }
                }
                if ((callouts == null || callouts.size() == 0 || !isExist) && media1.getPaths().size() > 0) {
                    addCallout(media1, layer);
                }
            }

            return saveResult;
        } catch (Exception e) {
            throw e;
        }
    }

    private void addCallout(SMMedia media, Layer layer) {
        if (layer != null) {
            Recordset rs = ((DatasetVector)layer.getDataset()).getRecordset(false, CursorType.DYNAMIC);
            rs.moveLast();

            InfoCallout callout = SMMediaCollector.createCalloutByMedia(getReactApplicationContext(), media, rs, layer.getName(), getCalloutListner());
            ArrayList<InfoCallout> callouts = new ArrayList<>();
            callouts.add(callout);
            SMMediaCollector.addCallouts(callouts);
            rs.dispose();
        }
    }

    private void addCallouts(ArrayList<SMMedia> medias, Layer layer) {
        if (layer != null) {
            Recordset rs = ((DatasetVector)layer.getDataset()).getRecordset(false, CursorType.DYNAMIC);
            rs.moveLast();

            ArrayList<InfoCallout> callouts = new ArrayList<>();
            for (int i = medias.size() - 1; i >= 0; i--) {
                String mediaName = rs.getFieldValue("MediaName").toString();
                SMMedia media = medias.get(i);

                if (media.getFileName().equals("TourLine")) continue; // 线

                while (!mediaName.equals(media.getFileName()) && !rs.isBOF()) {
                    rs.movePrev();
                    mediaName = rs.getFieldValue("MediaName").toString();
                }

                InfoCallout callout = SMMediaCollector.createCalloutByMedia(getReactApplicationContext(), media, rs, layer.getName(), getCalloutListner());
                callouts.add(callout);
                rs.movePrev();
            }
            SMMediaCollector.addCallouts(callouts);
            rs.dispose();
        }
    }

    private void addCalloutsByLayer(Layer layer) {
        if (layer != null) {
            SMMediaCollector.showMediasByLayer(getReactApplicationContext(), layer, getCalloutListner());
        }
    }

    @ReactMethod
    public void removeMedias(Promise promise) {
        try {
            SMap.getInstance().getActivity().runOnUiThread(new Runnable(){
                @Override
                public void run(){
                MapControl mapControl = SMap.getInstance().getSmMapWC().getMapControl();

                mapControl.getMap().getMapView().removeAllCallOut();
                }
            });
            promise.resolve(true);
        } catch (Exception e) {
            promise.reject(e);
        }
    }

    @ReactMethod
    public void removeByIds(ReadableArray ids, String layerName, Promise promise) {
        try {
            SMap.getInstance().getActivity().runOnUiThread(new Runnable(){
                @Override
                public void run(){
                    MapWrapView mapView = (MapWrapView)SMap.getInstance().getSmMapWC().getMapControl().getMap().getMapView();
                    List<CallOut> callouts = mapView.getCallouts();

                    if(callouts != null) {
                        for (int i = 0; i < callouts.size(); i++) {
                            InfoCallout callout = (InfoCallout) callouts.get(i);
                            for(int n = 0; n < ids.size(); n++) {
                                if (callout.getLayerName().equals(layerName) && callout.getGeoID() == ids.getInt(n)) {
                                    mapView.removeCallOut(callout.getID());
                                }
                            }
                        }
                    }
                }
            });
            promise.resolve(true);
        } catch (Exception e) {
            promise.reject(e);
        }
    }

    @ReactMethod
    public void showMedia(String layerName, Promise promise) {
        try {
            Layer layer = SMLayer.findLayerWithName(layerName);
            if (layer != null) {
                SMMediaCollector.showMediasByLayer(getReactApplicationContext(), layer, getCalloutListner());
                promise.resolve(true);
            } else {
                promise.reject(new Error("The layer is not exist"));
            }

        } catch (Exception e) {
            promise.reject(e);
        }
    }

    @ReactMethod
    public void hideMedia(final String layerName, Promise promise) {
        try {
            SMap.getInstance().getActivity().runOnUiThread(new Runnable(){
                @Override
                public void run(){
                    MapWrapView mapView = (MapWrapView)SMap.getInstance().getSmMapWC().getMapControl().getMap().getMapView();

                    List<CallOut> callouts = mapView.getCallouts();

                    if(callouts != null) {
                        for (int i = 0; i < callouts.size(); i++) {
                            InfoCallout callout = (InfoCallout) callouts.get(i);
                            if (callout.getLayerName().equals(layerName)) {
                                mapView.removeCallOut(callout.getID());
                            }
                        }
                    }
                }
            });

            promise.resolve(true);
        } catch (Exception e) {
            promise.reject(e);
        }
    }

    @ReactMethod
    public void getVideoInfo(String path, Promise promise) {
        try {
            WritableMap info = MediaUtil.getScreenShotImage(getReactApplicationContext(), path);
            int duration = MediaUtil.getVideoDuration(getReactApplicationContext(), path);

            duration = new Double(duration / 1000).intValue();
            info.putInt("duration", duration);

            promise.resolve(info);
        } catch (Exception e) {
            promise.reject(e);
        }
    }

    @ReactMethod
    public void getMediaInfo(String layerName, int geoID, Promise promise) {
        try {
            WritableMap data = getCalloutData(layerName + "-" + geoID, layerName, geoID, null);

            if (data != null) {
                promise.resolve(data);
            } else {
                promise.reject(new Error("Can not find this media"));
            }
        } catch (Exception e) {
            promise.reject(e);
        }
    }

    @ReactMethod
    public void addTour(String layerName, ReadableArray files, Promise promise) {
        try {
            Layer layer = SMLayer.findLayerWithName(layerName);
            if (layer == null) {
                promise.reject(new Error("Layer can not be found"));
            } else if (files.size() <= 0) {
                promise.reject(new Error("Files can not be empty"));
            } else {
                SMMediaCollector collector = SMMediaCollector.getInstance();
                int count = 0;
                WritableMap result = Arguments.createMap();
                WritableArray errorFiles = Arguments.createArray();

                ArrayList<SMMedia> medias = new ArrayList<>();
                for (int i = 0; i < files.size(); i++) {
                    ReadableMap mediaData = files.getMap(i);
                    String uri = mediaData.getString("uri");
                    String mediaName;
                    if (mediaData.hasKey("filename")) {
                        mediaName = mediaData.getString("filename");
                    } else {
                        mediaName = uri.substring(uri.lastIndexOf("/") + 1);
                    }

                    SMMedia media;
                    if (
                        mediaData.hasKey("location") &&
                        mediaData.getMap("location").hasKey("longitude") &&
                        mediaData.getMap("location").hasKey("latitude")
                    ) {
                        ReadableMap location = mediaData.getMap("location");
                        double longitute = location.getDouble("longitude");
                        double latitude = location.getDouble("latitude");

                        media = new SMMedia(mediaName, new Point2D(longitute, latitude));
                    } else {
                        media = new SMMedia(mediaName);
                    }

                    if (media.setMediaDataset(layer.getDataset().getDatasource(), layer.getDataset().getName())) {
                        ArrayList<String> filePaths = new ArrayList<>();
                        filePaths.add(uri);
                        boolean saveResult = media.saveMedia(this.getReactApplicationContext(), filePaths, collector.getMediaPath(), true);
                        if (saveResult) {
                            medias.add(media);
                            count++;
                        } else {
                            errorFiles.pushMap((WritableMap) mediaData);
                        }
                    } else {
                        errorFiles.pushMap((WritableMap) mediaData);
                    }
                }

                if (medias.size() > 0) {
                    SMMediaCollector.addLineByMedias(medias, (DatasetVector)layer.getDataset());
                    this.addCallouts(medias, layer);
                }

                boolean res = count == files.size();
                result.putBoolean("result", res);
                result.putArray("errorFiles", errorFiles);
                promise.resolve(result);
            }
        } catch (Exception e) {
            promise.reject(e);
        }
    }
}
