/**
 * Copyright (c) 2013, Redsolution LTD. All rights reserved.
 * 
 * This file is part of Xabber project; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License, Version 3.
 * 
 * Xabber 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 General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License,
 * along with this program. If not, see http://www.gnu.org/licenses/.
 */
package com.xabber.android.data.account;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.ParseException;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;

import android.net.Uri;

import com.xabber.android.data.Application;
import com.xabber.android.data.LogManager;
import com.xabber.android.data.NetworkException;
import com.xabber.android.classic.R;

class WLMManager implements OAuthProvider {

	private static enum GrantType {

		authorizationCode("authorization_code", "code"),

		refreshToken("refresh_token", "refresh_token");

		public final String name;
		public final String value;

		private GrantType(String name, String value) {
			this.name = name;
			this.value = value;
		}

	}

	private static final String WLM_CLIENT_SECRET = "XEazfSKu0Iu2pt6Z64Lqm-1cRxtEYgS0";
	private static final String WLM_CLIENT_ID = "00000000440923FF";
	private static final String WLM_SCOPE = "wl.messenger wl.offline_access";
	private static final String WLM_SCHEME = "https";
	private static final String WLM_AUTHORITY = "oauth.live.com";
	private static final String WLM_REDIRECT_PATH = "/desktop";
	private static final String WLM_REDIRECT_URL = new Uri.Builder()
			.scheme(WLM_SCHEME).authority(WLM_AUTHORITY)
			.path(WLM_REDIRECT_PATH).build().toString();

	private final static WLMManager instance;

	static {
		instance = new WLMManager();
		Application.getInstance().addManager(instance);
	}

	public static WLMManager getInstance() {
		return instance;
	}

	private WLMManager() {
	}

	/**
	 * @param protocol
	 * @param grantType
	 * @param value
	 * @return Access and refresh tokens or <code>null</code> if auth failed.
	 * @throws NetworkException
	 */
	private OAuthResult accessTokenOperation(GrantType grantType, String value)
			throws NetworkException {
		HttpPost httpPost = new HttpPost(new Uri.Builder().scheme(WLM_SCHEME)
				.authority(WLM_AUTHORITY).path("token").build().toString());
		List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();
		nameValuePairs
				.add(new BasicNameValuePair("grant_type", grantType.name));
		nameValuePairs.add(new BasicNameValuePair(grantType.value, value));
		nameValuePairs.add(new BasicNameValuePair("redirect_uri",
				WLM_REDIRECT_URL));
		nameValuePairs.add(new BasicNameValuePair("client_id", WLM_CLIENT_ID));
		nameValuePairs.add(new BasicNameValuePair("client_secret",
				WLM_CLIENT_SECRET));
		UrlEncodedFormEntity encodedFormEntity;
		try {
			encodedFormEntity = new UrlEncodedFormEntity(nameValuePairs,
					HTTP.UTF_8);
		} catch (UnsupportedEncodingException e) {
			throw new NetworkException(R.string.CONNECTION_FAILED, e);
		}
		String content;
		try {
			content = EntityUtils.toString(encodedFormEntity);
		} catch (ParseException e) {
			throw new NetworkException(R.string.CONNECTION_FAILED, e);
		} catch (IOException e) {
			throw new NetworkException(R.string.CONNECTION_FAILED, e);
		}
		LogManager.i(this, httpPost.getURI().toString() + "\n" + content);
		httpPost.setEntity(encodedFormEntity);
		HttpClient httpClient = new DefaultHttpClient();
		HttpResponse httpResponse;
		try {
			httpResponse = httpClient.execute(httpPost);
		} catch (ClientProtocolException e) {
			throw new NetworkException(R.string.CONNECTION_FAILED, e);
		} catch (IOException e) {
			throw new NetworkException(R.string.CONNECTION_FAILED, e);
		}
		HttpEntity entity = httpResponse.getEntity();
		try {
			content = EntityUtils.toString(entity);
		} catch (ParseException e) {
			throw new NetworkException(R.string.CONNECTION_FAILED, e);
		} catch (IOException e) {
			throw new NetworkException(R.string.CONNECTION_FAILED, e);
		} finally {
			try {
				entity.consumeContent();
			} catch (IOException e) {
				throw new NetworkException(R.string.CONNECTION_FAILED, e);
			}
		}
		LogManager.i(this, content);
		long expiresIn;
		String accessToken;
		String refreshToken;
		try {
			JSONObject jsonObject = (JSONObject) new JSONTokener(content)
					.nextValue();
			if (jsonObject.has("error"))
				return null;
			try {
				expiresIn = Long.valueOf(jsonObject.getString("expires_in")) * 1000;
			} catch (NumberFormatException e) {
				throw new NetworkException(R.string.CONNECTION_FAILED, e);
			}
			accessToken = jsonObject.getString("access_token");
			refreshToken = jsonObject.getString("refresh_token");
		} catch (JSONException e) {
			throw new NetworkException(R.string.CONNECTION_FAILED, e);
		}
		return new OAuthResult(accessToken, refreshToken, expiresIn);
	}

	@Override
	public AccountProtocol getAccountProtocol() {
		return AccountProtocol.wlm;
	}

	@Override
	public String requestRefreshToken(String code) throws NetworkException {
		OAuthResult result = accessTokenOperation(GrantType.authorizationCode,
				code);
		if (result == null)
			return null;
		else
			return result.getRefreshToken();
	}

	@Override
	public OAuthResult requestAccessToken(String refreshToken)
			throws NetworkException {
		return accessTokenOperation(GrantType.refreshToken, refreshToken);
	}

	@Override
	public String getUrl() {
		return new Uri.Builder().scheme(WLM_SCHEME).authority(WLM_AUTHORITY)
				.path("authorize")
				.appendQueryParameter("response_type", "code")
				.appendQueryParameter("client_id", WLM_CLIENT_ID)
				.appendQueryParameter("redirect_uri", WLM_REDIRECT_URL)
				.appendQueryParameter("scope", WLM_SCOPE)
				.appendQueryParameter("display", "touch").build().toString();
	}

	@Override
	public boolean isValidUri(Uri uri) {
		return WLM_SCHEME.equals(uri.getScheme())
				&& WLM_AUTHORITY.equals(uri.getAuthority())
				&& WLM_REDIRECT_PATH.equals(uri.getPath());
	}

}
