/*
 * 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 WARRANTIESOR 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.aries.subsystem.modelling.impl;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.aries.blueprint.container.NamespaceHandlerRegistry;
import org.apache.aries.blueprint.container.ParserServiceImpl;
import org.apache.aries.blueprint.namespace.NamespaceHandlerRegistryImpl;
import org.apache.aries.blueprint.services.ParserService;
import org.apache.aries.mocks.BundleContextMock;
import org.apache.aries.subsystem.modelling.ModellingManager;
import org.apache.aries.subsystem.modelling.ParsedServiceElements;
import org.apache.aries.subsystem.modelling.ParserProxy;
import org.apache.aries.subsystem.modelling.WrappedReferenceMetadata;
import org.apache.aries.subsystem.modelling.WrappedServiceMetadata;
import org.apache.aries.unittest.mocks.Skeleton;
import org.junit.AfterClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.osgi.framework.BundleContext;

@RunWith(Parameterized.class)
public class ParserProxyTest {

  @Parameters
  public static List<Object[]> parserProxies() {
    return Arrays.asList(new Object[][] {
            {getMockParserServiceProxy()}
    });
  }
  
  public static ParserProxy getMockParserServiceProxy() {
      BundleContext mockCtx = Skeleton.newMock(new BundleContextMock(), BundleContext.class);
      NamespaceHandlerRegistry nhri = new NamespaceHandlerRegistryImpl (mockCtx);

      ParserService parserService = new ParserServiceImpl(nhri, true);
      mockCtx.registerService(ParserService.class.getName(), parserService, new Hashtable<String, String>());
      
      ParserProxyImpl parserProxyService = new ParserProxyImpl();
      parserProxyService.setParserService(parserService);
      parserProxyService.setBundleContext(mockCtx);
      parserProxyService.setModellingManager(new ModellingManagerImpl());
      
      return parserProxyService;
  }
  
  @AfterClass
  public static void teardown() { 
    BundleContextMock.clear();
  }
  
  private final ModellingManager _modellingManager;
  private final ParserProxy _parserProxy;
  private final File resourceDir;
  
  public ParserProxyTest(ParserProxy sut) throws IOException {
	  _parserProxy = sut;
	  _modellingManager = new ModellingManagerImpl();
	  
	  // make sure paths work in Eclipse as well as Maven
	  if (new File(".").getCanonicalFile().getName().equals("target")) {
	      resourceDir = new File("../src/test/resources");
	  } else {
	      resourceDir = new File("src/test/resources");
	  }
  }
  
  
  @Test
  public void basicTest1() throws Exception { 
    File bpXml = new File (resourceDir, "appModeller/test1.eba/bundle1.jar/OSGI-INF/blueprint/bp.xml");
    File bp2Xml = new File (resourceDir, "appModeller/test1.eba/bundle1.jar/OSGI-INF/blueprint/bp2.xml");
    List<URL> urls = new ArrayList<URL>();
    urls.add ((bpXml.toURI()).toURL());
    urls.add ((bp2Xml.toURI()).toURL());
    
    List<? extends WrappedServiceMetadata> results = _parserProxy.parse(urls);
    assertTrue ("Four results expected, not " + results.size(), results.size() == 4);
    
    Set<WrappedServiceMetadata> resultSet = new HashSet<WrappedServiceMetadata>(results);
    Set<WrappedServiceMetadata> expectedResults = getTest1ExpectedResults();
    assertEquals ("Blueprint parsed xml is not as expected: " + resultSet.toString() + " != " + expectedResults,
        resultSet, expectedResults);
  }
  
  @Test
  public void testParseAllServiceElements() throws Exception { 
    File bpXml = new File (resourceDir, "appModeller/test1.eba/bundle1.jar/OSGI-INF/blueprint/bp.xml");
    File bp2Xml = new File (resourceDir, "appModeller/test1.eba/bundle1.jar/OSGI-INF/blueprint/bp2.xml");
    
    List<WrappedServiceMetadata> services = new ArrayList<WrappedServiceMetadata>();
    List<WrappedReferenceMetadata> references = new ArrayList<WrappedReferenceMetadata>();
    
    FileInputStream fis = new FileInputStream (bpXml);
    ParsedServiceElements bpelem = _parserProxy.parseAllServiceElements(fis); 
    services.addAll(bpelem.getServices());
    references.addAll(bpelem.getReferences());
    
    fis = new FileInputStream (bp2Xml);
    bpelem = _parserProxy.parseAllServiceElements(fis); 
    services.addAll(bpelem.getServices());
    references.addAll(bpelem.getReferences());
    
    // We expect:
    // bp.xml: 3 services and 2 references
    // bp2.xml: 3 services and a reference list
    //
    assertTrue ("Six services expected, not " + services.size(), services.size() == 6);
    assertTrue ("Three references expected, not " + references.size(), references.size() == 3);
    
    Set<WrappedServiceMetadata> expectedServices = getTest2ExpectedServices();
    // ServiceResultSet will contain some services with autogenerated names starting '.' so we can't 
    // use a straight Set.equals(). We could add the autogenerated names to the expected results but instead
    // let's test that differsOnlyByName() works
    int serviceMatchesFound = 0;
    for (WrappedServiceMetadata result : services) { 
      Iterator<WrappedServiceMetadata> it = expectedServices.iterator();
      while (it.hasNext()) { 
        WrappedServiceMetadata next = it.next();
        if (result.equals(next) || result.identicalOrDiffersOnlyByName(next)) { 
          serviceMatchesFound++;
          it.remove();
        }
      }
    }
    
    assertEquals ("Parsed services are wrong: " + expectedServices + " unmatched ",
        6, serviceMatchesFound);
    
    Set<WrappedReferenceMetadata> expectedReferences = getTest2ExpectedReferences();
    Set<WrappedReferenceMetadata> results = new HashSet<WrappedReferenceMetadata>(references);
    assertTrue ("Parsed references are not as we'd expected: " + results.toString() + " != " + expectedReferences,
        results.equals(expectedReferences));
  }
  
  @Test
  public void checkMultiValues() throws Exception { 
    File bpXml = new File (resourceDir, "appModeller/test1.eba/bundle1.jar/OSGI-INF/blueprint/bpMultiValues.xml");
    List<WrappedServiceMetadata> services = new ArrayList<WrappedServiceMetadata>();
    FileInputStream fis = new FileInputStream (bpXml);
    ParsedServiceElements bpelem = _parserProxy.parseAllServiceElements(fis); 
    services.addAll(bpelem.getServices());
    
    assertEquals ("Multi valued service not parsed correctly", services.size(), 1);
    
    WrappedServiceMetadata wsm = services.get(0);
    Map<String, Object> props = wsm.getServiceProperties();
    String [] intents = (String[]) props.get("service.intents");
    
    assertEquals ("Service.intents[0] wrong", intents[0], "propagatesTransaction");
    assertEquals ("Service.intents[1] wrong", intents[1], "confidentiality");
    
  }
  
  // model
  // <reference id="fromOutside" interface="foo.bar.MyInjectedService"/>
  // <reference-list id="refList1" interface="my.logging.services" filter="(active=true)"/>
  //
  private Set<WrappedReferenceMetadata> getTest2ExpectedReferences() throws Exception { 
    Set<WrappedReferenceMetadata> expectedResults = new HashSet<WrappedReferenceMetadata>();
         
    expectedResults.add(_modellingManager.getImportedService(false, "foo.bar.MyInjectedService", null, 
        null, "fromOutside", false));
    expectedResults.add(_modellingManager.getImportedService(true, "foo.bar.MyInjectedService", null, 
        null, "anotherOptionalReference", false));
    expectedResults.add(_modellingManager.getImportedService(false, "my.logging.service", null, "(&(trace=on)(debug=true))", "refList1", true));
    
    return expectedResults;
  }
  
  // Test 2 includes anonymous services: the expected results are a superset of test1
  private Set<WrappedServiceMetadata> getTest2ExpectedServices() { 
    Set<WrappedServiceMetadata> expectedResults = getTest1ExpectedResults();
        
    expectedResults.add(_modellingManager.getExportedService("", 0, Arrays.asList("foo.bar.AnonService"), null));
    expectedResults.add(_modellingManager.getExportedService("", 0, Arrays.asList("foo.bar.NamedInnerBeanService"), null));
    return expectedResults;
  }
  
  private Set<WrappedServiceMetadata> getTest1ExpectedResults() { 
    Set<WrappedServiceMetadata> expectedResults = new HashSet<WrappedServiceMetadata>();
    Map<String, Object> props = new HashMap<String, Object>();
    props.put ("priority", "9");
    props.put("volume", "11");
    props.put("property.list", Arrays.asList("1", "2", "3", "2", "1"));
    //Deliberately miss off duplicate entries and reorder, the parser should still match this
    props.put("property.set", new LinkedHashSet<String>(Arrays.asList("1", "2", "3")));
    props.put("property.array", new String[]{"1", "2", "3", "2", "1"});
    props.put("osgi.service.blueprint.compname", "myBean");
    expectedResults.add(_modellingManager.getExportedService("myService", 0, Arrays.asList("foo.bar.MyService"), props));

    props = new HashMap<String, Object>();
    props.put ("priority", "7");
    props.put ("volume", "11");
    props.put ("osgi.service.blueprint.compname", "bean1");
    expectedResults.add(_modellingManager.getExportedService("service1.should.be.exported", 0, Arrays.asList("foo.bar.MyService"), props));
 
    props = new HashMap<String, Object>();
    props.put ("customer", "pig");
    props.put ("osgi.service.blueprint.compname", "bean2");
    expectedResults.add(_modellingManager.getExportedService("service2.should.not.be.exported", 0, Arrays.asList("com.acme.Delivery"), props));
        
    props = new HashMap<String, Object>();
    props.put ("customer", "pig");
    props.put ("target", "rabbit");
    props.put ("payload", "excessive");
    props.put ("osgi.service.blueprint.compname", "bean3");
    expectedResults.add(_modellingManager.getExportedService("bean3", 0, Arrays.asList("com.acme.Delivery"), props));
       
    return expectedResults;
  } 
}
