module yamalib.donload.download;

pragma(lib, "ws2_32.lib ");
private import std.string; // replace, toString
private import std.socket;
private import std.uri;
private import std.thread;
private import std.stream;
private import std.file;
private import y4d_aux.lineparser;


private import yamalib.auxil.properties;
private import yamalib.util.systeminfo;


/** ファイル受信スレッドクラス */
class Receiver : Thread {
	this(Socket sock) 
	in
	{
		assert( !(sock is null) );
	}
	body
	{
		m_sock = sock;
		m_file = new File("C:\\tmp\\test.txt", FileMode.OutNew);
	}
	
	~this() {
		m_file.flush();
		m_file.close();
	}
	
	/// 処理関数
	override int run() {
		try {
			recev(m_file);
		} catch (Object o) {
			printf("Receiver#run : EXCEPTION : \n");
			o.print();
		} finally {
			m_received = true;
			m_file.close();
		}
		
		printf("ENNNND\n");
		return 0;
	}
	
	/// 受信が完了した
	bool isReceived() {
		return m_received;
	}
	
	/// 受信済みバイトの取得
	ulong getReceivedByte() {
		return m_recivSize;
	}
	
	/// 受信ファイルサイズ
	ulong getReceiveSize() {
		return m_DLSize;
	}
	
private:
	/// 受信処理
	void recev(Stream stream) {
		int n;
		ubyte[] buffer = new ubyte[BUFFER_SIZE];
		m_DLSize = getRecvSize(stream);

printf("DLSIZE : %ld\n", m_DLSize);
		
		while(true) {
			if ( (m_DLSize-stream.size()) < buffer.length ) {
				buffer = new ubyte[m_DLSize-stream.size()];
			}
			n = m_sock.receive(buffer);
			if (0 >= n) {
				break;
			}
			m_recivSize += n;
			stream.write(buffer);
		}
	}
	
	/// ダウンロードするコンテンツのサイズを取得する
	ulong getRecvSize(Stream stream) {
printf("getRecvSize\n");		
		ubyte[32] buffer;
		m_headerData = null;
		ulong size = 0UL;
		int n;
		while (true) {
			n = m_sock.receive(buffer);
			if (n <= 0) {
				return size;
			}
//printf("DATA : %s\n", std.string.toStringz(buffer));
//mem.write(buffer);

			m_headerData ~= cast(char[]) buffer;
			
			if ( std.string.find(m_headerData, HEADER_DELIM) >= 0 ) {
				int pos = std.string.find(m_headerData, HEADER_DELIM);
				ubyte[] restData = cast(ubyte[]) m_headerData[(pos+4)..length];

//printf("len : %d\n", restData.length);
//printf("RESTDATA : %s\n", std.string.toStringz(restData));
				// 余計に読んだ分を書き込む
				stream.write(restData);
				
				m_headerData.length = pos;
				
				break;
			}
		}

		return getSizeFromHeader(m_headerData);
	}
	
	/// ヘッダーからコンテンツの長さを取得する
	ulong getSizeFromHeader(char[] header) {
printf("getSizeFromHeader : %*s\n", header);		
		MemoryStream mem = new MemoryStream(header);
		
		while (!mem.eof) {
			char[] linebuf = cast(char[]) mem.readLine();
			if ( 0 > std.string.find(linebuf, CONTEXT_LEN) ) {
				continue;
			}
	
			LineParser lp = new LineParser();
			lp.setLine(linebuf);
			lp.isMatch(cast(char[]) CONTEXT_LEN, false);

			return lp.getNum(0UL);
		}
//printf("ERROR : HEADER ERROR\n");		
		return 0uL;
	}

private:
	static const char[] CONTEXT_LEN = "Content-Length: ";
	static const char[] HEADER_DELIM = "\r\n\r\n";
	static const int BUFFER_SIZE = 32;	// BIT数

	Socket m_sock;
	Stream m_file;
	char[] m_headerData;
	bool m_received;
	ulong m_recivSize;
	ulong m_DLSize;
}

class Donloader {

	/// ダウンロードを行う
	bool download(char[] url, char[] seveDir) {
		if (!enable) {
			return true;
		}
		
		if (!open(cast(char[]) "download.forest.impress.co.jp")) {
			return false;
		}
		
		if (!m_sock.isAlive) {
			return false;
		}

		int n = m_sock.send( cast(char[]) HTTP_REQ );
		if ( Socket.ERROR == n ) {
			return false;
		}
		
		m_receiver = new Receiver(m_sock);
		m_receiver.start();
		return true;
	}

	/// ダウンロードが完了したか	
	bool isDonwloaded() {
		if (m_receiver is null) {
			return false;
		}
		return m_receiver.isReceived();
	}

	/// 受信済みバイトの取得
	ulong getReceivedByte() {
		if (m_receiver is null) {
			return 0UL;
		}
		return m_receiver.getReceivedByte();
	}
	
	/// 受信ファイルサイズ
	ulong getReceiveSize() {
		if (m_receiver is null) {
			return 0UL;
		}
		return m_receiver.getReceiveSize();
	}


	/// コンストラクタ
	this() {
		try {
			m_sock = new Socket(cast(AddressFamily) AddressFamily.INET, SocketType.STREAM, cast(ProtocolType) 0);
		} catch (SocketException se) {
			throw se;
		}
		enable = true;
		prop = Properties.getInstance(cast(char[]) PROP_FILE_NAME);
		//prop.load(PROP_FILE_NAME);
	}
	
	~this() {
		try {
			close();
		} catch (Object o) {
			printf("Donloader#destractor : Exception\n");
		}
	}
	
private:

	/// ソケットオープン
	bool open(char[] hostname) {
		if (!enable) {
			return true;
		}
		
		// 通信先
		InternetAddress addr;

		char[] proxy = prop.getProperty(cast(char[]) PROP_KEY_PROXY);
		int port = prop.getPropertyNum(cast(char[]) PROP_KEY_PROXY_PORT, -1);

		if (proxy is null || port == -1) {
			addr = new InternetAddress(hostname,80);
		} else {
			addr = new InternetAddress(proxy, port);
		}

		try {
			m_sock.connect(addr);
		} catch (SocketException se) {
			se.print();
			// error
			return false;
		}
		
		if (m_sock.isAlive) {
			return true;
		}
		
		return false;
	}
	
	/// Socketをクローズする
	bool close() {
		if (!enable) {
			return true;
		}

		if (m_sock is null) {
			return true;
		}
		if (!m_sock.isAlive) {
			return true;
		}
		m_sock.shutdown(SocketShutdown.BOTH);
		m_sock.close();
		
		return true;
	}
	
	
	
private:
	static const char[] HTTP_REQ = "GET http://download.forest.impress.co.jp/pub/win/o/oseditor/osed2185.exe HTTP/1.1Host: download.forest.impress.co.jp\r\nUser-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; ja; rv:1.8) Gecko/20051111 Firefox/1.5\r\nAccept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5\r\nAccept-Language: ja,en-us;q=0.7,en;q=0.3\r\nAccept-Encoding: gzip,deflate\r\nAccept-Charset: Shift_JIS,utf-8;q=0.7,*;q=0.7\r\nKeep-Alive: 300\r\nProxy-Connection: keep-alive\r\nReferer: http://www.forest.impress.co.jp/lib/offc/business/schedule/groupwatcher.html\r\nCookie: NGUserID2=ac141f41-9798-1129523374-1\r\n\r\n";
	static const char[] PROP_FILE_NAME = "net_setting.txt";
	static Properties prop;
	static const char[] PROP_KEY_REPORT = "net_report";
	static const char[] PROP_KEY_NAME = "report_name";
	static const char[] PROP_KEY_EMAIL = "email";
	static const char[] PROP_KEY_PROXY = "proxy";
	static const char[] PROP_KEY_PROXY_PORT = "proxy_port";
	Socket m_sock;
	Receiver m_receiver;
	bool enable;

}