/*
Copyright © 2009 Jim Porter
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 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 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 .
*/
//#include "common.h"
#include 
#include 
#include 
#include 
#include "twitter.h"
#include "tinyjson.hpp"
#include 
typedef json::grammar js;
// utility functions
template 
static T cast_and_decode(boost::any &a,bool allow_null)
{
	if (allow_null && a.type() == typeid(void))
		return T();
	return boost::any_cast(a);
}
template <>
static std::string cast_and_decode(boost::any &a,bool allow_null)
{
	if (allow_null && a.type() == typeid(void))
		return std::string();
	std::string s = boost::any_cast(a);
	// Twitter *only* encodes < and >, so decode them
	size_t off;
	while( (off = s.find("<")) != std::string::npos)
		s.replace(off,4,"<");
	while( (off = s.find(">")) != std::string::npos)
		s.replace(off,4,">");
	return s;
}
template 
static T retrieve(const js::object &o,const std::string &key,bool allow_null = false)
{
	using boost::any_cast;
	js::object::const_iterator i = o.find(key);
	if (i == o.end())
		throw std::exception( ("unable to retrieve key '"+key+"'").c_str());
	try
	{
		return cast_and_decode(*i->second,allow_null);
	}
	catch(const boost::bad_any_cast &)
	{
		throw std::exception( ("unable to cast key '"+key+"' to target type").c_str());
	}
}
twitter::twitter() : base_url_("https://twitter.com/")
{}
bool twitter::set_credentials(const std::string &username,const std::string &password,
	bool test)
{
	username_ = username;
	password_ = password;
	if (test)
		return slurp(base_url_+"account/verify_credentials.json",http::get).code == 200;
	else
		return true;
}
void twitter::set_base_url(const std::string &base_url)
{
	base_url_ = base_url;
}
const std::string & twitter::get_username() const
{
	return username_;
}
const std::string & twitter::get_base_url() const
{
	return base_url_;
}
std::vector twitter::get_friends()
{
	std::vector friends;
	http::response resp = slurp(base_url_+"statuses/friends.json",http::get);
	if (resp.code != 200)
		throw bad_response();
	const js::variant var = json::parse( resp.data.begin(),resp.data.end());
	if (var->type() != typeid(js::array))
		throw std::exception("unable to parse response");
	const js::array &list = boost::any_cast(*var);
	for(js::array::const_iterator i=list.begin(); i!=list.end(); ++i)
	{
		if ((*i)->type() == typeid(js::object))
		{
			const js::object &one = boost::any_cast(**i);
			twitter_user user;
			user.username          = retrieve(one,"screen_name");
			user.real_name         = retrieve(one,"name",true);
			user.profile_image_url = retrieve(one,"profile_image_url",true);
			if (one.find("status") != one.end())
			{
				js::object &status  = retrieve(one,"status");
				user.status.text    = retrieve(status,"text");
				user.status.id      = retrieve(status,"id");
				std::string timestr = retrieve(status,"created_at");
				user.status.time    = parse_time(timestr);
			}
			friends.push_back(user);
		}
	}
	return friends;
}
bool twitter::get_info(const std::tstring &name,twitter_user *info)
{
	if (!info)
		return false;
	std::string url = base_url_+"users/show/"+http::url_encode(name)+".json";
	http::response resp = slurp(url,http::get);
	if (resp.code != 200)
		throw bad_response();
	const js::variant var = json::parse( resp.data.begin(),resp.data.end());
	if (var->type() == typeid(js::object))
	{
		const js::object &user_info = boost::any_cast(*var);
		if (user_info.find("error") != user_info.end())
			return false;
		info->username          = retrieve(user_info,"screen_name");
		info->real_name         = retrieve(user_info,"name",true);
		info->profile_image_url = retrieve(user_info,"profile_image_url",true);
		return true;
	}
	else
		return false;
}
bool twitter::get_info_by_email(const std::tstring &email,twitter_user *info)
{
	if (!info)
		return false;
	std::string url = base_url_+"users/show.json?email="+http::url_encode(email);
	http::response resp = slurp(url,http::get);
	if (resp.code != 200)
		throw bad_response();
	js::variant var = json::parse( resp.data.begin(),resp.data.end());
	if (var->type() == typeid(js::object))
	{
		const js::object &user_info = boost::any_cast(*var);
		if (user_info.find("error") != user_info.end())
			return false;
		info->username          = retrieve(user_info,"screen_name");
		info->real_name         = retrieve(user_info,"name",true);
		info->profile_image_url = retrieve(user_info,"profile_image_url",true);
		return true;
	}
	else
		return false;
}
twitter_user twitter::add_friend(const std::tstring &name)
{
	std::string url = base_url_+"friendships/create/"+http::url_encode(name)+".json";
	twitter_user ret;
	http::response resp = slurp(url,http::post);
	if (resp.code != 200)
		throw bad_response();
	js::variant var = json::parse( resp.data.begin(),resp.data.end());
	if (var->type() != typeid(js::object))
		throw std::exception("unable to parse response");
	const js::object &user_info = boost::any_cast(*var);
	ret.username          = retrieve(user_info,"screen_name");
	ret.real_name         = retrieve(user_info,"name",true);
	ret.profile_image_url = retrieve(user_info,"profile_image_url",true);
	if (user_info.find("status") != user_info.end())
	{
		// TODO: fill in more fields
		const js::object &status = retrieve(user_info,"status");
		ret.status.text          = retrieve(status,"text");
	}
	return ret;
}
void twitter::remove_friend(const std::tstring &name)
{
	std::string url = base_url_+"friendships/destroy/"+http::url_encode(name)+".json";
	slurp(url,http::post);
}
void twitter::set_status(const std::tstring &text)
{
	if (text.size())
	{
		slurp(base_url_+"statuses/update.json",http::post,
			"status="+http::url_encode(text)+
			"&source=mirandaim");
	}
}
void twitter::send_direct(const std::tstring &name,const std::tstring &text)
{
	slurp(base_url_+"direct_messages/new.json",http::post,
		"user=" +http::url_encode(name)+
		"&text="+http::url_encode(text));
}
std::vector twitter::get_statuses(int count,twitter_id id)
{
	using boost::lexical_cast;
	std::vector statuses;
	std::string url = base_url_+"statuses/friends_timeline.json?count="+
		lexical_cast(count);
	if (id != 0)
		url += "&since_id="+boost::lexical_cast(id);
	http::response resp = slurp(url,http::get);
	if (resp.code != 200)
		throw bad_response();
	js::variant var = json::parse( resp.data.begin(),resp.data.end());
	if (var->type() != typeid(js::array))
		throw std::exception("unable to parse response");
	const js::array &list = boost::any_cast(*var);
	for(js::array::const_iterator i=list.begin(); i!=list.end(); ++i)
	{
		if ((*i)->type() == typeid(js::object))
		{
			const js::object &one  = boost::any_cast(**i);
			const js::object &user = retrieve(one,"user");
			twitter_user u;
			u.username = retrieve(user,"screen_name");
			u.status.text       = retrieve(one,"text");
			u.status.id         = retrieve(one,"id");
			std::string timestr = retrieve(one,"created_at");
			u.status.time       = parse_time(timestr);
			statuses.push_back(u);
		}
	}
	return statuses;
}
std::vector twitter::get_direct(twitter_id id)
{
	std::vector messages;
	std::string url = base_url_+"direct_messages.json";
	if (id != 0)
		url += "?since_id="+boost::lexical_cast(id);
	http::response resp = slurp(url,http::get);
	if (resp.code != 200)
		throw bad_response();
	js::variant var = json::parse( resp.data.begin(),resp.data.end());
	if (var->type() != typeid(js::array))
		throw std::exception("unable to parse response");
	const js::array &list = boost::any_cast(*var);
	for(js::array::const_iterator i=list.begin(); i!=list.end(); ++i)
	{
		if ((*i)->type() == typeid(js::object))
		{
			const js::object &one = boost::any_cast(**i);
			twitter_user u;
			u.username = retrieve(one,"sender_screen_name");
			u.status.text       = retrieve(one,"text");
			u.status.id         = retrieve(one,"id");
			std::string timestr = retrieve(one,"created_at");
			u.status.time       = parse_time(timestr);
			messages.push_back(u);
		}
	}
	return messages;
}
// Some Unices get this, now we do too!
time_t timegm(struct tm *t)
{
	_tzset();
	t->tm_sec -= _timezone;
	t->tm_isdst = 0;
	return mktime(t);
}
static char *month_names[] = { "Jan","Feb","Mar","Apr","May","Jun",
                               "Jul","Aug","Sep","Oct","Nov","Dec" };
int parse_month(const char *m)
{
	for(size_t i=0; i<12; i++)
	{
		if (strcmp(month_names[i],m) == 0)
			return i;
	}
	return -1;
}
time_t parse_time(const std::string &s)
{
	struct tm t;
	char day[4],month[4];
	char plus;
	int zone;
	if (sscanf(s.c_str(),"%3s %3s %d %d:%d:%d %c%d %d",
		day,month,&t.tm_mday,&t.tm_hour,&t.tm_min,&t.tm_sec,
		&plus,&zone,&t.tm_year) == 9)
	{
		t.tm_year -= 1900;
		t.tm_mon = parse_month(month);
		if (t.tm_mon == -1)
			return 0;
		return timegm(&t);
	}
	return 0;
}