/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements.  See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.stanbol.enhancer.nlp.json.valuetype.impl;

import java.util.Collections;
import java.util.EnumSet;
import java.util.LinkedHashMap;
import java.util.Map;

import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.ConfigurationPolicy;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Service;
import org.apache.stanbol.enhancer.nlp.json.JsonUtils;
import org.apache.stanbol.enhancer.nlp.json.valuetype.ValueTypeParser;
import org.apache.stanbol.enhancer.nlp.json.valuetype.ValueTypeSerializer;
import org.apache.stanbol.enhancer.nlp.pos.LexicalCategory;
import org.apache.stanbol.enhancer.nlp.pos.Pos;
import org.apache.stanbol.enhancer.nlp.pos.PosTag;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.node.ArrayNode;
import org.codehaus.jackson.node.ObjectNode;

@Component(immediate=true,policy=ConfigurationPolicy.IGNORE)
@Service(value={ValueTypeParser.class,ValueTypeSerializer.class})
@Property(name=ValueTypeParser.PROPERTY_TYPE, value=PosTagSupport.TYPE_VALUE)
public class PosTagSupport implements ValueTypeParser<PosTag>, ValueTypeSerializer<PosTag> {

    public static final String TYPE_VALUE = "org.apache.stanbol.enhancer.nlp.pos.PosTag";
    
    Map<PosTagInfo,PosTag> posTagCache = Collections.synchronizedMap(
        new LinkedHashMap<PosTagInfo,PosTag>(16,0.75f,true){
            private static final long serialVersionUID = 1L;
    
            @Override
            protected boolean removeEldestEntry(java.util.Map.Entry<PosTagInfo,PosTag> arg0) {
                return size() > 1024;
            }
    });
        
    @Override
    public Class<PosTag> getType() {
        return PosTag.class;
    }
    @Override
    public PosTag parse(ObjectNode jValue) {
        PosTagInfo tagInfo = new PosTagInfo();
        JsonNode tag = jValue.path("tag");
        if(!tag.isTextual()){
            throw new IllegalStateException("Unable to parse PosTag. The value of the "
                    +"'tag' field MUST have a textual value (json: "+jValue+")");
        }
        tagInfo.tag = tag.getTextValue();
        if(jValue.has("lc")){
            tagInfo.categories = JsonUtils.parseEnum(jValue, "lc",LexicalCategory.class);
        } else {
            tagInfo.categories = EnumSet.noneOf(LexicalCategory.class);
        }
        if(jValue.has("pos")){
            tagInfo.pos = JsonUtils.parseEnum(jValue, "pos", Pos.class);
        } else {
            tagInfo.pos = EnumSet.noneOf(Pos.class);
        }
        PosTag posTag = posTagCache.get(tagInfo);
        if(posTag == null){
            posTag = new PosTag(tagInfo.tag,tagInfo.categories,tagInfo.pos);
            posTagCache.put(tagInfo, posTag);
        }
        return posTag;
    }



    @Override
    public ObjectNode serialize(ObjectMapper mapper, PosTag value){
        ObjectNode jPosTag = mapper.createObjectNode();
        jPosTag.put("tag", value.getTag());
        if(value.getPos().size() == 1){
            jPosTag.put("pos",value.getPos().iterator().next().ordinal());
        } else if(!value.getPos().isEmpty()){
            ArrayNode jPos = mapper.createArrayNode();
            for(Pos pos : value.getPos()){
                jPos.add(pos.ordinal());
            }
            jPosTag.put("pos", jPos);
        }
        if(!value.getCategories().isEmpty()){
            //we need only the categories not covered by Pos elements
            EnumSet<LexicalCategory> categories = EnumSet.noneOf(LexicalCategory.class);
            categories.addAll(value.getCategories());
            for(Pos pos : value.getPos()){
                categories.removeAll(pos.categories());
            }
            if(categories.size() == 1){
                jPosTag.put("lc",categories.iterator().next().ordinal());
            } else if(!categories.isEmpty()){
                ArrayNode jCategory = mapper.createArrayNode();
                for(LexicalCategory lc : categories){
                    jCategory.add(lc.ordinal());
                }
                jPosTag.put("lc", jCategory);
            }
        }
        return jPosTag;
    }
    
    private class PosTagInfo {
        
        protected String tag;
        protected EnumSet<LexicalCategory> categories;
        protected EnumSet<Pos> pos;
        
        @Override
        public int hashCode() {
            return tag.hashCode()+(categories != null ? categories.hashCode() : 0)+
                    (pos != null ? pos.hashCode() : 0);
        }
        @Override
        public boolean equals(Object obj) {
            return obj instanceof PosTagInfo && tag.equals(tag) && 
                    categories.equals(categories) && pos.equals(pos);
        }
        
    }
    
}
