/**
 * personium.io
 * Copyright 2014 FUJITSU LIMITED
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.fujitsu.dc.core.rs.odata;

import java.io.Reader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.ws.rs.HeaderParam;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;

import org.odata4j.core.ODataConstants;
import org.odata4j.core.ODataVersion;
import org.odata4j.core.OEntity;
import org.odata4j.core.OEntityKey;
import org.odata4j.core.OProperty;
import org.odata4j.edm.EdmComplexType;
import org.odata4j.edm.EdmDataServices;
import org.odata4j.edm.EdmEntitySet;
import org.odata4j.edm.EdmEntityType;
import org.odata4j.edm.EdmProperty;

import com.fujitsu.dc.core.DcCoreException;
import com.fujitsu.dc.core.auth.AccessContext;
import com.fujitsu.dc.core.model.ctl.Common;
import com.fujitsu.dc.core.odata.OEntityWrapper;

/**
 * ODataのEntityリソースのMERGEメソッドを扱うJAX-RS リソース.
 */
public class ODataMergeResource extends ODataEntityResource {

    private final String keyString;
    private final ODataResource odataResource;
    private final AccessContext accessContext;
    private OEntityKey oEntityKey;

    /**
     * コンストラクタ.
     * @param odataResource 親リソースであるODataResource
     * @param entitySetName EntitySet Name
     * @param key キー文字列
     */
    public ODataMergeResource(ODataResource odataResource, String entitySetName, String key) {
        super();

        this.odataResource = odataResource;
        this.accessContext = this.odataResource.accessContext;
        setOdataProducer(this.odataResource.getODataProducer());
        setEntitySetName(entitySetName);

        this.keyString = key;

        try {
            this.oEntityKey = OEntityKey.parse(this.keyString);
        } catch (IllegalArgumentException e) {
            throw DcCoreException.OData.ENTITY_KEY_PARSE_ERROR.reason(e);
        }
    }

    /**
     * MERGE メソッドの処理.
     * @param reader リクエストボディ
     * @param accept Accept ヘッダ
     * @param ifMatch If-Match ヘッダ
     * @return JAX-RSResponse
     */
    public Response merge(Reader reader,
            @HeaderParam(HttpHeaders.ACCEPT) final String accept,
            @HeaderParam(HttpHeaders.IF_MATCH) final String ifMatch) {
        // メソッド実行可否チェック
        checkNotAllowedMethod();

        // アクセス制御
        this.odataResource.checkAccessContext(this.accessContext,
                this.odataResource.getNecessaryWritePrivilege(getEntitySetName()));

        // リクエストからOEntityWrapperを作成する.
        OEntity oe = this.createRequestEntity(reader, this.oEntityKey);
        OEntityWrapper oew = new OEntityWrapper(null, oe, null);

        // 必要ならばメタ情報をつける処理
        this.odataResource.beforeMerge(oew, this.oEntityKey);

        // If-Matchヘッダで入力されたETagをMVCC用での衝突検知用にOEntityWrapperに設定する。
        String etag = ODataResource.parseEtagHeader(ifMatch);
        oew.setEtag(etag);

        // MERGE処理をODataProducerに依頼。
        // こちらでリソースの存在確認もしてもらう。
        getOdataProducer().mergeEntity(getEntitySetName(), this.oEntityKey, oew);

        // 特に例外があがらなければ、レスポンスを返す。
        // oewに新たに登録されたETagを返す
        etag = oew.getEtag();
        return Response.noContent()
                .header(HttpHeaders.ETAG, ODataResource.renderEtagHeader(etag))
                .header(ODataConstants.Headers.DATA_SERVICE_VERSION, ODataVersion.V2.asString)
                .build();

    }

    /**
     * スキーマ定義をもとにOPropertyにデフォルト値を設定. <br />
     * MERGEの場合、キー, updated, published以外の項目にデフォルト値は設定しない
     * @param ep EdmProperty
     * @param propName プロパティ名
     * @param op OProperty
     * @param metadata EdmDataServicesスキーマ定義
     * @return Oproperty
     */
    @Override
    protected OProperty<?> setDefaultValue(EdmProperty ep, String propName, OProperty<?> op, EdmDataServices metadata) {

        if (metadata != null) {
            // スキーマ情報の取得
            EdmEntitySet edmEntitySet = metadata.findEdmEntitySet(getEntitySetName());
            EdmEntityType edmEntityType = edmEntitySet.getType();
            // スキーマに定義されたキーリストを取得
            List<String> keysDefined = edmEntityType.getKeys();
            String epName = ep.getName();

            // キー, updated, published以外の項目にデフォルト値は設定しない
            if (!keysDefined.contains(epName) && !Common.P_PUBLISHED.getName().equals(epName)
                    && !Common.P_UPDATED.getName().equals(epName)) {
                return null;
            }
        }

        return super.setDefaultValue(ep, propName, op, metadata);
    }

    /**
     * ComplexTypeスキーマを参照して、必須チェックとデフォルト値の設定を行う.
     * @param metadata スキーマ情報
     * @param edmComplexType ComplexTypeのスキーマ情報
     * @param complexProperties ComplexTypePropertyのList
     * @return デフォルト値を設定したComplexTypeプロパティの一覧
     */
    @Override
    protected List<OProperty<?>> createNewComplexProperties(EdmDataServices metadata,
            EdmComplexType edmComplexType,
            Map<String, OProperty<?>> complexProperties) {
        // ComplexTypeスキーマを参照して、必須チェックとデフォルト値の設定を行う
        List<OProperty<?>> newComplexProperties = new ArrayList<OProperty<?>>();
        for (EdmProperty ctp : edmComplexType.getProperties()) {
            // プロパティ情報を取得する
            String compPropName = ctp.getName();
            OProperty<?> complexProperty = complexProperties.get(compPropName);
            if (ctp.getType().isSimple()) {
                // シンプル型の場合
                // MERGEの場合はデフォルト値を設定しない
                if (complexProperty == null) {
                    continue;
                } else if (complexProperty.getValue() == null) {
                    // Nullableチェック
                    complexProperty = setDefaultValue(ctp, compPropName, complexProperty);
                }
            } else {
                // Complex型の場合
                complexProperty = getComplexProperty(ctp, compPropName, complexProperty, metadata);
            }
            if (complexProperty != null) {
                // MERGEリクエストでは、ComplexTypeのPropertyが指定されていない場合は無視する
                newComplexProperties.add(complexProperty);
            }
        }
        return newComplexProperties;
    }

}