#
# Copyright:: Copyright (c) 2014 Chef Software Inc.
# License:: Apache License, Version 2.0
#
# 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.
#

require 'chef-dk/exceptions'
require 'chef-dk/policyfile_compiler'
require 'chef-dk/policyfile/storage_config'
require 'chef-dk/policyfile_lock'

module ChefDK
  module PolicyfileServices

    class Install

      attr_reader :root_dir
      attr_reader :ui

      def initialize(policyfile: nil, ui: nil, root_dir: nil)
        @policyfile_relative_path = policyfile
        @ui = ui
        @root_dir = root_dir

        @policyfile_content = nil
        @policyfile_compiler = nil
      end

      def run
        unless File.exist?(policyfile_path)
          # TODO: suggest next step. Add a generator/init command? Specify path to Policyfile.rb?
          # See card CC-232
          raise PolicyfileNotFound, "Policyfile not found at path #{policyfile_path}"
        end

        if File.exist?(lockfile_path)
          install_from_lock
        else
          generate_lock_and_install
        end
      end

      def policyfile_relative_path
        @policyfile_relative_path || "Policyfile.rb"
      end

      def policyfile_path
        File.expand_path(policyfile_relative_path, root_dir)
      end

      def lockfile_relative_path
        policyfile_relative_path.gsub(/\.rb\Z/, '') + ".lock.json"
      end

      def lockfile_path
        File.expand_path(lockfile_relative_path, root_dir)
      end

      def policyfile_content
        @policyfile_content ||= IO.read(policyfile_path)
      end

      def policyfile_compiler
        @policyfile_compiler ||= ChefDK::PolicyfileCompiler.evaluate(policyfile_content, policyfile_path, ui: ui)
      end

      def expanded_run_list
        policyfile_compiler.expanded_run_list.to_s
      end

      def policyfile_lock_content
        @policyfile_lock_content ||= IO.read(lockfile_path) if File.exist?(lockfile_path)
      end

      def policyfile_lock
        return nil if policyfile_lock_content.nil?
        @policyfile_lock ||= begin
          lock_data = FFI_Yajl::Parser.new.parse(policyfile_lock_content)
          PolicyfileLock.new(storage_config, ui: ui).build_from_lock_data(lock_data)
        end
      end

      def storage_config
        @storage_config ||= Policyfile::StorageConfig.new(relative_paths_root: root_dir)
      end

      def generate_lock_and_install
        policyfile_compiler.error!

        ui.msg "Building policy #{policyfile_compiler.name}"
        ui.msg "Expanded run list: " + expanded_run_list + "\n"

        ui.msg "Caching Cookbooks..."

        policyfile_compiler.install

        lock_data = policyfile_compiler.lock.to_lock

        File.open(lockfile_path, "w+") do |f|
          f.print(FFI_Yajl::Encoder.encode(lock_data, pretty: true ))
        end

        ui.msg ""

        ui.msg "Lockfile written to #{lockfile_path}"
      rescue => error
        raise PolicyfileInstallError.new("Failed to generate Policyfile.lock", error)
      end

      def install_from_lock
        ui.msg "Installing cookbooks from lock"

        policyfile_lock.install_cookbooks
      rescue => error
        raise PolicyfileInstallError.new("Failed to install cookbooks from lockfile", error)
      end

    end
  end
end