/*
 * Copyright 2013 Netatmo
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.ntels.onecable.admin.common.netatmo;

import com.android.volley.DefaultRetryPolicy;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.StringRequest;
import com.android.volley.toolbox.Volley;
import com.ntels.onecable.admin.common.util.LogUtil;

import org.json.JSONException;
import org.json.JSONObject;

import android.content.Context;
import android.util.Log;

import java.util.HashMap;
import java.util.Map;

/**
 * <pre>
 * 1. ClassName :   NetatmoHttpClient
 * 2. FileName  :   NetatmoHttpClient.java
 * 3. Package   :   com.ntels.onecable.admin.common.netatmo
 * 4. Date      :   2016-12-06
 * 5. Author    :   aaron
 * 6. Comment   :   Initialize
 * </pre>
 */
abstract public class NetatmoHttpClient {

    private static final String TAG = NetatmoHttpClient.class.getSimpleName();

    //volley library part
    RequestQueue queue;

    // API URLs that will be used for requests, see: https://dev.netatmo.com/doc.
    protected final String URL_BASE = "https://api.netatmo.net";
    protected final String URL_BASE2 = "https://api.netatmo.com";
    protected final String URL_REQUEST_TOKEN = URL_BASE + "/oauth2/token";
    protected final String URL_GET_DEVICES_LIST = URL_BASE + "/api/devicelist";
    protected final String URL_GET_HOME_DATA = URL_BASE + "/api/gethomedata";

    protected final int REQUEST_TIMEOUT = 10000;
    protected final int REQUEST_MAX_RETRIES = 0;

    /**
     * 생성자
     *
     * @para mContext 컨텍스트
     */
    public NetatmoHttpClient(Context context) {
        queue = Volley.newRequestQueue(context);
    }

    /**
     * Post request using volley
     *
     * @param url             Request URL
     * @param params          Request parameter map
     * @param successListener Request success listener
     * @param errorListener   Request error listener
     */
    protected void post(String url, final HashMap<String, String> params, Response.Listener<String> successListener, Response.ErrorListener errorListener) {

        LogUtil.i(TAG, params.toString());
        StringRequest request = new StringRequest(Request.Method.POST, url, successListener, errorListener) {
            @Override
            protected Map<String, String> getParams() {
                return params;
            }
        };

        // 타임아웃, 재시도 설정 셋팅
        request.setRetryPolicy(new DefaultRetryPolicy(REQUEST_TIMEOUT, REQUEST_MAX_RETRIES,
                DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));

        queue.add(request);
    }

    /**
     * <pre>
     * Get request using volley. Since the access token is needed for each GET request to the
     * Netatmo
     * API, we need to check if it has not expired.
     * </pre>
     *
     * @param url             Request URL
     * @param params          Request parameter map
     * @param successListener Request success listener
     * @param errorListener   Request error listener
     */
    protected void get(final String url, final HashMap<String, String> params, final Response.Listener<String> successListener, final Response.ErrorListener errorListener) {

        if (System.currentTimeMillis() >= getExpiresAt()) {
            refreshToken(getRefreshToken(), new Response.Listener<String>() {
                        @Override
                        public void onResponse(String response) {
                            JSONObject jsonObject;
                            try {
                                jsonObject = new JSONObject(response);
                                processOAuthResponse(jsonObject);
                                params.put("access_token", getAccessToken());
                                post(url, params, successListener, errorListener);
                            } catch (JSONException e) {
                                e.printStackTrace();
                            }
                        }
                    },
                    new Response.ErrorListener() {
                        @Override
                        public void onErrorResponse(VolleyError error) {
                            Log.e("errorRefreshToken", error.toString());
                        }
                    });
        } else {
            params.put("access_token", getAccessToken());
            Log.i("Netatmo", "Netatmo access token : " + getAccessToken());
            post(url, params, successListener, errorListener);
        }

    }

    /**
     * <pre>
     * This is the first request you have to do before being able to use the API. It allows you to
     * retrieve an access token in one step, using your application's credentials and the user's
     * credentials.
     * </pre>
     *
     * @param email           Login email
     * @param password        Login password
     * @param successListener Request success listener
     * @param errorListener   Request error listener
     */
    public void login(String email, String password, Response.Listener<String> successListener, Response.ErrorListener errorListener) {
        HashMap<String, String> params = new HashMap<>();
        params.put("grant_type", "password");
        params.put("client_id", getClientId());
        params.put("client_secret", getClientSecret());
        params.put("username", email);
        params.put("password", password);
        params.put("scope", getAppScope());

        post(URL_REQUEST_TOKEN, params, successListener, errorListener);

    }

    /**
     * <pre>
     * This is the first request you have to do before being able to use the API. It allows you to
     * retrieve an access token in one step, using your application's credentials and the user's
     * credentials.
     * </pre>
     *
     * @param authToken       User auth token
     * @param successListener Request success listener
     * @param errorListener   Request error listener
     */
    public void login(String authToken, Response.Listener<String> successListener, Response.ErrorListener errorListener) {
        HashMap<String, String> params = new HashMap<>();
        params.put("grant_type", "authorization_code");
        params.put("client_id", getClientId());
        params.put("client_secret", getClientSecret());
        params.put("code", authToken);
        params.put("redirect_uri", "kneet-app://getOauthCode");
        params.put("scope", getAppScope());

        post(URL_REQUEST_TOKEN, params, successListener, errorListener);

    }

    /**
     * <pre>
     * Once an access token has been obtained, it can be used immediately to access the REST API.
     * After a certain amount of time, the access token expires and the application needs to use
     * the
     * refresh token to renew the access token. Both the refresh token and the expiration time are
     * obtained during the authentication phase.
     * </pre>
     *
     * @param refreshToken    User refresh token
     * @param successListener Request success listener
     * @param errorListener   Request error listener
     */
    public void refreshToken(String refreshToken, Response.Listener<String> successListener, Response.ErrorListener errorListener) {
        HashMap<String, String> params = new HashMap<>();
        params.put("grant_type", "refresh_token");
        params.put("refresh_token", refreshToken);
        params.put("client_id", getClientId());
        params.put("client_secret", getClientSecret());

        post(URL_REQUEST_TOKEN, params, successListener, errorListener);
    }

    /**
     * <pre>
     * Returns the list of devices owned by the user, and their modules. A device is identified by
     * its
     * _id (which is its mac address) and each device may have one, several or no modules, also
     * identified by an _id. See <a href="https://dev.netatmo.com/doc/methods/devicelist">dev.netatmo.com/doc/methods/devicelist</a>
     * for more information.
     * </pre>
     *
     * @param successListener Request success listener
     * @param errorListener   Request error listener
     */
    public void getDevicesList(Response.Listener<String> successListener, Response.ErrorListener errorListener) {
        get(URL_GET_DEVICES_LIST, new HashMap<String, String>(), successListener, errorListener);
    }

    /**
     * Get user home data which has Netatmo Camera information.
     *
     * @param successListener Request success listener
     * @param errorListener   Request error listener
     */
    public void getHomeData(Response.Listener<String> successListener, Response.ErrorListener errorListener) {
        get(URL_GET_HOME_DATA, new HashMap<String, String>(), successListener, errorListener);
    }

    /**
     * Making sure to call {@link #storeTokens(String, String, long)} with proper values.
     *
     * @param response Response json object
     */
    public void processOAuthResponse(JSONObject response) {
        HashMap<String, String> parsedResponse = NetatmoUtils.parseOAuthResponse(response);
        storeTokens(parsedResponse.get(NetatmoUtils.KEY_REFRESH_TOKEN),
                parsedResponse.get(NetatmoUtils.KEY_ACCESS_TOKEN),
                Long.valueOf(parsedResponse.get(NetatmoUtils.KEY_EXPIRES_AT)));
    }


    /**
     * You can get your client id by creating a Netatmo app first: <a href="https://dev.netatmo.com/dev/createapp">dev.netatmo.com/dev/createapp</a>
     *
     * @return CLIENT_ID
     */
    protected abstract String getClientId();

    /**
     * You can get your client secret by creating a Netatmo app first: <a
     * href="https://dev.netatmo.com/dev/createapp">dev.netatmo.com/dev/createapp</a>
     *
     * @return CLIENT_SECRET
     */
    protected abstract String getClientSecret();

    /**
     * The application can require different scopes depending on the action it will need to
     * execute.
     * <a href="https://dev.netatmo.com/doc/authentication/scopes">dev.netatmo.com/doc/authentication/scopes</a>
     *
     * @return APP_SCOPE
     */
    protected abstract String getAppScope();

    /**
     * You have to call this method to store the different tokens
     *
     * @param refreshToken User refresh token
     * @param accessToken  User access token
     * @param expiresAt    Expired time of accessToken
     */
    protected abstract void storeTokens(String refreshToken, String accessToken, long expiresAt);

    /**
     * Called when the user sign out
     */
    protected abstract void clearTokens();

    /**
     * Return the refresh token stored by {@link #storeTokens(String, String, long)}.
     *
     * @return refreshToken
     */
    public abstract String getRefreshToken();

    /**
     * Return the access token stored by {@link #storeTokens(String, String, long)}.
     *
     * @return accessToken
     */
    public abstract String getAccessToken();

    /**
     * Return the expiration date, of the refreshToken, stored by {@link #storeTokens(String,
     * String,
     * long)}.
     *
     * @return expiresAt
     */
    protected abstract long getExpiresAt();

}
