/*
 * 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.brooklyn.location.jclouds.pool;


import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.brooklyn.location.jclouds.templates.PortableTemplateBuilder;
import org.jclouds.compute.options.TemplateOptions;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;

/**
 * A facility for having a template we can declare without knowing the provider,
 * then find matching instances, create instances, and generally manipulate them.
 * <p>
 * NB: to be sure of matching a specific template, you should provide a unique id in the constructor.
 * (this will force 'strict' mode.)
 */
// TODO tags/metadata semantics are not quite right, as they could apply to the server _image_ or _instance_

// TODO we could use a hashcode over the values of template-builder and template-options fields, as a tag/usermetadata, 
// to guarantee (virtually) matching only machines created from this template (instead of asking for unique id)
public class ReusableMachineTemplate extends PortableTemplateBuilder<ReusableMachineTemplate> {

    public static final String PREFIX = "brooklyn:template.";
    public static final String NAME_METADATA_KEY = PREFIX+"name";
    public static final String DESCRIPTION_METADATA_KEY = PREFIX+"name";
    public static final String HASH_METADATA_KEY = PREFIX+"hash";
    public static final String TEMPLATE_OWNER_METADATA_KEY = PREFIX+"owner";
    
    private String name = null;
    private String templateOwner = null;
    private String description = null;
    private boolean strict;
    
    public ReusableMachineTemplate() { strict = false; }
    public ReusableMachineTemplate(String name) { name(name); }
    
    /** see #getName() */
    public ReusableMachineTemplate name(String name) {
        this.name = name;
        strict = true;
        return this;
    }
    
    /** see #getDescription() */
    public ReusableMachineTemplate description(String description) {
        this.description = description;
        return this;
    }

    /** whether this template only matches machines instances created from this template; 
     * defaults true if a name is set, otherwise false.
     * if false, it will ignore name, owner, and hashcode */
    public ReusableMachineTemplate strict(boolean strict) {
        this.strict = strict;
        return this;
    }

    /** no owner, means anyone can pick this up (default) */
    public ReusableMachineTemplate templateUnowned() {
        return templateOwner(null);
    }
    /** adds user.name as owner of this template */
    public ReusableMachineTemplate templateOwnedByMe() {
        return templateOwner(System.getProperty("user.name"));
    }
    /** adds an owner tag to this template */
    public ReusableMachineTemplate templateOwner(String owner) {
        this.templateOwner = owner;
        return this;
    }
    
    /** human-friendly name for this template. should normally be unique, it is the primary differentiator for strict matching. */
    public String getName() {
        return name;
    }
    
    /** a description for this template; this is set on created machines but _not_ used to filter them 
     * (so you can change description freely).  */
    public String getDescription() {
        return description;
    }
    
    public String getOwner() {
        return templateOwner;
    }
    
    public boolean isStrict() {
        return strict;
    }

    @Override
    public List<TemplateOptions> getAdditionalOptions() {
        List<TemplateOptions> result = new ArrayList<TemplateOptions>();
        result.addAll(super.getAdditionalOptions());
        if (isStrict()) addStrictOptions(result);
        return result;
    }

    @Override
    public List<TemplateOptions> getAdditionalOptionalOptions() {
        List<TemplateOptions> result = new ArrayList<TemplateOptions>();
        result.addAll(super.getAdditionalOptions());
        addStrictOptions(result);
        return result;
    }
    
    protected void addStrictOptions(List<TemplateOptions> result) {
        if (name!=null) result.add(TemplateOptions.Builder.userMetadata(NAME_METADATA_KEY, name));
        if (templateOwner!=null) result.add(TemplateOptions.Builder.userMetadata(TEMPLATE_OWNER_METADATA_KEY, templateOwner));
        // this is too strict -- the hash code seems to change from run to run (would be nice to fix that)
//        result.add(TemplateOptions.Builder.userMetadata(HASH_METADATA_KEY, ""+hashCode()));
    }
    
    /** computes the user metadata that this template will set (argument true) or required to match (argument false) */
    public Map<String,String> getUserMetadata(boolean includeOptional) {
        return ImmutableMap.copyOf(computeAggregatedOptions(includeOptional).getUserMetadata());
    }

    /** computes the tags that this template will set (argument true) or require to match (argument false) */
    public Set<String> getTags(boolean includeOptional) {
        return ImmutableSet.copyOf(computeAggregatedOptions(includeOptional).getTags());
    }
    
    public ReusableMachineTemplate tag(String tag) {
        return tags(tag);
    }
    public ReusableMachineTemplate tags(String ...tags) {
        return addOptions(TemplateOptions.Builder.tags(Arrays.asList(tags)));
    }

    public ReusableMachineTemplate metadata(String key, String value) {
        return addOptions(TemplateOptions.Builder.userMetadata(key, value));
    }
    public ReusableMachineTemplate metadata(Map<String,String> m) {
        return addOptions(TemplateOptions.Builder.userMetadata(m));
    }

    public ReusableMachineTemplate tagOptional(String tag) {
        return tagsOptional(tag);
    }
    public ReusableMachineTemplate tagsOptional(String ...tags) {
        return addOptionalOptions(TemplateOptions.Builder.tags(Arrays.asList(tags)));
    }

    public ReusableMachineTemplate metadataOptional(String key, String value) {
        return addOptionalOptions(TemplateOptions.Builder.userMetadata(key, value));
    }
    public ReusableMachineTemplate metadataOptional(Map<String,String> m) {
        return addOptionalOptions(TemplateOptions.Builder.userMetadata(m));
    }

    @Override
    public String toString() {
        String s = makeNonTrivialArgumentsString();
        return (name!=null ? name : "Template") + " [ " + s + " ]";
    }
    
}
