content: package allbegray.slack.webapi;
  
  import allbegray.slack.RestUtils;
  import allbegray.slack.exception.SlackArgumentException;
  import allbegray.slack.exception.SlackException;
  import allbegray.slack.exception.SlackResponseErrorException;
  import allbegray.slack.rtm.ProxyServerInfo;
  import allbegray.slack.type.*;
  import allbegray.slack.validation.Problem;
  import allbegray.slack.validation.ValidationError;
  import allbegray.slack.webapi.method.SlackMethod;
  import allbegray.slack.webapi.method.bots.BotInfoMethod;
  import allbegray.slack.webapi.method.channels.*;
  import allbegray.slack.webapi.method.chats.ChatDeleteMethod;
  import allbegray.slack.webapi.method.chats.ChatMeMessageMethod;
  import allbegray.slack.webapi.method.chats.ChatPostMessageMethod;
  import allbegray.slack.webapi.method.chats.ChatUpdateMethod;
  import allbegray.slack.webapi.method.dnd.*;
  import allbegray.slack.webapi.method.emoji.EmojiListMethod;
  import allbegray.slack.webapi.method.files.*;
  import allbegray.slack.webapi.method.files.comments.FileCommentAddMethod;
  import allbegray.slack.webapi.method.files.comments.FileCommentDeleteMethod;
  import allbegray.slack.webapi.method.files.comments.FileCommentEditMethod;
  import allbegray.slack.webapi.method.groups.*;
  import allbegray.slack.webapi.method.im.*;
  import allbegray.slack.webapi.method.mpim.*;
  import allbegray.slack.webapi.method.oauth.OAuthAccessMethod;
  import allbegray.slack.webapi.method.pins.PinsAddMethod;
  import allbegray.slack.webapi.method.pins.PinsListMethod;
  import allbegray.slack.webapi.method.pins.PinsRemoveMethod;
  import allbegray.slack.webapi.method.reactions.ReactionsAddMethod;
  import allbegray.slack.webapi.method.reactions.ReactionsGetMethod;
  import allbegray.slack.webapi.method.reactions.ReactionsListMethod;
  import allbegray.slack.webapi.method.reactions.ReactionsRemoveMethod;
  import allbegray.slack.webapi.method.reminders.*;
  import allbegray.slack.webapi.method.rtm.RtmStartMethod;
  import allbegray.slack.webapi.method.stars.StarsAddMethod;
  import allbegray.slack.webapi.method.stars.StarsListMethod;
  import allbegray.slack.webapi.method.stars.StarsRemoveMethod;
  import allbegray.slack.webapi.method.team.TeamAccessLogsMethod;
  import allbegray.slack.webapi.method.team.TeamInfoMethod;
  import allbegray.slack.webapi.method.team.TeamIntegrationLogMethod;
  import allbegray.slack.webapi.method.test.AuthTestMethod;
  import allbegray.slack.webapi.method.usergroups.*;
  import allbegray.slack.webapi.method.usergroups.users.UsergroupsUsersListMethod;
  import allbegray.slack.webapi.method.usergroups.users.UsergroupsUsersUpdateMethod;
  import allbegray.slack.webapi.method.users.*;
  import com.fasterxml.jackson.core.type.TypeReference;
  import com.fasterxml.jackson.databind.JsonNode;
  import com.fasterxml.jackson.databind.ObjectMapper;
  import org.apache.http.HttpEntity;
  import org.apache.http.impl.client.CloseableHttpClient;
  
  import java.io.FileInputStream;
  import java.io.FileNotFoundException;
  import java.io.IOException;
  import java.io.InputStream;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.List;
  import java.util.Map;
  
  public class SlackWebApiClientImpl implements SlackWebApiClient {
  
  	private String token;
  	private ObjectMapper mapper;
  	private CloseableHttpClient httpClient;
  	private String webApiUrl = SlackWebApiConstants.SLACK_WEB_API_URL;
  
  	public SlackWebApiClientImpl(String token) {
  		this(token, null, SlackWebApiConstants.DEFAULT_TIMEOUT, null);
  	}
  
  	public SlackWebApiClientImpl(String token, ProxyServerInfo proxyServerInfo) {
  		this(token, null, SlackWebApiConstants.DEFAULT_TIMEOUT, proxyServerInfo);
  	}
  
  	public SlackWebApiClientImpl(String token, ObjectMapper mapper) {
  		this(token, mapper, SlackWebApiConstants.DEFAULT_TIMEOUT, null);
  	}
  
  	public SlackWebApiClientImpl(String token, ObjectMapper mapper, ProxyServerInfo proxyServerInfo) {
  		this(token, mapper, SlackWebApiConstants.DEFAULT_TIMEOUT, proxyServerInfo);
  	}
  
  	public SlackWebApiClientImpl(String token, ObjectMapper mapper, int timeout) {
  		this(token, mapper, timeout, null);
  	}
  
  	public SlackWebApiClientImpl(String token, ObjectMapper mapper, int timeout, ProxyServerInfo proxyServerInfo) {
  		this.token = token;
  		this.mapper = mapper != null ? mapper : new ObjectMapper();
  		httpClient = proxyServerInfo != null ? RestUtils.createHttpClient(timeout, proxyServerInfo) : RestUtils.createHttpClient(timeout);
  	}
  
  	@Override
  	public void setWebApiUrl(String webApiUrl) {
  		this.webApiUrl = webApiUrl;
  	}
  
  	@Override
  	public String getWebApiUrl() {
  		return webApiUrl;
  	}
  
  	@Override
  	public void shutdown() {
  		if (httpClient != null) try { httpClient.close(); } catch (Exception e) {}
  	}
  
  	// auth methods
  
  	@Override
  	public Authentication auth() {
  		JsonNode retNode = call(new AuthTestMethod());
  		return readValue(retNode, null, Authentication.class);
  	}
  
  	// bots
  
  	@Override
  	public Bot getBotInfo(String bot) {
  		BotInfoMethod method = new BotInfoMethod();
  		method.setBot(bot);
  		JsonNode retNode = call(method);
  		return readValue(retNode, "bot", Bot.class);
  	}
  
  	// channels
  
  	@Override
  	public boolean archiveChannel(String channel) {
  		return isOk(new ChannelArchiveMethod(channel));
  	}
  
  	@Override
  	public Channel createChannel(String name) {
  		JsonNode retNode = call(new ChannelCreateMethod(name));
  		return readValue(retNode, "channel", Channel.class);
  	}
  
  	@Override
  	public History getChannelHistory(String channel) {
  		return getChannelHistory(channel, null, null, false, SlackWebApiConstants.DEFAULT_COUNT, false);
  	}
  
  	@Override
  	public History getChannelHistory(String channel, int count) {
  		return getChannelHistory(channel, null, null, false, count, false);
  	}
  
  	@Override
  	public History getChannelHistory(String channel, String latest, String oldest, boolean inclusive, int count, boolean unreads) {
  
  		ChannelHistoryMethod channelHistoryMethod = new ChannelHistoryMethod(channel);
  		channelHistoryMethod.setLatest(latest);
  		channelHistoryMethod.setOldest(oldest);
  		channelHistoryMethod.setInclusive(inclusive);
  		channelHistoryMethod.setCount(count);
  		channelHistoryMethod.setUnreads(unreads);
  
  		JsonNode retNode = call(channelHistoryMethod);
  		return readValue(retNode, null, History.class);
  	}
  
  	@Override
  	public Channel getChannelInfo(String channel) {
  		JsonNode retNode = call(new ChannelInfoMethod(channel));
  		return readValue(retNode, "channel", Channel.class);
  	}
  
  	@Override
  	public Channel inviteUserToChannel(String channel, String user) {
  		JsonNode retNode = call(new ChannelInviteMethod(channel, user));
  		return readValue(retNode, "channel", Channel.class);
  	}
  
  	@Override
  	public Channel joinChannel(String name) {
  		JsonNode retNode = call(new ChannelJoinMethod(name));
  		return readValue(retNode, "channel", Channel.class);
  	}
  
  	@Override
  	public boolean kickUserFromChannel(String channel, String user) {
  		return isOk(new ChannelKickMethod(channel, user));
  	}
  
  	@Override
  	public boolean leaveChannel(String channel) {
  		return isOk(new ChannelLeaveMethod(channel));
  	}
  
  	@Override
  	public List<Channel> getChannelList() {
  		return getChannelList(false);
  	}
  
  	@Override
  	public List<Channel> getChannelList(boolean exclude_archived) {
  		JsonNode retNode = call(new ChannelListMethod(exclude_archived));
  
  		return readValue(retNode, "channels", new TypeReference<List<Channel>>() {
  		});
  	}
  
  	@Override
  	public boolean markChannel(String channel, String ts) {
  		return isOk(new ChannelMarkMethod(channel, ts));
  	}
  
  	@Override
  	public Channel renameChannel(String channel, String name) {
  		JsonNode retNode = call(new ChannelRenameMethod(channel, name));
  		return readValue(retNode, "channel", Channel.class);
  	}
  
  	@Override
  	public boolean setChannelPurpose(String channel, String purpose) {
  		return isOk(new ChannelSetPurposeMethod(channel, purpose));
  	}
  
  	@Override
  	public boolean setChannelTopic(String channel, String topic) {
  		return isOk(new ChannelSetTopicMethod(channel, topic));
  	}
  
  	@Override
  	public boolean unarchiveChannel(String channel) {
  		return isOk(new ChannelUnarchiveMethod(channel));
  	}
  
  	// chat
  
  	@Override
  	public boolean deleteMessage(String channel, String ts) {
  		return isOk(new ChatDeleteMethod(channel, ts));
  	}
  
  	@Override
  	public boolean meMessage(String channel, String text) {
  		return isOk(new ChatMeMessageMethod(channel, text));
  	}
  
  	@Override
  	public String postMessage(String channel, String text) {
  		return postMessage(new ChatPostMessageMethod(channel, text));
  	}
  
  	@Override
  	public String postMessage(String channel, String text, String username, boolean as_user) {
  		ChatPostMessageMethod method = new ChatPostMessageMethod(channel, text);
  		method.setUsername(username);
  		method.setAs_user(as_user);
  
  		return postMessage(method);
  	}
  
  	@Override
  	public String postMessage(String channel, String text, String username, boolean as_user, boolean link_names, List<Attachment> attachments, boolean unfurl_links, boolean unfurl_media,
  							  String icon_url, String icon_emoji) {
  
  		ChatPostMessageMethod method = new ChatPostMessageMethod(channel, text);
  		method.setUsername(username);
  		method.setLink_names(link_names);
  		method.setAttachments(attachments);
  		method.setUnfurl_links(unfurl_links);
  		method.setUnfurl_media(unfurl_media);
  		method.setIcon_url(icon_url);
  		method.setIcon_emoji(icon_emoji);
  
  		return postMessage(method);
  	}
  
  	@Override
  	public String postMessage(ChatPostMessageMethod method) {
  		if (method.getMapper() == null) {
  			method.setMapper(mapper);
  		}
  
  		JsonNode retNode = call(method);
  		return retNode.findPath("ts").asText();
  	}
  
  	@Override
  	public String updateMessage(String channel, String ts, String text) {
  		return updateMessage(channel, ts, text, null, false);
  	}
  
  	@Override
  	public String updateMessage(String channel, String ts, String text, List<Attachment> attachments, boolean link_names) {
  		ChatUpdateMethod method = new ChatUpdateMethod(channel, ts, text);
  		method.setAttachments(attachments);
  		method.setLink_names(link_names);
  		method.setMapper(mapper);
  
  		JsonNode retNode = call(method);
  		return retNode.findPath("ts").asText();
  	}
  
  	// dnd
  
  	@Override
  	public boolean endDnd() {
  		return isOk(new EndDndMethod());
  	}
  
  	@Override
  	public EndSnooze endSnooze() {
  		JsonNode retNode = call(new EndSnoozeMethod());
  		return readValue(retNode, null, EndSnooze.class);
  	}
  
  	@Override
  	public SetSnooze setSnooze(int num_minutes) {
  		JsonNode retNode = call(new SetSnoozeMethod(String.valueOf(num_minutes)));
  		return readValue(retNode, null, SetSnooze.class);
  	}
  
  	@Override
  	public DndInfo getDndInfo() {
  		return this.getDndInfo(null);
  	}
  
  	@Override
  	public DndInfo getDndInfo(String user) {
  		JsonNode retNode = call(new DndInfoMethod(user));
  		return readValue(retNode, null, DndInfo.class);
  	}
  
  	@Override
  	public Map<String, DndSimpleInfo> getDndTeamInfo() {
  		return this.getDndTeamInfo(null);
  	}
  
  	@Override
  	public Map<String, DndSimpleInfo> getDndTeamInfo(List<String> users) {
  		JsonNode retNode = call(new DndTeamInfoMethod(users));
  		return readValue(retNode, "users", new TypeReference<Map<String, DndSimpleInfo>>() {
  		});
  	}
  
  	// emoji
  
  	@Override
  	public Map<String, String> getEmojiList() {
  		JsonNode retNode = call(new EmojiListMethod());
  
  		return readValue(retNode, "emoji", new TypeReference<Map<String, String>>() {
  		});
  	}
  
  	// files.comments
  
  	@Override
  	public Comment addFileComment(String file, String comment) {
  		JsonNode retNode = call(new FileCommentAddMethod(file, comment));
  
  		return readValue(retNode, "comment", Comment.class);
  	}
  
  	@Override
  	public Comment editFileComment(String file, String id, String comment) {
  		JsonNode retNode = call(new FileCommentEditMethod(file, id, comment));
  
  		return readValue(retNode, "comment", Comment.class);
  	}
  
  	@Override
  	public boolean deleteFileComment(String file, String id) {
  		return isOk(new FileCommentDeleteMethod(file, id));
  	}
  
  	// files
  
  	@Override
  	public boolean deleteFile(String file) {
  		return isOk(new FileDeleteMethod(file));
  	}
  
  	@Override
  	public FileInfo getFileInfo(String file) {
  		return getFileInfo(file, SlackWebApiConstants.DEFAULT_PAGE);
  	}
  
  	@Override
  	public FileInfo getFileInfo(String file, int page) {
  		return getFileInfo(file, page, SlackWebApiConstants.DEFAULT_COUNT);
  	}
  
  	@Override
  	public FileInfo getFileInfo(String file, int page, int count) {
  		FileInfoMethod method = new FileInfoMethod(file);
  		method.setPage(page);
  		method.setCount(count);
  
  		JsonNode retNode = call(method);
  		return readValue(retNode, null, FileInfo.class);
  	}
  
  	@Override
  	public FileList getFileList(String user, String ts_from, String ts_to, String types, int page, int count) {
  		FileListMethod method = new FileListMethod();
  		method.setUser(user);
  		method.setTs_from(ts_from);
  		method.setTs_to(ts_to);
  		method.setTypes(types);
  		method.setPage(page);
  		method.setCount(count);
  
  		JsonNode retNode = call(method);
  		return readValue(retNode, null, FileList.class);
  	}
  
  	@Override
  	public File revokeFilePublicURL(String file) {
  		JsonNode retNode = call(new FileRevokePublicURLMethod(file));
  		return readValue(retNode, "file", File.class);
  	}
  
  	@Override
  	public File sharedFilePublicURL(String file) {
  		JsonNode retNode = call(new FileSharedPublicURLMethod(file));
  		return readValue(retNode, "file", File.class);
  	}
  
  	@Override
  	public File uploadFile(java.io.File file, String title, String initial_comment, String channels) {
  		String filename = file.getName();
  		String filetype = null;
  		int i = filename.lastIndexOf('.');
  		if (i > 0) {
  			filetype = filename.substring(i + 1);
  		}
  		return uploadFile(file, filetype, filename, title, initial_comment, channels);
  	}
  
  	@Override
  	public File uploadFile(java.io.File file, String filetype, String filename, String title, String initial_comment, String channels) {
  		try {
  			return uploadFile(new FileInputStream(file), filetype, filename, title, initial_comment, channels);
  		} catch (FileNotFoundException e) {
  			throw new RuntimeException(e);
  		}
  	}
  
  	@Override
  	public File uploadFile(InputStream is, String filetype, String filename, String title, String initial_comment, String channels) {
  		FileUploadMethod method = new FileUploadMethod();
  		method.setChannels(channels);
  		method.setFilename(filename);
  		method.setFiletype(filetype);
  		method.setInitial_comment(initial_comment);
  		method.setTitle(title);
  
  		JsonNode retNode = call(method, is);
  		return readValue(retNode, "file", File.class);
  	}
  
  	// groups
  
  	@Override
  	public boolean archiveGroup(String channel) {
  		return isOk(new GroupArchiveMethod(channel));
  	}
  
  	@Override
  	public boolean closeGroup(String channel) {
  		return isOk(new GroupCloseMethod(channel));
  	}
  
  	@Override
  	public Group createGroup(String name) {
  		JsonNode retNode = call(new GroupCreateMethod(name));
  		return readValue(retNode, "group", Group.class);
  	}
  
  	@Override
  	public Group createChildGroup(String name) {
  		JsonNode retNode = call(new GroupCreateChildMethod(name));
  		return readValue(retNode, "group", Group.class);
  	}
  
  	@Override
  	public History getGroupHistory(String channel) {
  		return getGroupHistory(channel, null, null, false, SlackWebApiConstants.DEFAULT_COUNT, false);
  	}
  
  	@Override
  	public History getGroupHistory(String channel, int count) {
  		return getGroupHistory(channel, null, null, false, count, false);
  	}
  
  	@Override
  	public History getGroupHistory(String channel, String latest, String oldest, boolean inclusive, int count, boolean unreads) {
  
  		GroupHistoryMethod channelHistoryMethod = new GroupHistoryMethod(channel);
  		channelHistoryMethod.setLatest(latest);
  		channelHistoryMethod.setOldest(oldest);
  		channelHistoryMethod.setInclusive(inclusive);
  		channelHistoryMethod.setCount(count);
  		channelHistoryMethod.setUnreads(unreads);
  
  		JsonNode retNode = call(channelHistoryMethod);
  		return readValue(retNode, null, History.class);
  	}
  
  	@Override
  	public Group getGroupInfo(String channel) {
  		JsonNode retNode = call(new GroupInfoMethod(channel));
  		return readValue(retNode, "group", Group.class);
  	}
  
  	@Override
  	public Group inviteUserToGroup(String channel, String user) {
  		JsonNode retNode = call(new GroupInviteMethod(channel, user));
  		return readValue(retNode, "group", Group.class);
  	}
  
  	@Override
  	public boolean kickUserFromGroup(String channel, String user) {
  		return isOk(new GroupKickMethod(channel, user));
  	}
  
  	@Override
  	public boolean leaveGroup(String channel) {
  		return isOk(new GroupLeaveMethod(channel));
  	}
  
  	@Override
  	public List<Group> getGroupList() {
  		return getGroupList(false);
  	}
  
  	@Override
  	public List<Group> getGroupList(boolean exclude_archived) {
  		JsonNode retNode = call(new GroupListMethod(exclude_archived));
  
  		return readValue(retNode, "groups", new TypeReference<List<Group>>() {
  		});
  	}
  
  	@Override
  	public boolean markGroup(String channel, String ts) {
  		return isOk(new GroupMarkMethod(channel, ts));
  	}
  
  	@Override
  	public boolean openGroup(String channel) {
  		return isOk(new GroupOpenMethod(channel));
  	}
  
  	@Override
  	public Group renameGroup(String channel, String name) {
  		JsonNode retNode = call(new GroupRenameMethod(channel, name));
  		return readValue(retNode, "channel", Group.class);
  	}
  
  	@Override
  	public boolean setGroupPurpose(String channel, String purpose) {
  		return isOk(new GroupSetPurposeMethod(channel, purpose));
  	}
  
  	@Override
  	public boolean setGroupTopic(String channel, String topic) {
  		return isOk(new GroupSetTopicMethod(channel, topic));
  	}
  
  	@Override
  	public boolean unarchiveGroup(String channel) {
  		return isOk(new GroupUnarchiveMethod(channel));
  	}
  
  	// im (direct message channel)
  
  	@Override
  	public boolean closeDirectMessageChannel(String channel) {
  		return isOk(new ImCloseMethod(channel));
  	}
  
  	@Override
  	public History getDirectMessageChannelHistory(String channel) {
  		return getDirectMessageChannelHistory(channel, null, null, false, SlackWebApiConstants.DEFAULT_COUNT, false);
  	}
  
  	@Override
  	public History getDirectMessageChannelHistory(String channel, int count) {
  		return getDirectMessageChannelHistory(channel, null, null, false, count, false);
  	}
  
  	@Override
  	public History getDirectMessageChannelHistory(String channel, String latest, String oldest, boolean inclusive, int count, boolean unreads) {
  
  		ImHistoryMethod imHistoryMethod = new ImHistoryMethod(channel);
  		imHistoryMethod.setLatest(latest);
  		imHistoryMethod.setOldest(oldest);
  		imHistoryMethod.setInclusive(inclusive);
  		imHistoryMethod.setCount(count);
  		imHistoryMethod.setUnreads(unreads);
  
  		JsonNode retNode = call(imHistoryMethod);
  		return readValue(retNode, null, History.class);
  	}
  
  	@Override
  	public List<DirectMessageChannel> getDirectMessageChannelList() {
  		JsonNode retNode = call(new ImListMethod());
  
  		return readValue(retNode, "ims", new TypeReference<List<DirectMessageChannel>>() {
  		});
  	}
  
  	@Override
  	public boolean markDirectMessageChannel(String channel, String ts) {
  		return isOk(new ImMarkMethod(channel, ts));
  	}
  
  	@Override
  	public String openDirectMessageChannel(String user) {
          JsonNode retNode = call(new ImOpenMethod(user));
  
  		return retNode.findPath("channel").findPath("id").asText();
  	}
  
  	// mpim (multiparty direct message channel)
  
  	@Override
  	public boolean closeMultipartyDirectMessageChannel(String channel) {
  		return isOk(new MpimCloseMethod(channel));
  	}
  
  	@Override
  	public History getMultipartyDirectMessageChannelHistory(String channel) {
  		return getMultipartyDirectMessageChannelHistory(channel, null, null, false, SlackWebApiConstants.DEFAULT_COUNT, false);
  	}
  
  	@Override
  	public History getMultipartyDirectMessageChannelHistory(String channel, int count) {
  		return getMultipartyDirectMessageChannelHistory(channel, null, null, false, count, false);
  	}
  
  	@Override
  	public History getMultipartyDirectMessageChannelHistory(String channel, String latest, String oldest, boolean inclusive, int count, boolean unreads) {
  
  		MpimHistoryMethod mpimHistoryMethod = new MpimHistoryMethod(channel);
  		mpimHistoryMethod.setLatest(latest);
  		mpimHistoryMethod.setOldest(oldest);
  		mpimHistoryMethod.setInclusive(inclusive);
  		mpimHistoryMethod.setCount(count);
  		mpimHistoryMethod.setUnreads(unreads);
  
  		JsonNode retNode = call(mpimHistoryMethod);
  		return readValue(retNode, null, History.class);
  	}
  
  	@Override
  	public List<Group> getMultipartyDirectMessageChannelList() {
  		JsonNode retNode = call(new MpimListMethod());
  		return readValue(retNode, "groups", new TypeReference<List<Group>>() {
  		});
  	}
  
  	@Override
  	public boolean markMultipartyDirectMessageChannel(String channel, String ts) {
  		return isOk(new MpimMarkMethod(channel, ts));
  	}
  
  	@Override
  	public Group openMultipartyDirectMessageChannel(String... users) {
  		return openMultipartyDirectMessageChannel(Arrays.asList(users));
  	}
  
  	@Override
  	public Group openMultipartyDirectMessageChannel(List<String> users) {
  		JsonNode retNode = call(new MpimOpenMethod(users));
  		return readValue(retNode, "group", Group.class);
  	}
  
  	// oauth
  
  	@Override
  	public OAuthAccessToken accessOAuth(String client_id, String client_secret, String code, String redirect_uri) {
  		OAuthAccessMethod method = new OAuthAccessMethod(client_id, client_secret, code);
  		method.setRedirect_uri(redirect_uri);
  
  		JsonNode retNode = call(method);
  		return readValue(retNode, null, OAuthAccessToken.class);
  	}
  
  	// pins
  
  	@Override
  	public boolean pinFile(String channel, String file) {
  		PinsAddMethod method = new PinsAddMethod(channel);
  		method.setFile(file);
  
  		return isOk(method);
  	}
  
  	@Override
  	public boolean pinFileComment(String channel, String file_comment) {
  		PinsAddMethod method = new PinsAddMethod(channel);
  		method.setFile_comment(file_comment);
  
  		return isOk(method);
  	}
  
  	@Override
  	public boolean pinMessage(String channel, String timestamp) {
  		PinsAddMethod method = new PinsAddMethod(channel);
  		method.setTimestamp(timestamp);
  
  		return isOk(method);
  	}
  
  	@Override
  	public List<PinItem> getPinList(String channel) {
  		JsonNode retNode = call(new PinsListMethod(channel));
  		return readValue(retNode, "items", new TypeReference<List<PinItem>>() {
  		});
  	}
  
  	@Override
  	public boolean unpinFile(String channel, String file) {
  		PinsRemoveMethod method = new PinsRemoveMethod(channel);
  		method.setFile(file);
  
  		return isOk(method);
  	}
  
  	@Override
  	public boolean unpinFileComment(String channel, String file_comment) {
  		PinsRemoveMethod method = new PinsRemoveMethod(channel);
  		method.setFile_comment(file_comment);
  
  		return isOk(method);
  	}
  
  	@Override
  	public boolean unpinMessage(String channel, String timestamp) {
  		PinsRemoveMethod method = new PinsRemoveMethod(channel);
  		method.setTimestamp(timestamp);
  
  		return isOk(method);
  	}
  
  	// reactions
  
  	@Override
  	public boolean addReactionToFile(String emojiName, String file) {
  		ReactionsAddMethod method = new ReactionsAddMethod(emojiName);
  		method.setFile(file);
  
  		return isOk(method);
  	}
  
  	@Override
  	public boolean addReactionToFileComment(String emojiName, String file_comment) {
  		ReactionsAddMethod method = new ReactionsAddMethod(emojiName);
  		method.setFile_comment(file_comment);
  
  		return isOk(method);
  	}
  
  	@Override
  	public boolean addReactionToMessage(String emojiName, String channel, String timestamp) {
  		ReactionsAddMethod method = new ReactionsAddMethod(emojiName);
  		method.setChannel(channel);
  		method.setTimestamp(timestamp);
  
  		return isOk(method);
  	}
  
  	@Override
  	public ReactionItem getReactionByFile(String emojiName, String file) {
  		ReactionsGetMethod method = new ReactionsGetMethod(emojiName);
  		method.setFile(file);
  
  		JsonNode retNode = call(method);
  		return readValue(retNode, null, ReactionItem.class);
  	}
  
  	@Override
  	public ReactionItem getReactionByFileComment(String emojiName, String file_comment) {
  		ReactionsGetMethod method = new ReactionsGetMethod(emojiName);
  		method.setFile_comment(file_comment);
  
  		JsonNode retNode = call(method);
  		return readValue(retNode, null, ReactionItem.class);
  	}
  
  	@Override
  	public ReactionItem getReactionByMessage(String emojiName, String channel, String timestamp) {
  		ReactionsGetMethod method = new ReactionsGetMethod(emojiName);
  		method.setChannel(channel);
  		method.setTimestamp(timestamp);
  
  		JsonNode retNode = call(method);
  		return readValue(retNode, null, ReactionItem.class);
  	}
  
  	@Override
  	public ReactionList getReactionList(int page) {
  		return this.getReactionList(page, SlackWebApiConstants.DEFAULT_COUNT);
  	}
  
  	@Override
  	public ReactionList getReactionList(int page, int count) {
  		return this.getReactionList(null, page, count);
  	}
  
  	@Override
  	public ReactionList getReactionList(String user, int page) {
  		return this.getReactionList(user, page, SlackWebApiConstants.DEFAULT_COUNT);
  	}
  
  	@Override
  	public ReactionList getReactionList(String user, int page, int count) {
  		ReactionsListMethod method = new ReactionsListMethod();
  		method.setUser(user);
  		method.setPage(page);
  		method.setCount(count);
  
  		JsonNode retNode = call(method);
  		return readValue(retNode, null, ReactionList.class);
  	}
  
  	@Override
  	public boolean removeReactionFromFile(String emojiName, String file) {
  		ReactionsRemoveMethod method = new ReactionsRemoveMethod(emojiName);
  		method.setFile(file);
  
  		return isOk(method);
  	}
  
  	@Override
  	public boolean removeReactionFromFileComment(String emojiName, String file_comment) {
  		ReactionsRemoveMethod method = new ReactionsRemoveMethod(emojiName);
  		method.setFile_comment(file_comment);
  
  		return isOk(method);
  	}
  
  	@Override
  	public boolean removeReactionFromMessage(String emojiName, String channel, String timestamp) {
  		ReactionsRemoveMethod method = new ReactionsRemoveMethod(emojiName);
  		method.setChannel(channel);
  		method.setTimestamp(timestamp);
  
  		return isOk(method);
  	}
  
      // Reminders
  
      @Override
      public boolean addReminder(String text, String time) {
          RemindersAddMethod method = new RemindersAddMethod(text, time);
          return isOk(method);
      }
  
      @Override
      public boolean completeReminder(String reminderId) {
          RemindersCompleteMethod method = new RemindersCompleteMethod(reminderId);
          return isOk(method);
      }
  
      @Override
      public boolean deleteReminder(String reminderId) {
          RemindersDeleteMethod method = new RemindersDeleteMethod(reminderId);
          return isOk(method);
      }
  
      @Override
      public boolean addReminder(String text, long time) {
          RemindersAddMethod method = new RemindersAddMethod(text, time);
          return isOk(method);
      }
  
      @Override
      public ReminderInfo getReminderInfo(String reminderId) {
  
          RemindersInfoMethod method = new RemindersInfoMethod(reminderId);
  
          JsonNode retNode = call(method);
          return readValue(retNode, null, ReminderInfo.class);
      }
  
      @Override
      public ReminderList getReminderList() {
          RemindersListMethod method = new RemindersListMethod();
  
          JsonNode retNode = call(method);
          return readValue(retNode, null, ReminderList.class);
      }
  
  	// rtm
  
  	@Override
  	public JsonNode startRealTimeMessagingApi() {
  		return startRealTimeMessagingApi(null, null, null);
  	}
  
  	@Override
  	public JsonNode startRealTimeMessagingApi(String simple_latest, String no_unreads, String mpim_aware) {
  		RtmStartMethod method = new RtmStartMethod();
  		method.setSimple_latest(simple_latest);
  		method.setNo_unreads(no_unreads);
  		method.setMpim_aware(mpim_aware);
  		return call(method);
  	}
  
  	// search
  
  	// stars
  
  	@Override
  	public boolean addStarToFile(String file) {
  		StarsAddMethod method = new StarsAddMethod();
  		method.setFile(file);
  
  		return isOk(method);
  	}
  
  	@Override
  	public boolean addStarToFileComment(String file_comment) {
  		StarsAddMethod method = new StarsAddMethod();
  		method.setFile_comment(file_comment);
  
  		return isOk(method);
  	}
  
  	@Override
  	public boolean addStarToMessage(String channel, String timestamp) {
  		StarsAddMethod method = new StarsAddMethod();
  		method.setChannel(channel);
  		method.setTimestamp(timestamp);
  
  		return isOk(method);
  	}
  
  	@Override
  	public StarList getStarList(int page) {
  		return this.getStarList(page, SlackWebApiConstants.DEFAULT_COUNT);
  	}
  
  	@Override
  	public StarList getStarList(int page, int count) {
  		return this.getStarList(null, page, count);
  	}
  
  	@Override
  	public StarList getStarList(String user, int page) {
  		return this.getStarList(user, page, SlackWebApiConstants.DEFAULT_COUNT);
  	}
  
  	@Override
  	public StarList getStarList(String user, int page, int count) {
  		StarsListMethod method = new StarsListMethod();
  		method.setUser(user);
  		method.setPage(page);
  		method.setCount(count);
  
  		JsonNode retNode = call(method);
  		return readValue(retNode, null, StarList.class);
  	}
  
  	@Override
  	public boolean removeStarFromFile(String file) {
  		StarsRemoveMethod method = new StarsRemoveMethod();
  		method.setFile(file);
  
  		return isOk(method);
  	}
  
  	@Override
  	public boolean removeStarFromFileComment(String file_comment) {
  		StarsRemoveMethod method = new StarsRemoveMethod();
  		method.setFile_comment(file_comment);
  
  		return isOk(method);
  	}
  
  	@Override
  	public boolean removeStarFromMessage(String channel, String timestamp) {
  		StarsRemoveMethod method = new StarsRemoveMethod();
  		method.setChannel(channel);
  		method.setTimestamp(timestamp);
  
  		return isOk(method);
  	}
  
  	// team
  
  	@Override
  	public TeamAccessLogList getTeamAccessLogList(int page) {
  		return this.getTeamAccessLogList(page, SlackWebApiConstants.DEFAULT_COUNT);
  	}
  
  	@Override
  	public TeamAccessLogList getTeamAccessLogList(int page, int count) {
  		TeamAccessLogsMethod method = new TeamAccessLogsMethod();
  		method.setPage(page);
  		method.setCount(count);
  
  		JsonNode retNode = call(method);
  		return readValue(retNode, null, TeamAccessLogList.class);
  	}
  
  	@Override
  	public Team getTeamInfo() {
  		JsonNode retNode = call(new TeamInfoMethod());
  		return readValue(retNode, "team", Team.class);
  	}
  
  	@Override
  	public TeamIntegrationLogList getTeamIntegrationLogList(int page) {
  		return getTeamIntegrationLogList(null, null, null, null, page, SlackWebApiConstants.DEFAULT_COUNT);
  	}
  
  	@Override
  	public TeamIntegrationLogList getTeamIntegrationLogList(int page, int count) {
  		return getTeamIntegrationLogList(null, null, null, null, page, count);
  	}
  
  	@Override
  	public TeamIntegrationLogList getTeamIntegrationLogList(String service_id, String app_id, String user, String change_type, int page, int count) {
  		TeamIntegrationLogMethod method = new TeamIntegrationLogMethod();
  		method.setService_id(service_id);
  		method.setApp_id(app_id);
  		method.setUser(user);
  		method.setChange_type(change_type);
  		method.setPage(page);
  		method.setCount(count);
  
  		JsonNode retNode = call(method);
  		return readValue(retNode, null, TeamIntegrationLogList.class);
  	}
  
  	// usergroups
  
  	@Override
  	public Usergroup createUsergroup(String name, String handle, String description, List<String> channels) {
  		return createUsergroup(name, handle, description, channels, true);
  	}
  
  	@Override
  	public Usergroup createUsergroup(String name, String handle, String description, List<String> channels, boolean include_count) {
  		UsergroupsCreateMethod method = new UsergroupsCreateMethod(name);
  		method.setHandle(handle);
  		method.setDescription(description);
  		method.setChannels(channels);
  		method.setInclude_count(include_count);
  
  		JsonNode retNode = call(method);
  		return readValue(retNode, "usergroup", Usergroup.class);
  	}
  
  	@Override
  	public Usergroup disableUsergroup(String usergroup) {
  		return disableUsergroup(usergroup, true);
  	}
  
  	@Override
  	public Usergroup disableUsergroup(String usergroup, boolean include_count) {
  		UsergroupsDisableMethod method = new UsergroupsDisableMethod(usergroup);
  		method.setInclude_count(include_count);
  
  		JsonNode retNode = call(method);
  		return readValue(retNode, "usergroup", Usergroup.class);
  	}
  
  	@Override
  	public Usergroup enableUsergroup(String usergroup) {
  		return enableUsergroup(usergroup, true);
  	}
  
  	@Override
  	public Usergroup enableUsergroup(String usergroup, boolean include_count) {
  		UsergroupsEnableMethod method = new UsergroupsEnableMethod(usergroup);
  		method.setInclude_count(include_count);
  
  		JsonNode retNode = call(method);
  		return readValue(retNode, "usergroup", Usergroup.class);
  	}
  
  	@Override
  	public List<Usergroup> getUsergroupList() {
  		return getUsergroupList(true, true, true);
  	}
  
  	@Override
  	public List<Usergroup> getUsergroupList(boolean include_disabled, boolean include_count, boolean include_users) {
  		UsergroupsListMethod method = new UsergroupsListMethod();
  		method.setInclude_disabled(include_disabled);
  		method.setInclude_count(include_count);
  		method.setInclude_users(include_users);
  
  		JsonNode retNode = call(method);
  		return readValue(retNode, "usergroups", new TypeReference<List<Usergroup>>() {
  		});
  	}
  
  	@Override
  	public Usergroup updateUsergroup(String name, String handle, String description, List<String> channels) {
  		return updateUsergroup(name, handle, description, channels, true);
  	}
  
  	@Override
  	public Usergroup updateUsergroup(String name, String handle, String description, List<String> channels, boolean include_count) {
  		UsergroupsUpdateMethod method = new UsergroupsUpdateMethod(name);
  		method.setHandle(handle);
  		method.setDescription(description);
  		method.setChannels(channels);
  		method.setInclude_count(include_count);
  
  		JsonNode retNode = call(method);
  		return readValue(retNode, "usergroup", Usergroup.class);
  	}
  
  	// usergroups.users
  
  	@Override
  	public List<String> getUsergroupUserList(String usergroup) {
  		return getUsergroupUserList(usergroup, false);
  	}
  
  	@Override
  	public List<String> getUsergroupUserList(String usergroup, boolean include_disabled) {
  		UsergroupsUsersListMethod method = new UsergroupsUsersListMethod(usergroup);
  
  		JsonNode retNode = call(method);
  		return readValue(retNode, "users", new TypeReference<List<String>>() {
  		});
  	}
  
  	@Override
  	public Usergroup updateUsergroupUser(String usergroup, List<String> users) {
  		return updateUsergroupUser(usergroup, users, true);
  	}
  
  	@Override
  	public Usergroup updateUsergroupUser(String usergroup, List<String> users, boolean include_count) {
  		UsergroupsUsersUpdateMethod method = new UsergroupsUsersUpdateMethod(usergroup, users);
  		method.setInclude_count(include_count);
  
  		JsonNode retNode = call(method);
  		return readValue(retNode, "usergroup", Usergroup.class);
  	}
  
  	// users
  
  	@Override
  	public UserPresence getUserPresence(String user) {
  		JsonNode retNode = call(new UserGetPresenceMethod(user));
  		return readValue(retNode, null, UserPresence.class);
  	}
  
  	@Override
  	public User getUserInfo(String user) {
  		JsonNode retNode = call(new UserInfoMethod(user));
  		return readValue(retNode, "user", User.class);
  	}
  
  	@Override
  	public User lookupUserByEmail(String email) {
  		JsonNode retNode = call(new UserLookupByEmailMethod(email));
  		return readValue(retNode, "user", User.class);
  	}
  
  	@Override
  	public List<User> getUserList() {
  		JsonNode retNode = call(new UserListMethod());
  		return readValue(retNode, "members", new TypeReference<List<User>>() {
  		});
  	}
  
  	@Override
  	public List<User> getUserListWithPresence() {
  		JsonNode retNode = call(new UserListMethod("1"));
  		return readValue(retNode, "members", new TypeReference<List<User>>() {
  		});
  	}
  
  	@Override
  	public boolean setActiveUser() {
  		return isOk(new UserSetActiveMethod());
  	}
  
  	@Override
  	public boolean setPresenceUser(Presence presence) {
  		if (presence == null) {
  			throw new SlackArgumentException("invalid presence(auto|away)");
  		}
  		return isOk(new UserSetPresenceMethod(presence.name().toLowerCase()));
  	}
  
  	// function
  
  	protected boolean isOk(SlackMethod method) {
  		JsonNode retNode = call(method);
  		return retNode.findPath("ok").asBoolean();
  	}
  
  	protected <T> T readValue(JsonNode node, String findPath, Class<T> valueType) {
  		try {
  			if (findPath != null) {
  				if (!node.has(findPath)) return null;
  				node = node.findPath(findPath);
  			}
  			return mapper.readValue(node.toString(), valueType);
  		} catch (IOException e) {
  			throw new RuntimeException(e);
  		}
  	}
  
  	protected <T> T readValue(JsonNode node, String findPath, TypeReference<?> typeReference) {
  		try {
  			if (findPath != null) {
  				if (!node.has(findPath)) return null;
  				node = node.findPath(findPath);
  			}
  			return mapper.readValue(node.toString(), typeReference);
  		} catch (IOException e) {
  			throw new RuntimeException(e);
  		}
  	}
  
  	protected JsonNode call(SlackMethod method) {
  		return call(method, null);
  	}
  
  	protected JsonNode call(SlackMethod method, InputStream is) {
  
  		List<ValidationError> errors = new ArrayList<ValidationError>();
  		method.validate(errors);
  
  		if (errors.size() > 0) {
  
  			StringBuffer sb = new StringBuffer("*** slack argument error ***");
  			for (ValidationError error : errors) {
  				if (sb.length() > 0) {
  					sb.append("\n");
  				}
  
  				if (error.getDescription() != null) {
  					sb.append(error.getDescription());
  				} else if (error.getProblem() == Problem.REQUIRED) {
  					sb.append("\"" + error.getField() + "\" is required.");
  				}
  			}
  
  			throw new SlackArgumentException(sb.toString());
  		}
  
  		Map<String, String> parameters = method.getParameters();
  		if (method.isRequiredToken()) {
  			parameters.put("token", token);
  		}
  
  		String apiUrl = webApiUrl + "/" + method.getMethodName();
  
  		HttpEntity httpEntity = null;
  		if (is == null) {
  			httpEntity = RestUtils.createUrlEncodedFormEntity(parameters);
  		} else {
  			httpEntity = RestUtils.createMultipartFormEntity(parameters, is);
  		}
  
  		String retContent = RestUtils.execute(httpClient, apiUrl, httpEntity);
  
  		JsonNode retNode = null;
  		try {
  			retNode = mapper.readTree(retContent);
  		} catch (IOException e) {
  			throw new SlackException(e);
  		}
  
  		boolean retOk = retNode.findPath("ok").asBoolean();
  		if (!retOk) {
  			String error = retNode.findPath("error").asText();
  			throw new SlackResponseErrorException(error + ". check the link " + SlackWebApiConstants.SLACK_WEB_API_DOCUMENT_URL + "/" + method.getMethodName());
  		}
  
  		return retNode;
  	}
  
  }
  