public interface IGISApplication
{
    /**
     * @return A MapBase or any inherited classes or null if not created in application
     */
    MapBase getMap();

    /**
     * @return A authority for sync purposes or empty string if not sync anything
     */
    String getAuthority();

    /**
     * Add account to android account storage
     * @param name Account name (must be uniq)
     * @param url NextGIS Web Server URL
     * @param login User login
     * @param password User password
     * @param token A token returned from NextGIS Web Server (may be empty string)
     * @return true on success or false
     */
    boolean addAccount(String name, String url, String login, String password, String token);

    /**
     * Update account information
     * @param name Account name (the account must be exist)
     * @param key The account key to change (i.e. URL)
     * @param value The new value for key
     */
    void setUserData(String name, String key, String value);

    /**
     * Change password for account
     * @param name Account name (the account must be exist)
     * @param value New password
     */
    void setPassword(String name,String value);

    /**
     * @param accountName Account name
     * @return Account by its name
     */
    Account getAccount(String accountName);

    /**
     * Remove an account
     * @param account Account to remove
     * @return An @see AccountManagerFuture which resolves to a Boolean, true if the account has been successfully removed
     */
    AccountManagerFuture<Boolean> removeAccount(Account account);

    /**
     * @param account Account object
     * @return Account URL
     */
    String getAccountUrl(Account account);

    /**
     * Return some account data
     * @param account account object
     * @param key key to return
     * @return value in user key - value map
     */
    String getAccountUserData(Account account, String key);

    /**
     * @param account Account object
     * @return Account login
     */
    String getAccountLogin(Account account);

    /**
     * @param account Account object
     * @return Account password
     */
    String getAccountPassword(Account account);

    /**
     * @return A GpsEventSource or null if not needed or created in application
     */
    GpsEventSource getGpsEventSource();

    /**
     * Show settings Activity or nothing
     */
    void showSettings(String setting);

    /**
     * Send target event to analytics
     */
    void sendEvent(String category, String action, String label);

    /**
     * Send screen hit to analytics
     */
    void sendScreen(String name);

    /**
     * Get accounts authenticator type
     */
    String getAccountsType();

    /**
     * Get LayerFactory
     */
    LayerFactory getLayerFactory();
} public interface INGWLayer
{
    /**
     * Return the account connected with this layer
     * @return account name
     */
    String getAccountName();

    /**
     * Set the account connected with this layer
     * @param account Name
     */
    void setAccountName(String account);

    /**
     * cache account data for fast access
     */
    void setAccountCacheData();

    /**
     * Sync layer data with NextGIS Web
     * @param authority the account authority
     * @param syncResult object for storing results of operations
     */
    void sync(String authority, Pair<Integer, Integer> ver, SyncResult syncResult);

    /**
     * Return the sync type (i.e. sync only attributes or the whole feature)
     * @return
     */
    int getSyncType();

    /**
     * Set sync type.
     * @see com.nextgis.maplib.util.Constants#SYNC_ALL
     * @param syncType The value result of OR different sync types
     */
    void setSyncType(int syncType);

    /**
     * Return the NextGIS Web server identificator - uniq identificator set for layer on server.
     * @return The NextGIS Web server identificator
     */
    long getRemoteId();

    /**
     * Set NextGIS Web server identificator.
     * @param remoteId The NextGIS Web server identificator
     */
    void setRemoteId(long remoteId);
} public class Geo
{

    protected final static double mEarthMajorRadius = 6378137.0;
    protected final static double mEarthMinorRadius = 6356752.3142;
    protected final static double mE                = mEarthMinorRadius / mEarthMajorRadius;
    protected final static double mES               = 1.0 - (mE * mE);
    protected final static double mEccent           = Math.sqrt(mES);
    protected final static double mCom              = 0.5 * mEccent;


    public static GeoPoint mercatorToWgs84SphereRet(final GeoPoint point)
    {
        GeoPoint retPt = new GeoPoint();
        retPt.mX = mercatorToWgs84SphereX(point.mX);
        retPt.mY = mercatorToWgs84SphereY(point.mY);
        return retPt;
    }


    public static double mercatorToWgs84SphereX(final double x)
    {
        return Math.toDegrees(x / mEarthMajorRadius);
    }


    public static double mercatorToWgs84SphereY(final double y)
    {
        return Math.toDegrees(2 * Math.atan(Math.exp(y / mEarthMajorRadius)) - Math.PI / 2);
    }


    public static void mercatorToWgs84Sphere(GeoPoint point)
    {
        point.mX = mercatorToWgs84SphereX(point.mX);
        point.mY = mercatorToWgs84SphereY(point.mY);
    }


    public static GeoPoint mercatorToWgs84EllipseRet(final GeoPoint point)
    {
        GeoPoint retPt = new GeoPoint();
        retPt.mX = mercatorToWgs84EllipseX(point.mX);
        retPt.mY = mercatorToWgs84EllipseY(point.mY);
        return retPt;
    }


    public static double mercatorToWgs84EllipseX(final double x)
    {
        return Math.toDegrees(x / mEarthMajorRadius);
    }


    public static double mercatorToWgs84EllipseY(final double y)
    {
        double phi = Math.toRadians(y);
        double sinphi = Math.sin(phi);
        double con = mEccent * sinphi;
        con = Math.pow(((1.0 - con) / (1.0 + con)), mCom);
        double ts = Math.tan(0.5 * ((Math.PI * 0.5) - phi)) / con;
        return 0 - mEarthMajorRadius * Math.log(ts);
    }


    public static void mercatorToWgs84Ellipse(GeoPoint point)
    {
        point.mX = mercatorToWgs84EllipseX(point.mX);
        point.mY = mercatorToWgs84EllipseY(point.mY);
    }


    public static GeoPoint wgs84ToMercatorSphereRet(final GeoPoint point)
    {
        GeoPoint retPt = new GeoPoint();
        retPt.mX = wgs84ToMercatorSphereX(point.mX);
        retPt.mY = wgs84ToMercatorSphereY(point.mY);
        return retPt;
    }


    public static double wgs84ToMercatorSphereX(final double x)
    {
        return mEarthMajorRadius * Math.toRadians(x);
    }


    public static double wgs84ToMercatorSphereY(final double y)
    {
        return mEarthMajorRadius * Math.log(Math.tan(Math.PI / 4 + Math.toRadians(y) / 2));
    }


    public static void wgs84ToMercatorSphere(GeoPoint point)
    {
        point.mX = wgs84ToMercatorSphereX(point.mX);
        point.mY = wgs84ToMercatorSphereY(point.mY);
    }


    public static boolean isGeometryTypeSame(
            final int type1,
            final int type2)
    {
        return type1 == type2 ||
               (type1 <= GeoConstants.GTMultiPolygon && type2 <= GeoConstants.GTMultiPolygon &&
                Math.abs(type1 - type2) == 3);
    }
} public class GeoEnvelope
        implements IJSONStore
{
    protected Double mMinX;
    protected Double mMaxX;
    protected Double mMinY;
    protected Double mMaxY;

    public final static int enumGISPtPosLeft = 1;
    public final static int enumGISPtPosRight = 2;
    public final static int enumGISPtPosBottom = 3;
    public final static int enumGISPtPosTop = 4;

    public GeoEnvelope()
    {
        unInit();
    }


    public void unInit()
    {
        mMinX = null;
        mMaxX = null;
        mMinY = null;
        mMaxY = null;
    }


    public GeoEnvelope(
            double minX,
            double maxX,
            double minY,
            double maxY)
    {
        mMinX = minX;
        mMaxX = maxX;
        mMinY = minY;
        mMaxY = maxY;
    }


    public GeoEnvelope(final GeoEnvelope env)
    {
        mMinX = env.mMinX;
        mMaxX = env.mMaxX;
        mMinY = env.mMinY;
        mMaxY = env.mMaxY;
    }


    public void setMin(
            double x,
            double y)
    {
        mMinX = x;
        mMinY = y;
    }


    public void setMax(
            double x,
            double y)
    {
        mMaxX = x;
        mMaxY = y;
    }


    public final GeoPoint getCenter()
    {
        double x = mMinX + width() / 2.0;
        double y = mMinY + height() / 2.0;
        return new GeoPoint(x, y);
    }

    public final double getArea(){
        return width() * height();
    }


    public final double width()
    {
        return mMaxX - mMinX;
    }


    public final double height()
    {
        return mMaxY - mMinY;
    }


    public void adjust(double ratio)
    {
        double w = width() / 2.0;
        double h = height() / 2.0;
        double centerX = mMinX + w;
        double centerY = mMinY + h;

        double envRatio = w / h;

        if (envRatio == ratio) {
            return;
        }

        if (ratio > envRatio) //increase width
        {
            w = h * ratio;
            mMaxX = centerX + w;
            mMinX = centerX - w;
        } else                //increase height
        {
            h = w / ratio;
            mMaxY = centerY + h;
            mMinY = centerY - h;
        }
    }


    public void merge(final GeoEnvelope other) {
        try {
            if (isInit() && other.isInit()) {
                mMinX = Math.min(mMinX, other.mMinX);
                mMaxX = Math.max(mMaxX, other.mMaxX);
                mMinY = Math.min(mMinY, other.mMinY);
                mMaxY = Math.max(mMaxY, other.mMaxY);
            } else {
                mMinX = other.mMinX;
                mMaxX = other.mMaxX;
                mMinY = other.mMinY;
                mMaxY = other.mMaxY;
            }
        } catch (Exception ignored) {}
    }


    public final boolean isInit()
    {
        return mMinX != null && mMinY != null && mMaxX != null && mMaxY != null;
    }


    public void merge(
            double dfX,
            double dfY)
    {
        if (isInit()) {
            mMinX = Math.min(mMinX, dfX);
            mMaxX = Math.max(mMaxX, dfX);
            mMinY = Math.min(mMinY, dfY);
            mMaxY = Math.max(mMaxY, dfY);
        } else {
            mMinX = mMaxX = dfX;
            mMinY = mMaxY = dfY;
        }
    }


    public void intersect(final GeoEnvelope other)
    {
        if (intersects(other)) {
            if (isInit()) {
                mMinX = Math.max(mMinX, other.mMinX);
                mMaxX = Math.min(mMaxX, other.mMaxX);
                mMinY = Math.max(mMinY, other.mMinY);
                mMaxY = Math.min(mMaxY, other.mMaxY);
            } else {
                mMinX = other.mMinX;
                mMaxX = other.mMaxX;
                mMinY = other.mMinY;
                mMaxY = other.mMaxY;
            }
        } else {
            unInit();
        }
    }


    public final boolean intersects(final GeoEnvelope other)
    {
        return mMinX <= other.mMaxX && mMaxX >= other.mMinX &&
               mMinY <= other.mMaxY && mMaxY >= other.mMinY;
    }


    public final boolean contains(final GeoEnvelope other)
    {
        return mMinX <= other.mMinX && mMinY <= other.mMinY &&
               mMaxX >= other.mMaxX && mMaxY >= other.mMaxY;
    }


    public final boolean contains(final GeoPoint pt)
    {
        return mMinX <= pt.getX() && mMinY <= pt.getY() && mMaxX >= pt.getX() && mMaxY >= pt.getY();
    }


    public void offset(
            double x,
            double y)
    {
        mMinX += x;
        mMaxX += x;
        mMinY += y;
        mMaxY += y;
    }


    public void scale(double scale)
    {
        mMaxX = mMinX + width() * scale;
        mMaxY = mMinY + height() * scale;
    }


    public void fix()
    {
        if (mMinX > mMaxX) {
            double tmp = mMinX;
            mMinX = mMaxX;
            mMaxX = tmp;
        }

        if (mMinY > mMaxY) {
            double tmp = mMinY;
            mMinY = mMaxY;
            mMaxY = tmp;
        }
    }


    public String toString()
    {
        return "MinX: " + mMinX + ", MinY: " + mMinY + ", MaxX: " + mMaxX + ", MaxY: " + mMaxY;
    }


    @Override
    public JSONObject toJSON()
            throws JSONException
    {
        JSONObject oJSONBBox = new JSONObject();
        oJSONBBox.put(JSON_BBOX_MINX_KEY, getMinX());
        oJSONBBox.put(JSON_BBOX_MINY_KEY, getMinY());
        oJSONBBox.put(JSON_BBOX_MAXX_KEY, getMaxX());
        oJSONBBox.put(JSON_BBOX_MAXY_KEY, getMaxY());
        return oJSONBBox;
    }


    public final double getMinX()
    {
        return mMinX;
    }


    public void setMinX(double x)
    {
        mMinX = x;
    }


    public final double getMinY()
    {
        return mMinY;
    }


    public void setMinY(double y)
    {
        mMinY = y;
    }


    public final double getMaxX()
    {
        return mMaxX;
    }


    public void setMaxX(double x)
    {
        mMaxX = x;
    }


    public final double getMaxY()
    {
        return mMaxY;
    }


    public void setMaxY(double y)
    {
        mMaxY = y;
    }


    @Override
    public void fromJSON(JSONObject jsonObject)
            throws JSONException
    {
        setMinX(jsonObject.getDouble(JSON_BBOX_MINX_KEY));
        setMinY(jsonObject.getDouble(JSON_BBOX_MINY_KEY));
        setMaxX(jsonObject.getDouble(JSON_BBOX_MAXX_KEY));
        setMaxY(jsonObject.getDouble(JSON_BBOX_MAXY_KEY));
    }

    public void set(GeoEnvelope env) {
        if (env == null)
            return;

        mMinX = env.mMinX;
        mMaxX = env.mMaxX;
        mMinY = env.mMinY;
        mMaxY = env.mMaxY;
    }


    /**
     * Sutherland-Hodgman Polygon Clipping
     * @param pt Test point
     * @param nPos Test type
     * @return true if point inside envelope or false
     */
    public boolean isInside(final GeoPoint pt, int nPos) {
        switch(nPos)
        {
            case enumGISPtPosLeft://XMin
                return (pt.getX() > mMinX);
            case enumGISPtPosRight://XMax
                return (pt.getX() < mMaxX);
            case enumGISPtPosBottom://YMin
                return (pt.getY() < mMaxY);
            case enumGISPtPosTop://YMax
                return (pt.getY() > mMinY);
        }
        return false;
    }
} public class TileItem
        implements Parcelable
{
    public static final Creator<TileItem> CREATOR = new Creator<TileItem>()
    {
        public TileItem createFromParcel(Parcel in)
        {
            return new TileItem(in);
        }


        public TileItem[] newArray(int size)
        {
            return new TileItem[size];
        }
    };

    protected int      mZoomLevel;
    protected int      mX;
    protected int      mY;
    protected GeoEnvelope mEnvelope;


    public TileItem(
            int x,
            int y,
            int zoom,
            GeoEnvelope envelope)
    {
        mZoomLevel = zoom;
        mX = x;
        mY = y;
        mEnvelope = envelope;
    }


    private TileItem(Parcel in)
    {
        mZoomLevel = in.readInt();
        mX = in.readInt();
        mY = in.readInt();
        double minX = in.readDouble();
        double minY = in.readDouble();
        double maxX = in.readDouble();
        double maxY = in.readDouble();
        mEnvelope = new GeoEnvelope(minX, maxX, minY, maxY);
    }


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


    @Override
    public void writeToParcel(
            Parcel parcel,
            int i)
    {
        parcel.writeInt(mX);
        parcel.writeInt(mY);
        parcel.writeInt(mZoomLevel);
        parcel.writeDouble(mEnvelope.getMinX());
        parcel.writeDouble(mEnvelope.getMinY());
        parcel.writeDouble(mEnvelope.getMaxX());
        parcel.writeDouble(mEnvelope.getMaxY());
    }

    public final String toString(final String pattern)
    {
        String out = pattern;
        out = out.replace("{z}", Integer.toString(getZoomLevel()));
        out = out.replace("{x}", Integer.toString(getX()));
        out = out.replace("{y}", Integer.toString(getY()));
        return out;

    }

    public final String toString(){
        return "" + getZoomLevel() + FileUtil.getPathSeparator() + getX() + FileUtil.getPathSeparator() + getY();
    }

    public final GeoPoint getPoint(){
        return new GeoPoint(mEnvelope.getMinX(), mEnvelope.getMaxY());
    }

    public final GeoEnvelope getEnvelope(){
        return mEnvelope;
    }

    public final int getZoomLevel()
    {
        return mZoomLevel;
    }


    public final int getX()
    {
        return mX;
    }


    public final int getY()
    {
        return mY;
    }


    public final String getHash()
    {
        return "z" + mZoomLevel + "." + mX + "." + mY;
    }
} public class AccountUtil {
    public static boolean isProUser(Context context) {
        File support = context.getExternalFilesDir(null);
        if (support == null)
            support = new File(context.getFilesDir(), SUPPORT);
        else
            support = new File(support, SUPPORT);

        try {
            String jsonString = FileUtil.readFromFile(support);
            JSONObject json = new JSONObject(jsonString);
            if (json.optBoolean(JSON_SUPPORTED_KEY)) {
                final String id = json.getString(JSON_USER_ID_KEY);
                final String start = json.getString(JSON_START_DATE_KEY);
                final String end = json.getString(JSON_END_DATE_KEY);
                final String data = id + start + end + "true";
                final String signature = json.getString(JSON_SIGNATURE_KEY);

                return verifySignature(data, signature);
            }
        } catch (JSONException | IOException ignored) { }

        return false;
    }

    private static boolean verifySignature(String data, String signature) {
        try {
            // add public key
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            String key = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzbmnrTLjTLxqCnIqXgIJ\n" +
                    "jebXVOn4oV++8z5VsBkQwK+svDkGK/UcJ4YjXUuPqyiZwauHGy1wizGCgVIRcPNM\n" +
                    "I0n9W6797NMFaC1G6Rp04ISv7DAu0GIZ75uDxE/HHDAH48V4PqQeXMp01Uf4ttti\n" +
                    "XfErPKGio7+SL3GloEqtqGbGDj6Yx4DQwWyIi6VvmMsbXKmdMm4ErczWFDFHIxpV\n" +
                    "ln/VfX43r/YOFxqt26M7eTpaBIvAU6/yWkIsvidMNL/FekQVTiRCl/exPgioDGrf\n" +
                    "06z5a0sd3NDbS++GMCJstcKxkzk5KLQljAJ85Jciiuy2vv14WU621ves8S9cMISO\n" + "HwIDAQAB";
            byte[] keyBytes = Base64.decode(key.getBytes("UTF-8"), Base64.DEFAULT);
            X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
            PublicKey publicKey = keyFactory.generatePublic(spec);

            // verify signature
            Signature signCheck = Signature.getInstance("SHA256withRSA");
            signCheck.initVerify(publicKey);
            signCheck.update(data.getBytes("UTF-8"));
            byte[] sigBytes = Base64.decode(signature, Base64.DEFAULT);
            return signCheck.verify(sigBytes);
        } catch (Exception e) {
            return false;
        }
    }

    public static boolean isSyncActive(Account account, String authority) {
        return isSyncActiveHoneycomb(account, authority);
    }

    public static boolean isSyncActiveHoneycomb(Account account, String authority) {
        for (SyncInfo syncInfo : ContentResolver.getCurrentSyncs()) {
            if (syncInfo.account.equals(account) && syncInfo.authority.equals(authority)) {
                return true;
            }
        }
        return false;
    }

    public static AccountData getAccountData(Context context, String accountName) throws IllegalStateException {
        IGISApplication app = (IGISApplication) context.getApplicationContext();
        Account account = app.getAccount(accountName);

        if (null == account) {
            throw new IllegalStateException("Account is null");
        }

        AccountData accountData = new AccountData();

        accountData.url = app.getAccountUrl(account);
        accountData.login = app.getAccountLogin(account);
        accountData.password = app.getAccountPassword(account);

        return accountData;
    }

    public static class AccountData {
        public String url;
        public String login;
        public String password;
    }
} public interface Constants
{
    String  TAG                = "nextgismobile";
    String  PREFERENCES        = "nextgismobile";
    String  SUPPORT            = "support.bin";
    int     NOT_FOUND          = -1;
    boolean DEBUG_MODE         = false;

    int NGW_v3 = 3;

    /**
     * HTTP parameters
     */
    String APP_USER_AGENT     = "NextGIS Mobile maplib v" + BuildConfig.VERSION_NAME;
    int    IO_BUFFER_SIZE     = 32 * 1024; // 32k
    int    MAX_CONTENT_LENGTH = 5 * 1024 * 1024; //5Mb

    long MIN_LOCAL_FEATURE_ID = 10000000;
    int  MAX_TILES_COUNT      = 6001;

    /**
     * NGW account type
     */
    String NGW_ACCOUNT_TYPE = "com.nextgis.account";
    String NGW_ACCOUNT_GUEST = "anonymous";

    /**
     * Map parameters
     */
    float MIN_SCROLL_STEP = 5.5f;
    int   MAP_LIMITS_NO   = 1; // no limits to scroll map
    int   MAP_LIMITS_X    = 2; // limit to scroll map by x axis
    int   MAP_LIMITS_Y    = 3; // limit to scroll map by y axis
    int   MAP_LIMITS_XY   = 4; // limit to scroll map by x & y axis

    /**
     * The additional size to off screen drawing from 1 and higher As more than more memory needed
     */

    float OFFSCREEN_EXTRASIZE_RATIO = 1.0f;
    int   DEFAULT_TILE_SIZE         = 256;

    /**
     * thread priorities and delays
     */
    int DEFAULT_DRAW_THREAD_PRIORITY       = android.os.Process.THREAD_PRIORITY_DEFAULT + 11;
    int DEFAULT_DOWNLOAD_THREAD_PRIORITY   = android.os.Process.THREAD_PRIORITY_BACKGROUND + 3;
    int DEFAULT_LOAD_LAYER_THREAD_PRIORITY = Thread.MIN_PRIORITY;
    int DEFAULT_EXECUTION_DELAY            = 650;

    /**
     * tune line string and linear ring simplifier
     */
    double SIMPLIFY_TOENV_AREA_MULTIPLY = 1.5;
    // area multiplier to skip if greater than quad tolerance
    double SIMPLIFY_SKIP_AREA_MULTIPLY  = 5;
    int    SAMPLE_DISTANCE_PX           = 5;


    String CONFIG       = "config.json";
    String LAYER_PREFIX = "layer_";
    String MAP_EXT      = ".ngm";

    /**
     * notifications
     */
    String NOTIFY_DELETE            = "com.nextgis.maplib.notify_delete";
    String NOTIFY_DELETE_ALL        = "com.nextgis.maplib.notify_delete_all";
    String NOTIFY_INSERT            = "com.nextgis.maplib.notify_insert";
    String NOTIFY_UPDATE            = "com.nextgis.maplib.notify_update";
    String NOTIFY_UPDATE_ALL        = "com.nextgis.maplib.notify_update_all";
    String NOTIFY_UPDATE_FIELDS     = "com.nextgis.maplib.notify_update_fields";
    String NOTIFY_FEATURE_ID_CHANGE = "com.nextgis.maplib.notify_change_id";

    String NOTIFY_LAYER_NAME = "layer_name";

    /**
     * JSON keys
     */
    String JSON_ID_KEY            = "id";
    String JSON_NAME_KEY          = "name";
    String JSON_VISIBILITY_KEY    = "visible";
    String JSON_LEVELS_KEY        = "levels";
    String JSON_LEVEL_KEY         = "level";
    String JSON_TYPE_KEY          = "type";
    String JSON_MAXLEVEL_KEY      = "max_level";
    String JSON_MINLEVEL_KEY      = "min_level";
    String JSON_LAYERS_KEY        = "layers";
    String JSON_LAYER_KEY         = "layer";
    String JSON_PATH_KEY          = "path";
    String JSON_BBOX_MINX_KEY     = "bbox_minx";
    String JSON_BBOX_MINY_KEY     = "bbox_miny";
    String JSON_BBOX_MAXX_KEY     = "bbox_maxx";
    String JSON_BBOX_MAXY_KEY     = "bbox_maxy";
    String JSON_RENDERERPROPS_KEY = "renderer_properties";
    String JSON_STYLE_RULE_KEY    = "style_rule";
    String JSON_WIDTH_KEY         = "width";
    String JSON_COLOR_KEY         = "color";
    String JSON_OUTCOLOR_KEY      = "out_color";
    String JSON_ALPHA_KEY         = "alpha";
    String JSON_OUTALPHA_KEY      = "out_alpha";
    String JSON_CHANGES_KEY       = "changes";
    String JSON_VALUE_KEY         = "value";
    String JSON_SIZE_KEY          = "size";
    String JSON_TEXT_SIZE_KEY     = "text_size";
    String JSON_TEXT_ALIGN_KEY    = "text_alignment";
    String JSON_TEXT_COLOR_KEY    = "text_color";
    String JSON_DISPLAY_NAME      = "display_name";
    String JSON_RESOURCE_KEY      = "resource";
    String JSON_MESSAGE_KEY       = "message";
    String JSON_EXTENT_KEY        = "extent";
    String JSON_MIN_LAT_KEY       = "minLat";
    String JSON_MAX_LAT_KEY       = "maxLat";
    String JSON_MIN_LON_KEY       = "minLon";
    String JSON_MAX_LON_KEY       = "maxLon";
    String JSON_SUPPORTED_KEY     = "supported";
    String JSON_START_DATE_KEY    = "start_date";
    String JSON_END_DATE_KEY      = "end_date";
    String JSON_USER_ID_KEY       = "nextgis_guid";
    String JSON_SIGNATURE_KEY     = "sign";

    /**
     * database fields
     */
    String FIELD_ID               = "_id";
    String FIELD_OLD_ID           = "old_id";
    String FIELD_GEOM             = "_geom";
    String FIELD_GEOM_            = "_geom_";
    String FIELD_FEATURE_ID       = "feature_id";
    String FIELD_OPERATION        = "operation";
    String FIELD_ATTACH_ID        = "attach_id";
    String FIELD_ATTACH_OPERATION = "attach_operation";

    String ATTRIBUTES_ONLY = "attributes_only";

    /**
     * Layer type
     */
    String CE50 = "CE50";
    String CE90 = "CE90";
    String CE95 = "CE95";
    String CE98 = "CE98";

    /**
     * Layer type
     */
    int LAYERTYPE_REMOTE_TMS   = 1 << 0;
    int LAYERTYPE_NGW_RASTER   = 1 << 1;
    int LAYERTYPE_NGW_VECTOR   = 1 << 2;
    int LAYERTYPE_GROUP        = 1 << 3;
    int LAYERTYPE_LOCAL_VECTOR = 1 << 4;
    int LAYERTYPE_LOCAL_TMS    = 1 << 5;
    int LAYERTYPE_TRACKS       = 1 << 6;
    int LAYERTYPE_LOOKUPTABLE  = 1 << 7;
    int LAYERTYPE_NGW_WEBMAP   = 1 << 8;

    int LAYERTYPE_SYSMAX = 9; // should be the max + 1 of system layer type

    /**
     * File type
     */
    int FILETYPE_PARENT  = 1 << 0;
    int FILETYPE_FOLDER  = 1 << 1;
    int FILETYPE_ZIP     = 1 << 2;
    int FILETYPE_GEOJSON = 1 << 3;
    int FILETYPE_FB      = 1 << 4;
    int FILETYPE_UNKNOWN = 1 << 31;

    /**
     * time constants
     */
    long     ONE_SECOND           = 1000;
    long     ONE_MINUTE           = ONE_SECOND * 60;
    long     ONE_HOUR             = ONE_MINUTE * 60;
    long     ONE_DAY              = ONE_HOUR * 24;
    long     ONE_WEEK             = ONE_DAY * 7;
    long     DEFAULT_TILE_MAX_AGE = ONE_WEEK;
    long     ONE_YEAR             = ONE_DAY * 365;
    int      KEEP_ALIVE_TIME      = 35;
    int      TERMINATE_TIME       = 350;
    TimeUnit KEEP_ALIVE_TIME_UNIT = TimeUnit.MILLISECONDS;

    int  SYNC_NONE           = 1 << 0;
    int  SYNC_GEOMETRY       = 1 << 1;
    int  SYNC_ATTRIBUTES     = 1 << 2;
    int  SYNC_DATA           = SYNC_GEOMETRY | SYNC_ATTRIBUTES;
    int  SYNC_ATTACH         = 1 << 3;
    int  SYNC_ALL            = SYNC_DATA | SYNC_ATTACH;
    long DEFAULT_SYNC_PERIOD = 3600; //1 hour

    String URI_ATTACH  = "attach";
    String URI_CHANGES = "changes";

    // http://stackoverflow.com/a/24055457
    String URI_PARAMETER_LIMIT    = "limit";
    String URI_PARAMETER_TEMP     = "temp";
    String URI_PARAMETER_NOT_SYNC = "not_sync";

    String CHANGES_NAME_POSTFIX      = "_changes";
    int    CHANGE_OPERATION_TEMP     = 1;
    int    CHANGE_OPERATION_NEW      = 1 << 1; // 2
    int    CHANGE_OPERATION_CHANGED  = 1 << 2; // 4
    int    CHANGE_OPERATION_DELETE   = 1 << 3; // 8
    int    CHANGE_OPERATION_ATTACH   = 1 << 4; // 16
    int    CHANGE_OPERATION_NOT_SYNC = 1 << 5; // 32

    int DRAWING_SEPARATE_THREADS = 9;
    int DRAW_NOTIFY_STEP_PERCENT = 10;
    int DRAW_FINISH_ID = -22;

    String[] VECTOR_FORBIDDEN_FIELDS = {
            "ABORT",
            "ACTION",
            "ADD",
            "AFTER",
            "ALL",
            "ALTER",
            "ANALYZE",
            "AND",
            "AS",
            "ASC",
            "ATTACH",
            "AUTOINCREMENT",
            "BEFORE",
            "BEGIN",
            "BETWEEN",
            "BY",
            "CASCADE",
            "CASE",
            "CAST",
            "CHECK",
            "COLLATE",
            "COLUMN",
            "COMMIT",
            "CONFLICT",
            "CONSTRAINT",
            "CREATE",
            "CROSS",
            "CURRENT_DATE",
            "CURRENT_TIME",
            "CURRENT_TIMESTAMP",
            "DATABASE",
            "DEFAULT",
            "DEFERRABLE",
            "DEFERRED",
            "DELETE",
            "DESC",
            "DETACH",
            "DISTINCT",
            "DROP",
            "EACH",
            "ELSE",
            "END",
            "ESCAPE",
            "EXCEPT",
            "EXCLUSIVE",
            "EXISTS",
            "EXPLAIN",
            "FAIL",
            "FOR",
            "FOREIGN",
            "FROM",
            "FULL",
            "GLOB",
            "GROUP",
            "HAVING",
            "IF",
            "IGNORE",
            "IMMEDIATE",
            "IN",
            "INDEX",
            "INDEXED",
            "INITIALLY",
            "INNER",
            "INSERT",
            "INSTEAD",
            "INTERSECT",
            "INTO",
            "IS",
            "ISNULL",
            "JOIN",
            "KEY",
            "LEFT",
            "LIKE",
            "LIMIT",
            "MATCH",
            "NATURAL",
            "NO",
            "NOT",
            "NOTNULL",
            "NULL",
            "OF",
            "OFFSET",
            "ON",
            "OR",
            "ORDER",
            "OUTER",
            "PLAN",
            "PRAGMA",
            "PRIMARY",
            "QUERY",
            "RAISE",
            "RECURSIVE",
            "REFERENCES",
            "REGEXP",
            "REINDEX",
            "RELEASE",
            "RENAME",
            "REPLACE",
            "RESTRICT",
            "RIGHT",
            "ROLLBACK",
            "ROW",
            "SAVEPOINT",
            "SELECT",
            "SET",
            "TABLE",
            "TEMP",
            "TEMPORARY",
            "THEN",
            "TO",
            "TRANSACTION",
            "TRIGGER",
            "UNION",
            "UNIQUE",
            "UPDATE",
            "USING",
            "VACUUM",
            "VALUES",
            "VIEW",
            "VIRTUAL",
            "WHEN",
            "WHERE",
            "WITH",
            "WITHOUT"};

} public class HttpResponse
{
    protected int    mResponseCode;
    protected String mResponseMessage;
    protected String mResponseBody;
    protected boolean mIsOk = false;

    public HttpResponse(int responseCode)
    {
        mResponseCode = responseCode;
    }

    public HttpResponse(
            int responseCode,
            String responseMessage)
    {
        mResponseCode = responseCode;
        mResponseMessage = responseMessage;
    }

    public HttpResponse(
            int responseCode,
            String responseMessage,
            String responseBody)
    {
        mResponseCode = responseCode;
        mResponseMessage = responseMessage;
        mResponseBody = responseBody;
    }

    public int getResponseCode()
    {
        return mResponseCode;
    }

    public void setResponseCode(int responseCode)
    {
        mResponseCode = responseCode;
    }

    public String getResponseMessage()
    {
        return mResponseMessage;
    }

    public void setResponseMessage(String responseMessage)
    {
        mResponseMessage = responseMessage;
    }

    public String getResponseBody()
    {
        return mResponseBody;
    }

    public void setResponseBody(String responseBody)
    {
        mResponseBody = responseBody;
    }

    public boolean isOk()
    {
        return mIsOk;
    }

    public void setOk(boolean ok)
    {
        mIsOk = ok;
    }
} public class NGWUtil
{
    public static String NGWKEY_ID              = "id";
    public static String NGWKEY_GEOM            = "geom";
    public static String NGWKEY_FIELDS          = "fields";
    public static String NGWKEY_SRS             = "srs";
    public static String NGWKEY_EXTENSIONS      = "extensions";
    public static String NGWKEY_NAME            = "name";
    public static String NGWKEY_MIME            = "mime_type";
    public static String NGWKEY_DESCRIPTION     = "description";
    public static String NGWKEY_DISPLAY_NAME    = JSON_DISPLAY_NAME;
    public static String NGWKEY_YEAR            = "year";
    public static String NGWKEY_MONTH           = "month";
    public static String NGWKEY_DAY             = "day";
    public static String NGWKEY_HOUR            = "hour";
    public static String NGWKEY_MINUTE          = "minute";
    public static String NGWKEY_SECOND          = "second";
    public static String NGWKEY_FEATURE_COUNT   = "total_count";
    public static String NGWKEY_KEYNAME         = "keyname";
    public static String NGWKEY_PASSWORD        = "password";
    public static String NGWKEY_CLS             = "cls";
    public static String NGWKEY_VECTOR_LAYER    = "vector_layer";
    public static String NGWKEY_RESOURCE_GROUP  = "resource_group";
    public static String NGWKEY_PARENT          = "parent";
    public static String NGWKEY_GEOMETRY_TYPE   = "geometry_type";
    public static String NGWKEY_DATATYPE        = "datatype";
    public static String NGWKEY_LOOKUP_TABLE    = "lookup_table";
    public static String NGWKEY_RESMETA         = "resmeta";
    public static String NGWKEY_ITEMS           = "items";
    public static String NGUA = "";
    public static String NGID = "";
    public static String UUID = "";

    /**
     * NGW API Functions
     */

    public static String getConnectionCookie(
            AtomicReference<String> reference,
            String login,
            String password)
            throws IOException
    {
        String sUrl = reference.get();
        if (!sUrl.startsWith("http")) {
            sUrl = "http://" + sUrl;
            reference.set(sUrl);
        }

        sUrl += "/login";
        String sPayload = "login=" + login + "&password=" + password;
        final HttpURLConnection conn = NetworkUtil.getHttpConnection("POST", sUrl, null, null);
        if (null == conn) {
            Log.d(TAG, "Error get connection object: " + sUrl);
            return null;
        }
        conn.setInstanceFollowRedirects(false);
        conn.setDefaultUseCaches(false);
        conn.setDoOutput(true);
        conn.connect();

        OutputStream os = conn.getOutputStream();
        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(os, "UTF-8"));
        writer.write(sPayload);

        writer.flush();
        writer.close();
        os.close();

        int responseCode = conn.getResponseCode();
        if (!(responseCode == HttpURLConnection.HTTP_MOVED_TEMP || responseCode == HttpURLConnection.HTTP_MOVED_PERM)) {
            Log.d(TAG, "Problem execute post: " + sUrl + " HTTP response: " + responseCode);
            return null;
        }

        String headerName;
        for (int i = 1; (headerName = conn.getHeaderFieldKey(i)) != null; i++) {
            if (headerName.equals("Set-Cookie")) {
                return conn.getHeaderField(i);
            }
        }

        if (!sUrl.startsWith("https")) {
            sUrl = sUrl.replace("http", "https").replace("/login", "");
            reference.set(sUrl);
        }

        return getConnectionCookie(reference, login, password);
    }

    public static String appendix() {
        return "?source=" + NGUA + "&ngid=" + NGID + "&deviceid=" + UUID;
    }

    public static String getServerUrl(String server) {
        if (!server.startsWith("http")) {
            server = "http://" + server;
        }
        return server;//.replace("http://", "https://");
    }

    public static String getFileUploadUrl(String server)
    {
        return getServerUrl(server) + "/api/component/file_upload/upload" + appendix();
    }


    public static String getNgwVersionUrl(String server)
    {
        return getServerUrl(server) + "/api/component/pyramid/pkg_version";
    }


    public static String getNgwUrlResolverUrl(String uuid)
    {
        return "https://groundcontrol.nimbo.nextgis.net/api/instance/" + uuid + "/url";
    }


    public static String getRealNgwUrlFromUuid(String url) throws IOException {
        HttpResponse response = NetworkUtil.get(url, null, null, false);
        if (response.isOk()) {
            return response.mResponseBody;
        }
        return null;
    }


    public static Pair<Integer, Integer> getNgwVersion(Context context, String account) {
        Pair<Integer, Integer> ver = new Pair<>(-1, -1);
        try {
            AccountUtil.AccountData accountData = AccountUtil.getAccountData(context, account);
            ver = NGWUtil.getNgwVersion(accountData.url, accountData.login, accountData.password);
        } catch (IOException | JSONException | NumberFormatException | IllegalStateException ignored) { }

        return ver;
    }


    public static Pair<Integer, Integer> getNgwVersion(
            String url,
            String login,
            String password)
            throws IOException, JSONException, NumberFormatException
    {
        HttpResponse response =
                NetworkUtil.get(NGWUtil.getNgwVersionUrl(url), login, password, false);
        if (response.isOk()) {
            JSONObject verJSONObject = new JSONObject(response.getResponseBody());
            String fullVer = verJSONObject.getString("nextgisweb");
            String[] verParts = fullVer.split("\\.");

            if (verParts.length >= 2) {
                try {
                    Integer major = Integer.parseInt(verParts[0]);
                    Integer minor = Integer.parseInt(verParts[1]);
                    return new Pair<>(major, minor);
                } catch (NumberFormatException ignored) { }
            }

            Log.d(TAG, "BAD format of the NGW version, must start with 'major.minor', obtained: " + fullVer);
        }

        return null;
    }


    /**
     * GeoJSON URL. Get data as GeoJSON
     *
     * @param server
     *         URL to NextGIS Web server
     * @param remoteId
     *         Vector layer resource id
     *
     * @return URL
     */
    public static String getGeoJSONUrl(
            String server,
            long remoteId)
    {
        return getResourceUrl(server, remoteId) + "/geojson";
    }


    /**
     * TMS URL for raster layer
     *
     * @param server
     *         URL
     * @param styleIds
     *         Raster style ids
     *
     * @return URL to TMS for TMSLayer
     */
    public static String getTMSUrl(
            String server,
            Long[] styleIds)
    {
        String ids = "";
        if (styleIds != null && styleIds.length > 0) {
            ids += styleIds[0];
            for (int i = 1; i < styleIds.length; i++) {
                ids += "," + styleIds[i];
            }
        }

        // old url return server + "/resource/" + styleId + "/tms?z={z}&x={x}&y={y}";
        return getServerUrl(server) + "/api/component/render/tile?x={x}&y={y}&z={z}&resource=" + ids;
    }


    /**
     * Resource URL
     *
     * @param server
     *         URL
     * @param remoteId
     *         resource id
     *
     * @return URL to resource
     */
    public static String getResourceUrl(
            String server,
            long remoteId)
    {
        return getBaseUrl(server) + remoteId;
    }


    /**
     * The resource base url
     *
     * @param server
     *         URL
     *
     * @return URL to base resource
     */
    public static String getBaseUrl(String server) {
        return getServerUrl(server) + "/api/resource/";
    }


    /**
     * The form url for vector layer
     *
     * @param server
     *         URL
     * @param remoteId
     *         resource id
     *
     * @return URL to resource
     */
    public static String getFormUrl(String server, long remoteId) {
        return getResourceUrl(server, remoteId) + "/ngfp";
    }


    /**
     * The resource's children url
     *
     * @param server
     *         URL
     *
     * @return URL to base resource
     */
    public static String getResourceChildrenUrl(String server, long resourceId) {
        return getBaseUrl(server) + "?parent=" + resourceId;
    }


    /**
     * Get one row from vector layer
     *
     * @param server
     *         URL
     * @param remoteId
     *         resource id
     * @param featureId
     *         row id
     *
     * @return URL to row
     */
    public static String getFeatureUrl(
            String server,
            long remoteId,
            long featureId)
    {
        return getFeaturesUrl(server, remoteId) + featureId + appendix();
    }


    /**
     * Get the url to JSONArray of features  (NOT GeoJSON!)
     *
     * @param server
     *         URL
     * @param remoteId
     *         vector layer id
     *
     * @return URL
     */
    public static String getFeaturesUrl(
            String server,
            long remoteId)
    {
        return getResourceUrl(server, remoteId) + "/feature/";
    }


    public static String getFeaturesUrl(
            String server,
            long remoteId,
            String where)
    {
        if (TextUtils.isEmpty(where))
            return getFeaturesUrl(server, remoteId);

        return getFeaturesUrl(server, remoteId) + "?" + where;
    }


    public static String getExtent(String server, long remoteId) {
        return getResourceUrl(server, remoteId) + "/extent";
    }


    public static String getTrackedFeaturesUrl(
            String server,
            long remoteId,
            long startDate)
    {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.getDefault());
        String start = sdf.format(new Date(startDate));
        return getServerUrl(server) + "/api/vector_layer/" + remoteId + "/diff/?ts_start=" + start;
    }


    public static String getFeatureAttachmentUrl(
            String server,
            long remoteId,
            long featureId)
    {
        return getFeaturesUrl(server, remoteId) + featureId + "/attachment/";
    }


    public static List<Field> getFieldsFromJson(JSONArray fieldsJSONArray)
            throws JSONException
    {
        List<Field> fields = new LinkedList<>();
        for (int i = 0; i < fieldsJSONArray.length(); i++) {
            JSONObject fieldJSONObject = fieldsJSONArray.getJSONObject(i);
            String type = fieldJSONObject.getString("datatype");
            String alias = fieldJSONObject.getString("display_name");
            String name = fieldJSONObject.getString("keyname");

            int nType = LayerUtil.stringToType(type);
            if (Constants.NOT_FOUND != nType) {
                fields.add(new Field(nType, name, alias));
            }
        }
        return fields;
    }


    public static Feature readNGWFeature(
            JsonReader reader,
            List<Field> fields,
            int nSRS)
            throws IOException, IllegalStateException, NumberFormatException, OutOfMemoryError
    {
        final Feature feature = new Feature(Constants.NOT_FOUND, fields);

        reader.beginObject();
        while (reader.hasNext()) {
            String name = reader.nextName();
            if (name.equals(NGWUtil.NGWKEY_ID)) {
                feature.setId(reader.nextLong());
            } else if (name.equals(NGWUtil.NGWKEY_GEOM)) {
                String wkt = reader.nextString();
                GeoGeometry geom = GeoGeometryFactory.fromWKT(wkt, nSRS);
                geom.setCRS(nSRS);
                if (nSRS != GeoConstants.CRS_WEB_MERCATOR) {
                    geom.project(GeoConstants.CRS_WEB_MERCATOR);
                }
                feature.setGeometry(geom);
            } else if (name.equals(NGWUtil.NGWKEY_FIELDS)) {
                readNGWFeatureFields(feature, reader, fields);
            } else if (name.equals(NGWUtil.NGWKEY_EXTENSIONS)) {
                if (reader.peek() != JsonToken.NULL) {
                    readNGWFeatureAttachments(feature, reader);
                }
            } else {
                reader.skipValue();
            }
        }
        reader.endObject();
        return feature;
    }


    public static void readNGWFeatureFields(Feature feature, JsonReader reader, List<Field> fields)
            throws IOException, IllegalStateException, NumberFormatException
    {
        reader.beginObject();
        while (reader.hasNext()) {
            String name = reader.nextName();
            if (reader.peek() == JsonToken.NULL) {
                reader.skipValue();
            } else {
                boolean bAdded = false;
                for (Field field : fields) {
                    if (field.getName().equals(name) || field.getName().equals(LayerUtil.normalizeFieldName(name))) {
                        switch (field.getType()) {
                            case GeoConstants.FTReal:
                                feature.setFieldValue(field.getName(), reader.nextDouble());
                                bAdded = true;
                                break;
                            case GeoConstants.FTInteger:
                                feature.setFieldValue(field.getName(), reader.nextInt());
                                bAdded = true;
                                break;
                            case GeoConstants.FTString:
                                feature.setFieldValue(field.getName(), reader.nextString());
                                bAdded = true;
                                break;
                            case GeoConstants.FTDate:
                            case GeoConstants.FTTime:
                            case GeoConstants.FTDateTime:
                                readNGWDate(feature, reader, field.getName());
                                bAdded = true;
                                break;
                            default:
                                break;
                        }
                        break;
                    }
                }
                if (!bAdded) {
                    reader.skipValue();
                }
            }
        }
        reader.endObject();
    }


    private static void readNGWDate(Feature feature, JsonReader reader, String fieldName)
            throws IOException
    {
        reader.beginObject();
        int nYear = 1900;
        int nMonth = 1;
        int nDay = 1;
        int nHour = 0;
        int nMinute = 0;
        int nSecond = 0;
        while (reader.hasNext()) {
            String name = reader.nextName();
            if (name.equals(NGWUtil.NGWKEY_YEAR)) {
                nYear = reader.nextInt();
            } else if (name.equals(NGWUtil.NGWKEY_MONTH)) {
                nMonth = reader.nextInt();
            } else if (name.equals(NGWUtil.NGWKEY_DAY)) {
                nDay = reader.nextInt();
            } else if (name.equals(NGWUtil.NGWKEY_HOUR)) {
                nHour = reader.nextInt();
            } else if (name.equals(NGWUtil.NGWKEY_MINUTE)) {
                nMinute = reader.nextInt();
            } else if (name.equals(NGWUtil.NGWKEY_SECOND)) {
                nSecond = reader.nextInt();
            } else {
                reader.skipValue();
            }
        }

        TimeZone timeZone = TimeZone.getDefault();
        timeZone.setRawOffset(0); // set to UTC
        Calendar calendar = new GregorianCalendar(timeZone);
        calendar.set(nYear, nMonth - 1, nDay, nHour, nMinute, nSecond);
        calendar.set(Calendar.MILLISECOND, 0); // we must to reset millis
        feature.setFieldValue(fieldName, calendar.getTimeInMillis());

        reader.endObject();
    }


    private static void readNGWFeatureAttachments(Feature feature, JsonReader reader)
            throws IOException
    {
        //add extensions
        reader.beginObject();
        while (reader.hasNext()) {
            String name = reader.nextName();
            if (name.equals("attachment") && reader.peek() != JsonToken.NULL) {
                reader.beginArray();
                while (reader.hasNext()) {
                    readNGWFeatureAttachment(feature, reader);
                }
                reader.endArray();
            } else {
                reader.skipValue();
            }
        }

        reader.endObject();
    }


    private static void readNGWFeatureAttachment(Feature feature, JsonReader reader)
            throws IOException
    {
        reader.beginObject();
        String attachId = "";
        String name = "";
        String mime = "";
        String descriptionText = "";
        while (reader.hasNext()) {
            String keyName = reader.nextName();
            if (reader.peek() == JsonToken.NULL) {
                reader.skipValue();
                continue;
            }

            if (keyName.equals(NGWUtil.NGWKEY_ID)) {
                attachId += reader.nextLong();
            } else if (keyName.equals(NGWUtil.NGWKEY_NAME)) {
                name += reader.nextString();
            } else if (keyName.equals(NGWUtil.NGWKEY_MIME)) {
                mime += reader.nextString();
            } else if (keyName.equals(NGWUtil.NGWKEY_DESCRIPTION)) {
                descriptionText += reader.nextString();
            } else {
                reader.skipValue();
            }
        }
        AttachItem item = new AttachItem(attachId, name, mime, descriptionText);
        feature.addAttachment(item);

        reader.endObject();
    }


    public static List<Feature> jsonToFeatures(
            JSONArray featuresJSONArray,
            List<Field> fields,
            int nSRS,
            IProgressor progressor)
            throws JSONException
    {
        List<Feature> features = new LinkedList<>();
        if (null != progressor) {
            progressor.setMax(featuresJSONArray.length());
            progressor.setIndeterminate(false);
        }

        for (int i = 0; i < featuresJSONArray.length(); i++) {
            JSONObject featureJSONObject = featuresJSONArray.getJSONObject(i);

            if (null != progressor) {
                progressor.setValue(i);
            }

            long id = featureJSONObject.getLong(NGWUtil.NGWKEY_ID);
            String wkt = featureJSONObject.getString(NGWUtil.NGWKEY_GEOM);
            JSONObject fieldsJSONObject = featureJSONObject.getJSONObject(NGWUtil.NGWKEY_FIELDS);
            Feature feature = new Feature(id, fields);
            GeoGeometry geom = GeoGeometryFactory.fromWKT(wkt, nSRS);
            if (null == geom)
                continue;

            geom.setCRS(nSRS);
            if (nSRS != GeoConstants.CRS_WEB_MERCATOR)
                geom.project(GeoConstants.CRS_WEB_MERCATOR);
            if (!geom.isValid())
                continue;

            feature.setGeometry(geom);

            for (Field field : fields) {
                if (field.getType() == GeoConstants.FTDateTime ||
                        field.getType() == GeoConstants.FTDate ||
                        field.getType() == GeoConstants.FTTime) {
                    if (!fieldsJSONObject.isNull(field.getName())) {
                        JSONObject dateJson = fieldsJSONObject.getJSONObject(field.getName());
                        int nYear = 1900;
                        int nMonth = 1;
                        int nDay = 1;
                        int nHour = 0;
                        int nMinute = 0;
                        int nSec = 0;
                        if (dateJson.has(NGWKEY_YEAR)) {
                            nYear = dateJson.getInt(NGWKEY_YEAR);
                        }
                        if (dateJson.has(NGWKEY_MONTH)) {
                            nMonth = dateJson.getInt(NGWKEY_MONTH);
                        }
                        if (dateJson.has(NGWKEY_DAY)) {
                            nDay = dateJson.getInt(NGWKEY_DAY);
                        }
                        if (dateJson.has(NGWKEY_HOUR)) {
                            nHour = dateJson.getInt(NGWKEY_HOUR);
                        }
                        if (dateJson.has(NGWKEY_MINUTE)) {
                            nMinute = dateJson.getInt(NGWKEY_MINUTE);
                        }
                        if (dateJson.has(NGWKEY_SECOND)) {
                            nSec = dateJson.getInt(NGWKEY_SECOND);
                        }

                        TimeZone timeZone = TimeZone.getDefault();
                        timeZone.setRawOffset(0); // set to UTC
                        Calendar calendar = new GregorianCalendar(timeZone);
                        calendar.set(nYear, nMonth - 1, nDay, nHour, nMinute, nSec);
                        calendar.set(Calendar.MILLISECOND, 0); // we must to reset millis
                        feature.setFieldValue(field.getName(), calendar.getTimeInMillis());
                    }
                } else {
                    if (!fieldsJSONObject.isNull(field.getName())) {
                        feature.setFieldValue(
                                field.getName(), fieldsJSONObject.get(field.getName()));
                    }
                }
            }

            //add extensions
            if (featureJSONObject.has("extensions")) {
                JSONObject ext = featureJSONObject.getJSONObject("extensions");
                //get attachment & description
                if (!ext.isNull("attachment")) {
                    JSONArray attachment = ext.getJSONArray("attachment");
                    for (int j = 0; j < attachment.length(); j++) {
                        JSONObject jsonAttachmentDetails = attachment.getJSONObject(j);
                        String attachId = "" + jsonAttachmentDetails.getLong(Constants.JSON_ID_KEY);
                        String name = jsonAttachmentDetails.getString(Constants.JSON_NAME_KEY);
                        String mime = jsonAttachmentDetails.getString("mime_type");
                        String descriptionText = jsonAttachmentDetails.getString("description");
                        AttachItem item = new AttachItem(attachId, name, mime, descriptionText);
                        feature.addAttachment(item);
                    }
                }
            }

            features.add(feature);
        }
        return features;
    }


    public static boolean signUp(
            String server,
            String login,
            String password,
            String displayName,
            String description)
    {
        server += server.endsWith("/") ? "" : "/";
        server += "api/component/auth/register";
        server = getServerUrl(server);

        JSONObject payload = new JSONObject();
        try {
            payload.put(NGWKEY_KEYNAME, login);
            payload.put(NGWKEY_PASSWORD, password);
            payload.put(NGWKEY_DISPLAY_NAME, displayName == null ? login : displayName);
            payload.put(NGWKEY_DESCRIPTION, description);

            HttpResponse response =
                    NetworkUtil.post(server, payload.toString(), null, null, false);
            return response.isOk() && new JSONObject(response.getResponseBody()).has(JSON_ID_KEY);
        } catch (JSONException | IOException e) {
            e.printStackTrace();
        }

        return false;
    }

    public static boolean getResourceByKey(Context context, INGWResource resource, Map<String, Resource> keys) {
        if (resource instanceof Connection) {
            Connection connection = (Connection) resource;
            connection.loadChildren();
        } else if (resource instanceof ResourceGroup) {
            ResourceGroup resourceGroup = (ResourceGroup) resource;
            resourceGroup.loadChildren();
        }

        for (int i = 0; i < resource.getChildrenCount(); ++i) {
            INGWResource childResource = resource.getChild(i);

            if (keys.containsKey(childResource.getKey()) && childResource instanceof Resource) {
                Resource ngwResource = (Resource) childResource;
                keys.put(ngwResource.getKey(), ngwResource);
            }

            boolean bIsFill = true;
            for (Map.Entry<String, Resource> entry : keys.entrySet()) {
                if (entry.getValue() == null) {
                    bIsFill = false;
                    break;
                }
            }

            if (bIsFill) {
                return true;
            }

            if (getResourceByKey(context, childResource, keys)) {
                return true;
            }
        }

        boolean bIsFill = true;

        for (Map.Entry<String, Resource> entry : keys.entrySet()) {
            if (entry.getValue() == null) {
                bIsFill = false;
                break;
            }
        }

        return bIsFill;
    }

    public static HttpResponse createNewResource(
            Context context,
            Connection connection,
            JSONObject json)
    {
        HttpResponse result;
        try {
            AccountUtil.AccountData accountData =
                    AccountUtil.getAccountData(context, connection.getName());
            result = NetworkUtil.post(getBaseUrl(accountData.url), json.toString(),
                    accountData.login, accountData.password, false);
        } catch (IOException e) {
            e.printStackTrace();
            result = new HttpResponse(500);
        } catch (IllegalStateException e) {
            e.printStackTrace();
            result = new HttpResponse(401);
        }

        return result;
    }

    public static HttpResponse createNewGroup(
            Context context,
            Connection connection,
            long parentId,
            String name,
            String keyName)
    {
        JSONObject payload = new JSONObject();
        try {
            JSONObject resource = new JSONObject();
            resource.put(NGWKEY_CLS, NGWKEY_RESOURCE_GROUP);
            JSONObject id = new JSONObject();
            id.put(JSON_ID_KEY, parentId);
            resource.put(NGWKEY_PARENT, id);
            resource.put(NGWKEY_DISPLAY_NAME, name);
            if (keyName != null) {
                resource.put(NGWKEY_KEYNAME, keyName);
            }
            payload.put(JSON_RESOURCE_KEY, resource);
        } catch (JSONException e) {
            e.printStackTrace();
            return new HttpResponse(500);
        }

        return createNewResource(context, connection, payload);
    }

    public static HttpResponse createNewLayer(
            Connection connection,
            VectorLayer layer,
            long parentId,
            String keyName)
    {
        JSONObject payload = new JSONObject();
        try {
            JSONObject resource = new JSONObject();
            resource.put(NGWKEY_CLS, NGWKEY_VECTOR_LAYER);
            JSONObject id = new JSONObject();
            id.put(JSON_ID_KEY, parentId);
            resource.put(NGWKEY_PARENT, id);
            resource.put(JSON_DISPLAY_NAME, layer.getName());
            if (keyName != null) {
                resource.put(NGWKEY_KEYNAME, keyName);
            }
            payload.put(JSON_RESOURCE_KEY, resource);

            JSONObject vectorLayer = new JSONObject();
            JSONObject srs = new JSONObject();
            srs.put(JSON_ID_KEY, GeoConstants.CRS_WEB_MERCATOR);
            vectorLayer.put(NGWKEY_SRS, srs);
            vectorLayer.put(
                    NGWKEY_GEOMETRY_TYPE, GeoGeometryFactory.typeToString(layer.getGeometryType()));
            JSONArray fields = new JSONArray();
            for (Field field : layer.getFields()) {
                JSONObject current = new JSONObject();
                current.put(NGWKEY_KEYNAME, field.getName());
                current.put(NGWKEY_DISPLAY_NAME, field.getAlias());
                current.put(NGWKEY_DATATYPE, LayerUtil.typeToString(field.getType()));
                fields.put(current);
            }
            vectorLayer.put(NGWKEY_FIELDS, fields);
            payload.put(NGWKEY_VECTOR_LAYER, vectorLayer);
        } catch (JSONException e) {
            e.printStackTrace();
            return new HttpResponse(500);
        }

        return createNewResource(layer.getContext(), connection, payload);
    }

    public static HttpResponse createNewLookupTable(
            Connection connection,
            NGWLookupTable table,
            long parentId,
            String keyName)
    {
        JSONObject payload = new JSONObject();
        try {
            JSONObject resource = new JSONObject();
            resource.put(NGWKEY_CLS, NGWKEY_LOOKUP_TABLE);
            JSONObject id = new JSONObject();
            id.put(JSON_ID_KEY, parentId);
            resource.put(NGWKEY_PARENT, id);
            resource.put(JSON_DISPLAY_NAME, table.getName());
            if (keyName != null) {
                resource.put(NGWKEY_KEYNAME, keyName);
            }
            payload.put(JSON_RESOURCE_KEY, resource);

            JSONObject itemsResmeta = new JSONObject();
            itemsResmeta.put(NGWKEY_ITEMS, new JSONObject());
            payload.put(NGWKEY_RESMETA, itemsResmeta);

            JSONObject jsonData = new JSONObject();
            Map<String, String> data = table.getData();
            if (data != null) {
                for (Map.Entry<String, String> entry : data.entrySet()) {
                    jsonData.put(entry.getKey(), entry.getValue());
                }
            }
            JSONObject items = new JSONObject();
            items.put(NGWKEY_ITEMS, jsonData);
            payload.put(NGWKEY_LOOKUP_TABLE, items);
        } catch (JSONException e) {
            e.printStackTrace();
            return new HttpResponse(500);
        }

        return createNewResource(table.getContext(), connection, payload);
    }
} public class NetworkUtil
{
    private static final String IP_ADDRESS = "((25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(25[0-5]|2[0-4]"
            + "[0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]"
            + "[0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}"
            + "|[1-9][0-9]|[0-9]))(:[0-9]{1,5})?";
    public static final String URL_PATTERN = "^(?i)((ftp|https?)://)?(([\\da-z.-]+)\\.([a-z.]{2,6})|" + IP_ADDRESS + ")(:[0-9]{1,5})?(/\\S*)?$";

    public final ConnectivityManager mConnectionManager;
    public final TelephonyManager    mTelephonyManager;
    protected       long                mLastCheckTime;
    protected       Context             mContext;

    public final static int TIMEOUT_CONNECTION = 10000;
    public final static int TIMEOUT_SOCKET = 240000; // 180 sec

    public final static int ERROR_AUTH                = -401;
    public final static int ERROR_NETWORK_UNAVAILABLE = -1;
    public final static int ERROR_CONNECT_FAILED      = 0;
    public final static int ERROR_DOWNLOAD_DATA       = 1;

    public final static String HTTP_GET    = "GET";
    public final static String HTTP_POST   = "POST";
    public final static String HTTP_PUT    = "PUT";
    public final static String HTTP_DELETE = "DELETE";


    public NetworkUtil(Context context)
    {
        mConnectionManager =
                (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
        mLastCheckTime = Constants.NOT_FOUND;
        mContext = context;
    }


    public synchronized boolean isNetworkAvailable()
    {
        if (mConnectionManager == null) {
            return false;
        }

        NetworkInfo info = mConnectionManager.getActiveNetworkInfo();
        if (info == null) {
            return false;
        }

        return info.isConnected();
    }

    public static HttpURLConnection getProperConnection(String targetURL) throws IOException {
        URL url = new URL(targetURL);
        // Open a HTTP connection to the URL
        if (targetURL.startsWith("https://"))
            return (HttpsURLConnection) url.openConnection();
        else
            return (HttpURLConnection) url.openConnection();
    }


    public static HttpURLConnection getHttpConnection(
            String method,
            String targetURL,
            String username,
            String password)
            throws IOException {
        HttpURLConnection conn = getProperConnection(targetURL);

        String basicAuth = getHTTPBaseAuth(username, password);
        if (null != basicAuth) {
            conn.setRequestProperty("Authorization", basicAuth);
        }

        return getHttpConnection(method, targetURL, conn);
    }

    public static HttpURLConnection getHttpConnection(
            String method,
            String targetURL,
            String auth)
            throws IOException {
        HttpURLConnection conn = getProperConnection(targetURL);

        if (null != auth) {
            conn.setRequestProperty("Authorization", auth);
        }

        return getHttpConnection(method, targetURL, conn);
    }


    public static HttpURLConnection getHttpConnection(
            String method,
            String targetURL,
            HttpURLConnection conn)
            throws IOException {
        conn.setRequestProperty("User-Agent", Constants.APP_USER_AGENT);

        // Allow Inputs
        conn.setDoInput(true);
        // Don't use a cached copy.
        conn.setUseCaches(false);
        // Use a post method.
        if (method.length() > 0)
            conn.setRequestMethod(method);

        conn.setConnectTimeout(TIMEOUT_CONNECTION);
        conn.setReadTimeout(TIMEOUT_SOCKET);
        conn.setRequestProperty("Accept", "*/*");

        return isValidUri(targetURL) ? conn : null;
    }


    public static boolean isValidUri(String url) {
        Pattern pattern = Pattern.compile(URL_PATTERN);
        Matcher match = pattern.matcher(url);
        return match.matches();
    }

    public static String getHTTPBaseAuth(String username, String password){
        if (!TextUtils.isEmpty(username) && !TextUtils.isEmpty(password)) {
            return "Basic " + Base64.encodeToString(
                    (username + ":" + password).getBytes(), Base64.NO_WRAP);
        }
        return null;
    }

    public static String responseToString(final InputStream is)
            throws IOException
    {
        if (is == null) {
            return null;
        }

        byte[] buffer = new byte[Constants.IO_BUFFER_SIZE];
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        FileUtil.copyStream(is, baos, buffer, Constants.IO_BUFFER_SIZE);
        byte[] bytesReceived = baos.toByteArray();
        baos.close();
        is.close();

        return new String(bytesReceived);
    }

    public static void getStream(
            String targetURL,
            String username,
            String password,
            OutputStream outputStream)
            throws IOException
    {
        final HttpURLConnection conn = getHttpConnection("GET", targetURL, username, password);
        if (null == conn) {
            if (Constants.DEBUG_MODE)
                Log.d(TAG, "Error get stream: " + targetURL);
            throw new IOException("Connection is null");
        }
        int responseCode = conn.getResponseCode();
        if (responseCode == HttpURLConnection.HTTP_MOVED_PERM && conn.getURL().getProtocol().equals("http")) {
            targetURL = targetURL.replace("http", "https");
            getStream(targetURL, username, password, outputStream);
            return;
        }
        if (responseCode != HttpURLConnection.HTTP_OK) {
            if(Constants.DEBUG_MODE)
                Log.d(TAG, "Problem execute getStream: " + targetURL + " HTTP response: " +
                    responseCode + " username: " + username);
            throw new IOException("Response code is " + responseCode);
        }

        byte data[] = new byte[Constants.IO_BUFFER_SIZE];
        InputStream is = conn.getInputStream();
        FileUtil.copyStream(is, outputStream, data, Constants.IO_BUFFER_SIZE);
        outputStream.close();
    }

    protected static HttpResponse getHttpResponse(
            HttpURLConnection conn,
            boolean readErrorResponseBody)
            throws IOException
    {
        String method = conn.getRequestMethod();
        int code = conn.getResponseCode();
        String message = conn.getResponseMessage();
        HttpResponse response = new HttpResponse(code, message);

        if (code == HttpURLConnection.HTTP_MOVED_PERM && conn.getURL().getProtocol().equals("http")) {
            if (method.equals("PUT") || method.equals("POST")) {
                return response;
            }
            String target = conn.getURL().toString().replace("http", "https");
            String auth = conn.getRequestProperty("Authorization");
            HttpURLConnection connection = getHttpConnection(conn.getRequestMethod(), target, auth);
            return getHttpResponse(connection, readErrorResponseBody);
        }

        if (!(code == HttpURLConnection.HTTP_OK
                || (code == HttpURLConnection.HTTP_CREATED && method.equals(HTTP_POST)))) {
            if (Constants.DEBUG_MODE) {
                String url = conn.getURL().toExternalForm();
                if ("Keep-Alive".equals(conn.getRequestProperty("Connection")))
                    method = "postFile(), targetURL";
                Log.d(TAG, "Problem execute " + method + ": " + url + " HTTP response: " + code);
            }
            if (readErrorResponseBody)
                response.setResponseBody(responseToString(conn.getErrorStream()));
            return response;
        }

        String body = responseToString(conn.getInputStream());
        if (null == body) {
            response.setResponseCode(ERROR_DOWNLOAD_DATA);
            response.setResponseMessage(null);
            return response;
        }

        response.setResponseBody(body);
        response.setOk(true);
        return response;
    }

    public static HttpResponse get(
            String targetURL,
            String username,
            String password,
            boolean readErrorResponseBody)
            throws IOException {
        final HttpURLConnection conn = getHttpConnection(HTTP_GET, targetURL, username, password);
        if (null == conn) {
            if (Constants.DEBUG_MODE)
                Log.d(TAG, "Error get connection object: " + targetURL);
            return new HttpResponse(ERROR_CONNECT_FAILED);
        }
        return getHttpResponse(conn, readErrorResponseBody);
    }


    public static HttpResponse post(
            String targetURL,
            String payload,
            String username,
            String password,
            boolean readErrorResponseBody)
            throws IOException
    {
        final HttpURLConnection conn = getHttpConnection(HTTP_POST, targetURL, username, password);
        if (null == conn) {
            if (Constants.DEBUG_MODE)
                Log.d(TAG, "Error get connection object: " + targetURL);
            return new HttpResponse(ERROR_CONNECT_FAILED);
        }
        conn.setRequestProperty("Content-type", "application/json");
        // Allow Outputs
        conn.setDoOutput(true);

        OutputStream os = conn.getOutputStream();
        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(os, "UTF-8"));
        writer.write(payload);

        writer.flush();
        writer.close();
        os.close();

        HttpResponse response = getHttpResponse(conn, readErrorResponseBody);
        if (response.mResponseCode == HttpURLConnection.HTTP_MOVED_PERM && conn.getURL().getProtocol().equals("http")) {
            targetURL = targetURL.replace("http", "https");
            return post(targetURL, payload, username, password, readErrorResponseBody);
        }
        return response;
    }

    public static HttpResponse delete(
            String targetURL,
            String username,
            String password,
            boolean readErrorResponseBody)
            throws IOException
    {
        final HttpURLConnection conn = getHttpConnection(HTTP_DELETE, targetURL, username, password);
        if (null == conn) {
            if (Constants.DEBUG_MODE)
                Log.d(TAG, "Error get connection object: " + targetURL);
            return new HttpResponse(ERROR_CONNECT_FAILED);
        }
        return getHttpResponse(conn, readErrorResponseBody);
    }


    public static HttpResponse put(
            String targetURL,
            String payload,
            String username,
            String password,
            boolean readErrorResponseBody)
            throws IOException
    {
        final HttpURLConnection conn = getHttpConnection(HTTP_PUT, targetURL, username, password);
        if (null == conn) {
            if (Constants.DEBUG_MODE)
                Log.d(TAG, "Error get connection object: " + targetURL);
            return new HttpResponse(ERROR_CONNECT_FAILED);
        }
        conn.setRequestProperty("Content-type", "application/json");
        // Allow Outputs
        conn.setDoOutput(true);

        OutputStream os = conn.getOutputStream();
        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(os, "UTF-8"));
        writer.write(payload);

        writer.flush();
        writer.close();
        os.close();

        HttpResponse response = getHttpResponse(conn, readErrorResponseBody);
        if (response.mResponseCode == HttpURLConnection.HTTP_MOVED_PERM && conn.getURL().getProtocol().equals("http")) {
            targetURL = targetURL.replace("http", "https");
            return put(targetURL, payload, username, password, readErrorResponseBody);
        }
        return response;
    }


    public static HttpResponse postFile(
            String targetURL,
            String fileName,
            File file,
            String fileMime,
            String username,
            String password,
            boolean readErrorResponseBody)
            throws IOException
    {
        final String lineEnd = "\r\n";
        final String twoHyphens = "--";
        final String boundary = "**nextgis**";

        //------------------ CLIENT REQUEST
        FileInputStream fileInputStream = new FileInputStream(file);
        // open a URL connection to the Servlet

        HttpURLConnection conn = getHttpConnection(HTTP_POST, targetURL, username, password);
        if (null == conn) {
            if (Constants.DEBUG_MODE)
                Log.d(TAG, "Error get connection object: " + targetURL);
            return new HttpResponse(ERROR_CONNECT_FAILED);
        }
        conn.setRequestProperty("Connection", "Keep-Alive");
        conn.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary);
        // Allow Outputs
        conn.setDoOutput(true);

        DataOutputStream dos = new DataOutputStream(conn.getOutputStream());
        dos.writeBytes(twoHyphens + boundary + lineEnd);
        dos.writeBytes(
                "Content-Disposition: form-data; name=\"file\"; filename=\"" + fileName + "\"" +
                lineEnd);

        if (!TextUtils.isEmpty(fileMime)) {
            dos.writeBytes("Content-Type: " + fileMime + lineEnd);
        }

        dos.writeBytes(lineEnd);

        byte[] buffer = new byte[Constants.IO_BUFFER_SIZE];
        FileUtil.copyStream(fileInputStream, dos, buffer, Constants.IO_BUFFER_SIZE);

        dos.writeBytes(lineEnd);
        dos.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd);
        fileInputStream.close();
        dos.flush();
        dos.close();

        HttpResponse response = getHttpResponse(conn, readErrorResponseBody);
        if (response.mResponseCode == HttpURLConnection.HTTP_MOVED_PERM && conn.getURL().getProtocol().equals("http")) {
            targetURL = targetURL.replace("http", "https");
            return postFile(targetURL, fileName, file, fileMime, username, password, readErrorResponseBody);
        }
        return response;
    }

    public static String trimSlash(String url) {
        return url.endsWith("/") ? url.substring(0, url.length() - 1) : url;
    }

    public static String getError(
            Context context,
            int responseCode)
    {
        switch (responseCode) {
            case ERROR_AUTH:
                return context.getString(R.string.error_auth);
            case ERROR_NETWORK_UNAVAILABLE:
                return context.getString(R.string.error_network_unavailable);
            case ERROR_CONNECT_FAILED:
                return context.getString(R.string.error_connect_failed);
            case ERROR_DOWNLOAD_DATA:
                return context.getString(R.string.error_download_data);
            case HttpURLConnection.HTTP_UNAUTHORIZED:
                return context.getString(R.string.error_401);
            case HttpURLConnection.HTTP_FORBIDDEN:
                return context.getString(R.string.error_403);
            case HttpURLConnection.HTTP_NOT_FOUND:
                return context.getString(R.string.error_404);
            case HttpURLConnection.HTTP_INTERNAL_ERROR:
                return context.getString(R.string.error_500);
            default:
                return context.getString(R.string.error_500);
        }
    }
}
import android.accounts.Account;
import android.content.Context;
import android.content.SyncResult;
import android.graphics.Bitmap;
import android.util.Log;
import android.util.Pair;
import com.nextgis.maplib.api.IGISApplication;
import com.nextgis.maplib.api.INGWLayer;
import com.nextgis.maplib.datasource.Geo;
import com.nextgis.maplib.datasource.GeoEnvelope;
import com.nextgis.maplib.datasource.TileItem;
import com.nextgis.maplib.util.AccountUtil;
import com.nextgis.maplib.util.Constants;
import com.nextgis.maplib.util.HttpResponse;
import com.nextgis.maplib.util.NGWUtil;
import com.nextgis.maplib.util.NetworkUtil;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.io.IOException;
import static com.nextgis.maplib.util.Constants.JSON_EXTENT_KEY;
import static com.nextgis.maplib.util.Constants.JSON_ID_KEY;
import static com.nextgis.maplib.util.Constants.JSON_MAX_LAT_KEY;
import static com.nextgis.maplib.util.Constants.JSON_MAX_LON_KEY;
import static com.nextgis.maplib.util.Constants.JSON_MIN_LAT_KEY;
import static com.nextgis.maplib.util.Constants.JSON_MIN_LON_KEY;
import static com.nextgis.maplib.util.Constants.LAYERTYPE_NGW_RASTER;
import static com.nextgis.maplib.util.GeoConstants.MERCATOR_MAX;
/*
 * Project:  NextGIS Mobile
 * Purpose:  Mobile GIS for Android.
 * Author:   Dmitry Baryshnikov (aka Bishop), bishop.dev@gmail.com
 * Author:   NikitaFeodonit, nfeodonit@yandex.com
 * Author:   Stanislav Petriakov, becomeglory@gmail.com
 * *****************************************************************************
 * Copyright (c) 2012-2017 NextGIS, info@nextgis.com
 *
 * 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.map;






public class NGWRasterLayer extends RemoteTMSLayer implements INGWLayer {
    protected String mAccountName;
    protected String mCacheLogin;
    protected String mCachePassword;
    protected long mRemoteId;
    protected boolean mExtentReceived = false;

    protected final static short MAX_THREAD_COUNT = 8;
    protected static final String JSON_ACCOUNT_KEY = "account";

    public NGWRasterLayer(Context context, File path) {
        super(context, path);
        mLayerType = LAYERTYPE_NGW_RASTER;
        mExtents.set(new GeoEnvelope(-MERCATOR_MAX, MERCATOR_MAX, -MERCATOR_MAX, MERCATOR_MAX));
    }

    @Override
    public JSONObject toJSON() throws JSONException {
        JSONObject rootConfig = super.toJSON();
        rootConfig.put(JSON_ID_KEY, mRemoteId);
        rootConfig.put(JSON_ACCOUNT_KEY, mAccountName);
        rootConfig.put(Constants.JSON_BBOX_MAXX_KEY, mExtents.getMaxX());
        rootConfig.put(Constants.JSON_BBOX_MINX_KEY, mExtents.getMinX());
        rootConfig.put(Constants.JSON_BBOX_MAXY_KEY, mExtents.getMaxY());
        rootConfig.put(Constants.JSON_BBOX_MINY_KEY, mExtents.getMinY());

        return rootConfig;
    }

    @Override
    public void fromJSON(JSONObject jsonObject) throws JSONException {
        super.fromJSON(jsonObject);
        setAccountName(jsonObject.getString(JSON_ACCOUNT_KEY));
        mRemoteId = jsonObject.optInt(JSON_ID_KEY);
        mExtents.setMaxX(jsonObject.optDouble(Constants.JSON_BBOX_MAXX_KEY, MERCATOR_MAX));
        mExtents.setMinX(jsonObject.optDouble(Constants.JSON_BBOX_MINX_KEY, -MERCATOR_MAX));
        mExtents.setMaxY(jsonObject.optDouble(Constants.JSON_BBOX_MAXY_KEY, MERCATOR_MAX));
        mExtents.setMinY(jsonObject.optDouble(Constants.JSON_BBOX_MINY_KEY, -MERCATOR_MAX));
    }

    public void setAccountCacheData() {
        IGISApplication app = (IGISApplication) mContext.getApplicationContext();
        Account account = app.getAccount(mAccountName);

        if (null != account) {
            mCacheLogin = app.getAccountLogin(account);
            mCachePassword = app.getAccountPassword(account);
            if (Constants.DEBUG_MODE)
                Log.d(Constants.TAG, "Get account. User: " + mCacheLogin);
        } else if (Constants.DEBUG_MODE) {
            Log.d(Constants.TAG, "Failed to get account for name: " + mAccountName);
        }
    }

    @Override
    public void sync(String authority, Pair<Integer, Integer> ver, SyncResult syncResult) {
        // TODO: 26.07.15 By now nothing to sync
    }

    @Override
    public int getSyncType() {
        return Constants.SYNC_NONE;
    }

    @Override
    public void setSyncType(int syncType) {

    }

    @Override
    public long getRemoteId() {
        return mRemoteId;
    }

    @Override
    public void setRemoteId(long remoteId) {
        mRemoteId = remoteId;
    }

    @Override
    public int getMaxThreadCount() {
        return MAX_THREAD_COUNT;
    }

    @Override
    public String getAccountName() {
        return mAccountName;
    }

    @Override
    public void setAccountName(String accountName) {
        mAccountName = accountName;
        setAccountCacheData();
    }

    @Override
    public String getLogin() {
        return mCacheLogin;
    }

    @Override
    public void setLogin(String login) {
        throw new AssertionError("NGWRasterLayer.setLogin() can not be used");
    }

    @Override
    public String getPassword() {
        return mCachePassword;
    }

    @Override
    public void setPassword(String password) {
        throw new AssertionError("NGWRasterLayer.setPassword() can not be used");
    }

    @Override
    public Bitmap getBitmap(TileItem tile) {
        if (!mExtentReceived) {
            HttpResponse result = null;