/*
 * 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.jclouds.openstack.nova.v2_0.compute.functions;

import static com.google.common.base.Predicates.containsPattern;
import static com.google.common.base.Predicates.equalTo;
import static com.google.common.base.Predicates.not;
import static com.google.common.collect.Iterables.any;
import static com.google.common.collect.Iterables.filter;
import static com.google.common.collect.Iterables.find;

import java.util.Arrays;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.inject.Inject;
import javax.inject.Named;

import org.jclouds.compute.domain.OperatingSystem;
import org.jclouds.compute.domain.OsFamily;
import org.jclouds.compute.reference.ComputeServiceConstants;
import org.jclouds.compute.util.ComputeServiceUtils;
import org.jclouds.logging.Logger;
import org.jclouds.openstack.nova.v2_0.domain.Image;

import com.google.common.base.CharMatcher;
import com.google.common.base.Function;
import com.google.common.base.MoreObjects;
import com.google.common.base.Predicate;
import com.google.common.base.Splitter;

/**
 * A function for transforming a nova specific Image into a generic OperatingSystem object.
 */
public class ImageToOperatingSystem implements Function<Image, OperatingSystem> {
   public static final Pattern DEFAULT_PATTERN = Pattern.compile("(([^ ]*) ([0-9.]+) ?.*)");
   // Windows Server 2008 R2 x64
   public static final Pattern WINDOWS_PATTERN = Pattern.compile("Windows (.*) (x[86][64])");

   @javax.annotation.Resource
   @Named(ComputeServiceConstants.COMPUTE_LOGGER)
   protected Logger logger = Logger.NULL;

   protected final Map<OsFamily, Map<String, String>> osVersionMap;

   @Inject
   public ImageToOperatingSystem(Map<OsFamily, Map<String, String>> osVersionMap) {
      this.osVersionMap = osVersionMap;
   }

   public OperatingSystem apply(final Image from) {
      OsFamily osFamily = null;
      String osVersion = null;

      String imageName = MoreObjects.firstNonNull(from.getName(), "unspecified");

      boolean is64Bit = true;

      if (imageName.indexOf("Windows") != -1) {
         osFamily = OsFamily.WINDOWS;
         Matcher matcher = WINDOWS_PATTERN.matcher(from.getName());
         if (matcher.find()) {
            osVersion = ComputeServiceUtils.parseVersionOrReturnEmptyString(osFamily, matcher.group(1), osVersionMap);
            is64Bit = matcher.group(2).equals("x64");
         }
      } else {
         if (imageName.contains("Red Hat")) {
            osFamily = OsFamily.RHEL;
         } else if (imageName.contains("Oracle")) {
            osFamily = OsFamily.OEL;
         } else {
            final Iterable<String> imageNameParts = Splitter.on(CharMatcher.whitespace()).trimResults().split(
                     imageName.toLowerCase());

            try {
               osFamily = find(Arrays.asList(OsFamily.values()), new Predicate<OsFamily>() {
                  @Override
                  public boolean apply(OsFamily osFamily) {
                     return any(imageNameParts, equalTo(osFamily.name().toLowerCase()));
                  }
               });
            } catch (NoSuchElementException e) {
               String ubuntuVersion = startsWithUbuntuVersion(imageNameParts);
               if (ubuntuVersion != null) {
                  osFamily = OsFamily.UBUNTU;
                  osVersion = ubuntuVersion;
               } else {
                  logger.trace("could not parse operating system family for image(%s): %s", from.getId(), imageNameParts);
                  osFamily = OsFamily.UNRECOGNIZED;
               }
            }
         }
         Matcher matcher = DEFAULT_PATTERN.matcher(imageName);
         if (matcher.find() && matcher.groupCount() >= 3) {
            osVersion = ComputeServiceUtils.parseVersionOrReturnEmptyString(osFamily, matcher.group(3), osVersionMap);
         }
      }
      return new OperatingSystem(osFamily, imageName, osVersion, null, imageName, is64Bit);
   }

   String startsWithUbuntuVersion(final Iterable<String> imageNameParts) {
      Map<String, String> ubuntuVersions = osVersionMap.get(OsFamily.UBUNTU);
      for (String ubuntuKey : filter(ubuntuVersions.keySet(), not(equalTo("")))) {
         if (any(imageNameParts, containsPattern("^" + ubuntuKey + ".*"))) {
            return ubuntuVersions.get(ubuntuKey);
         }
      }
      return null;
   }
}
