## --- BEGIN LICENSE BLOCK ---
# Copyright (c) 2016-present WeWantToKnow AS
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
## --- END LICENSE BLOCK ---

require 'u3d/utils'

module U3d
  UNITY_DIR_CHECK = /Unity_\d+\.\d+\.\d+[a-z]\d+/
  UNITY_DIR_CHECK_LINUX = /unity-editor-\d+\.\d+\.\d+[a-z]\d+\z/

  class Installation
    attr_reader :root_path

    NOT_PLAYBACKENGINE_PACKAGES = %w[Documentation StandardAssets MonoDevelop].freeze
    PACKAGE_ALIASES =
      {
        'Android' => [],
        'iOS' => ['iPhone'],
        'AppleTV' => ['tvOS'],
        'Linux' => ['StandaloneLinux'],
        'Mac' => %w[StandaloneOSXIntel StandaloneOSXIntel64 StandaloneOSX],
        'Windows' => ['StandaloneWindows'],
        'Metro' => [],
        'UWP-IL2CPP' => [],
        'Samsung-TV' => [],
        'Tizen' => [],
        'WebGL' => [],
        'Facebook-Games' => ['Facebook'],
        'Vuforia-AR' => ['UnityExtensions']
      }.freeze

    def initialize(root_path: nil, path: nil)
      @root_path = root_path
      @path = path
    end

    def self.create(root_path: nil, path: nil)
      UI.deprecated("path is deprecated. Use root_path instead") unless path.nil?
      if Helper.mac?
        MacInstallation.new(root_path: root_path, path: path)
      elsif Helper.linux?
        LinuxInstallation.new(root_path: root_path, path: path)
      else
        WindowsInstallation.new(root_path: root_path, path: path)
      end
    end

    def packages
      false
    end

    def package_installed?(package)
      return true if (packages || []).include?(package)

      aliases = PACKAGE_ALIASES[package]

      # If no aliases for the package are found, then it's a new package not yet known by Unity
      # If the exact name doesn't match then we have to suppose it's not installed
      return false unless aliases

      return !(aliases & packages).empty?
    end
  end

  class PlaybackEngineUtils
    def self.list_module_configs(playbackengine_parent_path)
      Dir.glob("#{playbackengine_parent_path}/PlaybackEngines/*/ivy.xml")
    end

    def self.node_value(config_path, node_name)
      require 'rexml/document'
      UI.verbose("reading #{config_path}")
      raise "File not found at #{config_path}" unless File.exist? config_path
      doc = REXML::Document.new(File.read(config_path))
      REXML::XPath.first(doc, node_name).value
    end

    def self.module_name(config_path)
      node_value(config_path, 'ivy-module/info/@module')
    end

    def self.unity_version(config_path)
      node_value(config_path, 'ivy-module/info/@e:unityVersion')
    end
  end

  class MacInstallation < Installation
    require 'plist'

    def version
      plist['CFBundleVersion']
    end

    def default_log_file
      "#{ENV['HOME']}/Library/Logs/Unity/Editor.log"
    end

    def exe_path
      "#{root_path}/Unity.app/Contents/MacOS/Unity"
    end

    def path
      UI.deprecated("path is deprecated. Use root_path instead")
      return @path if @path
      "#{@root_path}/Unity.app"
    end

    def packages
      pack = []
      PlaybackEngineUtils.list_module_configs(root_path).each do |mpath|
        pack << PlaybackEngineUtils.module_name(mpath)
      end
      NOT_PLAYBACKENGINE_PACKAGES.each do |module_name|
        pack << module_name unless Dir[module_name_pattern(module_name)].empty?
      end
      pack
    end

    def module_name_pattern(module_name)
      case module_name
      when 'Documentation'
        return "#{root_path}/Documentation/"
      when 'StandardAssets'
        return "#{root_path}/Standard Assets/"
      when 'MonoDevelop'
        return "#{root_path}/MonoDevelop.app/"
      else
        UI.crash! "No pattern is known for #{module_name} on Mac"
      end
    end

    def clean_install?
      !(root_path =~ UNITY_DIR_CHECK).nil?
    end

    private

    def plist
      @plist ||=
        begin
          fpath = "#{root_path}/Unity.app/Contents/Info.plist"
          raise "#{fpath} doesn't exist" unless File.exist? fpath
          Plist.parse_xml(fpath)
        end
    end
  end

  class LinuxInstallation < Installation
    def version
      # I don't find an easy way to extract the version on Linux
      path = "#{root_path}/Editor/Data/"
      package = PlaybackEngineUtils.list_module_configs(path).first
      raise "Couldn't find a module under #{path}" unless package
      version = PlaybackEngineUtils.unity_version(package)
      if (m = version.match(/^(.*)x(.*)Linux$/))
        version = "#{m[1]}#{m[2]}"
      end
      version
    end

    def default_log_file
      "#{ENV['HOME']}/.config/unity3d/Editor.log"
    end

    def exe_path
      "#{root_path}/Editor/Unity"
    end

    def path
      UI.deprecated("path is deprecated. Use root_path instead")
      @root_path || @path
    end

    def packages
      path = "#{root_path}/Editor/Data/"
      pack = []
      PlaybackEngineUtils.list_module_configs(path).each do |mpath|
        pack << PlaybackEngineUtils.module_name(mpath)
      end
      NOT_PLAYBACKENGINE_PACKAGES.each do |module_name|
        pack << module_name unless Dir[module_name_pattern(module_name)].empty?
      end
      pack
    end

    def module_name_pattern(module_name)
      # FIXME: we are not yet sure where these modules will end up yet
      case module_name
      when 'Documentation'
        return "#{root_path}/Editor/Data/Documentation/"
      when 'StandardAssets'
        return "#{root_path}/Editor/Standard Assets/"
      when 'MonoDevelop'
        return "#{root_path}/MonoDevelop/"
      else
        UI.crash! "No pattern is known for #{module_name} on Linux"
      end
    end

    def clean_install?
      !(root_path =~ UNITY_DIR_CHECK_LINUX).nil?
    end
  end

  class WindowsInstallation < Installation
    def version
      path = "#{root_path}/Editor/Data/"
      package = PlaybackEngineUtils.list_module_configs(path).first
      raise "Couldn't find a module under #{path}" unless package
      PlaybackEngineUtils.unity_version(package)
    end

    def default_log_file
      if @logfile.nil?
        begin
          loc_appdata = Utils.windows_local_appdata
          log_dir = File.expand_path('Unity/Editor/', loc_appdata)
          UI.important "Log directory (#{log_dir}) does not exist" unless Dir.exist? log_dir
          @logfile = File.expand_path('Editor.log', log_dir)
        rescue RuntimeError => ex
          UI.error "Unable to retrieve the editor logfile: #{ex}"
        end
      end
      @logfile
    end

    def exe_path
      File.join(@root_path, 'Editor', 'Unity.exe')
    end

    def path
      UI.deprecated("path is deprecated. Use root_path instead")
      @root_path || @path
    end

    def packages
      path = "#{root_path}/Editor/Data/"
      pack = []
      PlaybackEngineUtils.list_module_configs(path).each do |mpath|
        pack << PlaybackEngineUtils.module_name(mpath)
      end
      NOT_PLAYBACKENGINE_PACKAGES.each do |module_name|
        pack << module_name unless Dir[module_name_pattern(module_name)].empty?
      end
      pack
    end

    def module_name_pattern(module_name)
      case module_name
      when 'Documentation'
        return "#{root_path}/Editor/Data/Documentation/"
      when 'StandardAssets'
        return "#{root_path}/Editor/Standard Assets/"
      when 'MonoDevelop'
        return "#{root_path}/MonoDevelop/"
      else
        UI.crash! "No pattern is known for #{module_name} on Windows"
      end
    end

    def clean_install?
      !(root_path =~ UNITY_DIR_CHECK).nil?
    end
  end
end