package appengine.util;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.xml.parsers.ParserConfigurationException;

import org.apache.commons.io.IOUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.xml.sax.SAXException;

import com.google.appengine.api.datastore.Blob;
import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityNotFoundException;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.Text;
import com.google.appengine.repackaged.com.google.common.util.Base64DecoderException;

import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;

import static org.junit.Assert.assertThat;

/**
 * @author shin1ogawa
 */
public class DatastoreXmlUtilTest {

	/**
	 * テスト用のXMLを読み込ませる。
	 * <p>Keyは自動採番モードと名称指定モード</p>
	 * @throws Base64DecoderException 
	 * @throws ClassNotFoundException 
	 * @throws ParserConfigurationException 
	 * @throws IOException 
	 * @throws SAXException 
	 * @throws EntityNotFoundException 
	 */
	@Test
	public void read() throws SAXException, IOException, ParserConfigurationException,
			ClassNotFoundException, Base64DecoderException, EntityNotFoundException {
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		PrintWriter writer = new PrintWriter(bos);
		writeTestXml(writer);
		writer.flush();
		IOUtils.closeQuietly(writer);
		byte[] byteArray = bos.toByteArray();
		ByteArrayInputStream bis = new ByteArrayInputStream(byteArray);
		List<Entity> newEntities = DatastoreXmlUtil.readFromXml(bis);
		IOUtils.closeQuietly(bis);
		assertThat(newEntities.size(), is(equalTo(4)));
		assertThat(newEntities.get(0).getKey().getName(), is(nullValue()));
		assertThat((String) newEntities.get(0).getProperty("property1"), is(equalTo("hoge")));
		assertThat(newEntities.get(1).getKey().getName(), is(nullValue()));
		assertThat((String) newEntities.get(1).getProperty("property1"), is(equalTo("fuga")));
		assertThat(newEntities.get(2).getKey().getName(), is(equalTo("piyo")));
		assertThat((String) newEntities.get(2).getProperty("property1"), is(equalTo("piyo")));
		assertThat(newEntities.get(3).getKey().getName(), is(equalTo("ponyo")));
		assertThat((String) newEntities.get(3).getProperty("property1"), is(equalTo("ponyo")));

		// 問題なく保存できる
		List<Key> keys = new ArrayList<Key>(newEntities.size());
		for (Entity entity : newEntities) {
			keys.add(DatastoreServiceUtil.putWithRetry(entity, 3));
		}
		// 問題なく読み込める
		for (Key key : keys) {
			DatastoreServiceFactory.getDatastoreService().get(key);
		}
	}

	private void writeTestXml(PrintWriter writer) {
		writer.println("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
		writer.println("<entities>");
		writer.println("<entity kind=\"Kind1\">");
		writer.println("<property name=\"property1\" type=\"java.lang.String\">hoge</property>");
		writer.println("</entity>");
		writer.println("<entity kind=\"Kind1\">");
		writer.println("<property name=\"property1\" type=\"java.lang.String\">fuga</property>");
		writer.println("</entity>");
		writer.println("<entity kind=\"Kind2\" key-name=\"piyo\">");
		writer.println("<property name=\"property1\" type=\"java.lang.String\">piyo</property>");
		writer.println("</entity>");
		writer.println("<entity kind=\"Kind2\" key-name=\"ponyo\">");
		writer.println("<property name=\"property1\" type=\"java.lang.String\">ponyo</property>");
		writer.println("</entity>");
		writer.println("</entities>");
	}

	/**
	 * Xmlへの出力と読み込みのテスト。
	 * <ul>
	 * <li>{@link DatastoreXmlUtil#writeToXml(PrintWriter, Entity)}</li>
	 * <li>{@link DatastoreXmlUtil#readFromXml(java.io.InputStream)}</li>
	 * </ul>
	 * @throws EntityNotFoundException
	 * @throws SAXException
	 * @throws IOException
	 * @throws ParserConfigurationException
	 * @throws ClassNotFoundException
	 * @throws Base64DecoderException
	 */
	@Test
	public void writeAndRead() throws EntityNotFoundException, SAXException, IOException,
			ParserConfigurationException, ClassNotFoundException, Base64DecoderException {
		DatastoreService service = DatastoreServiceFactory.getDatastoreService();
		// テストデータを作って、
		Key[] keys = createSampleEntities(service);
		// xml形式で出力したものをバイト配列に格納して、
		byte[] bytes = write(service, keys);
		// テストデータを全部消して、
		for (Key key : keys) {
			DatastoreServiceUtil.deleteWithRetry(service.get(key).getKey(), 5);
		}
		// 空っぽになった事を確認して、
		assertThat(service.prepare(new com.google.appengine.api.datastore.Query("Kind3"))
			.countEntities(), is(equalTo(0)));
		assertThat(service.prepare(new com.google.appengine.api.datastore.Query("Kind2"))
			.countEntities(), is(equalTo(0)));
		assertThat(service.prepare(new com.google.appengine.api.datastore.Query("Kind1"))
			.countEntities(), is(equalTo(0)));
		// xmlからの登録を試みる。
		ByteArrayInputStream input = new ByteArrayInputStream(bytes);
		List<Entity> newEntities = DatastoreXmlUtil.readFromXml(input);
		IOUtils.closeQuietly(input);
		for (Entity entity : newEntities) {
			DatastoreServiceUtil.putWithRetry(entity, 10);
		}
		Entity entity1 = service.get(keys[0]);
		assertThat((Long) entity1.getProperty("property1"), is(equalTo(1L)));
		assertThat((Long) entity1.getProperty("property2"), is(equalTo(2L)));
		assertThat((String) entity1.getProperty("property3"), is(equalTo("property3-value")));
		assertThat(((Text) entity1.getProperty("property4")).getValue(),
				is(equalTo("property4-value")));
		assertThat(new String(((Blob) entity1.getProperty("property5")).getBytes()),
				is(equalTo("property5-value")));
		Entity entity2 = service.get(keys[1]);
		assertThat((Key) entity2.getProperty("entity-key"), is(equalTo(keys[0])));
		Entity entity3 = service.get(keys[2]);
		assertThat(entity3.getParent(), is(equalTo(keys[0])));
	}

	private byte[] write(DatastoreService service, Key[] keys) throws EntityNotFoundException,
			IOException {
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		PrintWriter writer = new PrintWriter(new OutputStreamWriter(bos));
		writer.println("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
		writer.println("<entities>");
		for (Key key : keys) {
			DatastoreXmlUtil.writeToXml(writer, service.get(key));
		}
		writer.println("</entities>");
		writer.flush();
		IOUtils.closeQuietly(writer);
		return bos.toByteArray();
	}

	/**
	 * ファイルへの保存なしモードで準備。
	 * @throws IOException 
	 */
	@Before
	public void setUp() throws IOException {
		if (AppEngineUtil.isLocalDevelopment()) {
			TestUtil.setUpAppEngine("gae-j-sandbox", "gae-j-sandbox.1",
					"target/DatastoreXmlUtilTest", true);
		}
	}

	/**
	 * 終了。
	 */
	@After
	public void tearDown() {
		if (AppEngineUtil.isLocalDevelopment()) {
			TestUtil.tearDownAppEngine();
		}
	}

	private Key[] createSampleEntities(DatastoreService service) {
		Entity entity1 = new Entity("Kind1");
		entity1.setProperty("property1", Long.valueOf(1L));
		entity1.setProperty("property2", Integer.valueOf(2));
		entity1.setProperty("property3", "property3-value");
		entity1.setUnindexedProperty("property4", new Text("property4-value"));
		entity1.setUnindexedProperty("property5", new Blob("property5-value".getBytes()));
		Key entity1Key = service.put(entity1);

		Entity entity2 = new Entity("Kind2");
		entity2.setProperty("entity-key", entity1Key);
		entity2.setUnindexedProperty("entity-body", new Blob(LocalDataFileUtilTest
			.getBytes(entity1)));
		Key entity2Key = service.put(entity2);

		Entity entity3 = new Entity("Kind3", entity1Key);
		entity3.setProperty("property1", "hoge");
		Set<String> set = new HashSet<String>();
		set.add("apple");
		set.add("google");
		set.add("yahoo");
		entity3.setProperty("property2", set);
		Key entity3Key = service.put(entity3);
		return new Key[] {
			entity1Key,
			entity2Key,
			entity3Key
		};
	}
}
