// StoreMultStatement はステートメントを単一、もしくは複数挿入するためのハンドラである。
// ステートメントは配列、もしくは単一のJSONの形でリクエストボディに与えられる。
func (c *Controller) StoreMultStatement(params martini.Params, w http.ResponseWriter, req *http.Request) (int, string) {
	w.Header().Set("Access-Control-Allow-Origin", "*")
	w.Header().Set("X-Experience-API-Version", "1.0.2")
	user, app := params["user"], params["app"]

	contentType := req.Header.Get("Content-Type")
	if len(contentType) == 0 {
		contentType = "application/json"
	}
	statements, attachmentSHA2s, err := c.parseRequestBody(req.Body, contentType)
	if err != nil {
		return NewBadRequestErrF("An error occured on parse request: %s", err).Response()
	}

	// xAPI のバージョンを確認, Experience API, Section 6.2 を参照
	xAPIVersion := req.Header.Get("X-Experience-API-Version")
	if err := validator.MultStatement(validator.ToXAPIVersion(xAPIVersion), statements); err != nil {
		return NewBadRequestErrF("Invalid statement: %s", err).Response()
	}

	// attachments がある場合、チェック
	if attachmentSHA2s != nil && !hasSameHashBetween(statements, attachmentSHA2s) {
		return NewBadRequestErr("Unexpected content hash given on attachment or multipart header").Response()
	}

	// authority を取得
	authority := bson.M{
		"objectType": "Agent",
		"account": bson.M{
			"homePage": req.Header.Get(miscs.GlobalConfig.Authority.HomePageHeader),
			"name":     req.Header.Get(miscs.GlobalConfig.Authority.NameHeader),
		},
	}

	docs, insertedIDs, err := parseStatementsAndGetIDs(xAPIVersion, user, app, statements, authority)
	if err != nil {
		return NewBadRequestErrF("Invalid statements: %s", err).Response()
	}

	if status, mess := c.insertIntoDB(xAPIVersion, user, app, docs); status != http.StatusOK {
		return status, mess
	}

	result, err := json.Marshal(insertedIDs)
	if err != nil {
		logger.Err("An unexpected error occured: ", err)
		return http.StatusInternalServerError, "Internal Server Error"
	}

	w.Header().Set("Content-Type", "application/json")
	return http.StatusOK, string(result)
}
// queryOfAgent は agnet の文字列を受け取り、データベースのクエリを返す。
// 引数に与えられた文字列が変換不可ならエラーを返す。
func queryOfAgent(xAPIVersion, agentString string, relatedActivities bool) (bson.M, error) {
	var agent map[string]interface{}
	err := json.Unmarshal([]byte(agentString), &agent)

	if err != nil {
		return nil, err
	}

	if err = validator.Agent(validator.ToXAPIVersion(xAPIVersion), agent); err != nil {
		return nil, err
	}

	terms := termsOfAgent(agent, relatedActivities)

	return queryOfTerms(terms), nil
}
// StoreStatement はステートメントの PUT リクエストを扱うハンドラである。
// このハンドラは与えられたステートメントをバリデートし、データベースに挿入する。
// URLパラメータには UUID (ステートメントID) が与えられており、そのIDのステートメントを挿入する。
func (c *Controller) StoreStatement(params martini.Params, w http.ResponseWriter, req *http.Request) (int, string) {
	w.Header().Set("Access-Control-Allow-Origin", "*")
	w.Header().Set("X-Experience-API-Version", "1.0.2")
	user, app := params["user"], params["app"]

	contentType := req.Header.Get("Content-Type")
	if len(contentType) == 0 {
		contentType = "application/json"
	}
	statements, attachmentSHA2s, err := c.parseRequestBody(req.Body, contentType)
	if err != nil {
		return NewBadRequestErrF("An error occured on parse request: %s", err).Response()
	}
	if len(statements) != 1 {
		return NewBadRequestErr("Statement must be single on PUT request").Response()
	}

	// parseRequestBody でチェックしているため変換可能
	statement := statements[0].(map[string]interface{})

	// xAPI のバージョンを確認, Experience API, Section 6.2 を参照
	xAPIVersion := req.Header.Get("X-Experience-API-Version")
	if err := validator.Statement(validator.ToXAPIVersion(xAPIVersion), statement); err != nil {
		return NewBadRequestErrF("Invalid statement: %s", err).Response()
	}

	// ステートメントIDをチェック, Experience API, Section 7.2.1 を参照
	statementID := req.URL.Query().Get("statementId")
	if !validator.IsUUID(statementID) {
		return NewBadRequestErr("Statement ID must be valid UUID").Response()
	}

	// Attachment と multipart/mixed に指定されるヘッダ情報との整合性をチェック
	if attachmentSHA2s != nil && !hasSameHashBetween(statements, attachmentSHA2s) {
		return NewBadRequestErr("Unexpected content hash given on attachment or multipart header").Response()
	}

	// 与えられたステートメントと、URLのパラメータのIDをチェック
	if id, ok := statement["id"]; !ok {
		// URL にのみ ID が付加されているときは, ステートメントにその ID を補完
		statement["id"] = statementID
	} else if id.(string) != statementID {
		// ステートメントに指定されている ID と違っている場合はエラー
		return NewBadRequestErr("ID mismatch between URL parameter and request body").Response()
	}

	// タイムスタンプに関する処理
	currentTime := time.Now()

	timestamp := currentTime
	statement["stored"] = currentTime

	if ts, ok := statement["timestamp"]; ok {
		t, err := time.Parse(time.RFC3339Nano, ts.(string))
		if err != nil {
			return NewBadRequestErr("Timestamp must be of the form of RFC3339").Response()
		}
		timestamp = t
	} else {
		statement["timestamp"] = timestamp
	}

	// authority フィールドの補完
	statement["authority"] = bson.M{
		"objectType": "Agent",
		"account": bson.M{
			"homePage": req.Header.Get(miscs.GlobalConfig.Authority.HomePageHeader),
			"name":     req.Header.Get(miscs.GlobalConfig.Authority.NameHeader),
		},
	}

	if code, mess := c.insertIntoDB(xAPIVersion, user, app, model.DocumentSlice{
		*model.NewDocument(xAPIVersion, user, app, timestamp, statement),
	}); code != http.StatusOK {
		return code, mess
	}

	return http.StatusNoContent, "No Content"
}