/*
 * Copyright 2016 The Apache Software Foundation.
 *
 * 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 org.apache.brooklyn.camp.brooklyn.spi.dsl;

import org.apache.brooklyn.api.entity.Entity;
import org.apache.brooklyn.camp.BasicCampPlatform;
import org.apache.brooklyn.camp.brooklyn.AbstractYamlTest;
import org.apache.brooklyn.camp.brooklyn.BrooklynCampConstants;
import org.apache.brooklyn.config.ConfigKey;
import org.apache.brooklyn.core.config.ConfigKeys;
import org.apache.brooklyn.core.entity.Attributes;
import org.apache.brooklyn.core.entity.EntityInternal;
import org.apache.brooklyn.core.entity.EntityPredicates;
import org.apache.brooklyn.entity.stock.BasicApplication;
import org.apache.brooklyn.entity.stock.BasicEntity;
import org.apache.brooklyn.test.Asserts;
import org.apache.brooklyn.util.collections.MutableMap;
import org.apache.brooklyn.util.core.task.Tasks;
import org.apache.brooklyn.util.stream.Streams;
import org.apache.brooklyn.util.yaml.Yamls;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.Test;

import com.google.common.collect.Iterables;

public class DslParseComponentsTest extends AbstractYamlTest {
    
    private static final ConfigKey<Object> DEST = ConfigKeys.newConfigKey(Object.class, "dest");
    private static final ConfigKey<Object> DEST2 = ConfigKeys.newConfigKey(Object.class, "dest2");

    private static final Logger log = LoggerFactory.getLogger(DslParseComponentsTest.class);
    
    Entity app = null;
    
    protected Entity app() throws Exception {
        if (app==null) {
            app = createAndStartApplication(
                    "services:",
                    "- type: " + BasicApplication.class.getName(),
                    "  id: one",
                    "  brooklyn.config:",
                    "    dest: 1",
                    "    dest2: 1",
                    "    pattern: '%s-%s'",
                    "  brooklyn.children:",
                    "  - type: "+BasicEntity.class.getName(),
                    "    id: two",
                    "    brooklyn.config:",
                    "      dest2: 2"
                    );
        }
        return app;
    }

    @AfterMethod(alwaysRun = true)
    @Override
    public void tearDown() throws Exception {
        app = null;
        super.tearDown();
    }
    
    private Entity find(String desiredComponentId) {
        return Iterables.tryFind(mgmt().getEntityManager().getEntities(), EntityPredicates.configEqualTo(BrooklynCampConstants.PLAN_ID, desiredComponentId)).orNull();
    }
    
    public Object parseDslExpression(String input) {
        String s1 = Yamls.getAs( Yamls.parseAll( Streams.reader(Streams.newInputStreamWithContents(input)) ), String.class );
        BasicCampPlatform p = new BasicCampPlatform();
        p.pdp().addInterpreter(new BrooklynDslInterpreter());
        Object out = p.pdp().applyInterpreters(MutableMap.of("key", s1)).get("key");
        log.debug("parsed "+input+" as "+out+" ("+(out==null ? "null" : out.getClass())+")");
        return out;
    }
    
    @Test
    public void testTestSetup() throws Exception {
        app();
        Assert.assertEquals(find("two").getConfig(DEST), 1);
        Assert.assertEquals(find("two").getConfig(DEST2), 2);
    }
    
    @Test
    public void testDslParsingAndFormatStringEvaluation() throws Exception {
        // format string evaluates immediately
        Assert.assertEquals(parseDslExpression("$brooklyn:formatString(\"hello %s\", \"world\")"), "hello world");
    }
    
    @Test
    public void testConfigParsingAndToString() throws Exception {
        String x1 = "$brooklyn:config(\"dest\")";
        Object y1 = parseDslExpression(x1);
        // however config is a deferred supplier, with a toString in canonical form
        Asserts.assertInstanceOf(y1, BrooklynDslDeferredSupplier.class);
        Assert.assertEquals(y1.toString(), x1);
    }

    @Test
    public void testConfigEvaluation() throws Exception {
        app();
        
        Object y1 = parseDslExpression("$brooklyn:config(\"dest\")");
        String y2 = Tasks.resolveValue(y1, String.class, ((EntityInternal) find("two")).getExecutionContext());
        Assert.assertEquals(y2.toString(), "1");
    }

    @Test
    public void testFormatStringWithConfig() throws Exception {
        app();
        
        Object y1 = parseDslExpression("$brooklyn:formatString(\"%s-%s\", config(\"dest\"), $brooklyn:config(\"dest2\"))");
        Assert.assertEquals(y1.toString(), "$brooklyn:formatString(\"%s-%s\", config(\"dest\"), config(\"dest2\"))");
        
        String y2 = Tasks.resolveValue(y1, String.class, ((EntityInternal) find("two")).getExecutionContext());
        Assert.assertEquals(y2.toString(), "1-2");
        
        String y3 = Tasks.resolveValue(y1, String.class, ((EntityInternal) find("one")).getExecutionContext());
        Assert.assertEquals(y3.toString(), "1-1");
    }

    @Test
    public void testFormatStringWithDslPatternEvaluation() throws Exception {
        app();

        Object y1 = parseDslExpression("$brooklyn:formatString($brooklyn:config(\"pattern\"), $brooklyn:config(\"dest\"), $brooklyn:config(\"dest2\"))");
        Assert.assertEquals(y1.toString(), "$brooklyn:formatString(config(\"pattern\"), config(\"dest\"), config(\"dest2\"))");

        String y2 = Tasks.resolveValue(y1, String.class, ((EntityInternal) find("one")).getExecutionContext());
        Assert.assertEquals(y2.toString(), "1-1");
    }

    @Test
    public void testEntityReferenceAndAttributeWhenReady() throws Exception {
        app();
        find("one").sensors().set(Attributes.ADDRESS, "1");
        find("two").sensors().set(Attributes.ADDRESS, "2");
        
        Object y1 = parseDslExpression("$brooklyn:formatString(\"%s-%s\", "
            + "parent().attributeWhenReady(\"host.address\"), "
            + "$brooklyn:attributeWhenReady(\"host.address\"))");
        Assert.assertEquals(y1.toString(), "$brooklyn:formatString(\"%s-%s\", "
            + "parent().attributeWhenReady(\"host.address\"), "
            + "attributeWhenReady(\"host.address\"))");
        
        String y2 = Tasks.resolveValue(y1, String.class, ((EntityInternal) find("two")).getExecutionContext());
        Assert.assertEquals(y2.toString(), "1-2");
        
        Object z1 = parseDslExpression("$brooklyn:formatString(\"%s-%s\", "
            + "entity(\"one\").descendant(\"two\").attributeWhenReady(\"host.address\"), "
            + "component(\"two\").entity(entityId()).attributeWhenReady(\"host.address\"))");
        Assert.assertEquals(z1.toString(), "$brooklyn:formatString(\"%s-%s\", "
            + "entity(\"one\").descendant(\"two\").attributeWhenReady(\"host.address\"), "
            + "entity(entityId()).attributeWhenReady(\"host.address\"))");
        
        String z2 = Tasks.resolveValue(z1, String.class, ((EntityInternal) find("one")).getExecutionContext());
        Assert.assertEquals(z2.toString(), "2-1");
    }

}