text: /*
   * Copyright (C) 2011-2015 Daniel Scharrer
   *
   * This software is provided 'as-is', without any express or implied
   * warranty.  In no event will the author(s) be held liable for any damages
   * arising from the use of this software.
   *
   * Permission is granted to anyone to use this software for any purpose,
   * including commercial applications, and to alter it and redistribute it
   * freely, subject to the following restrictions:
   *
   * 1. The origin of this software must not be misrepresented; you must not
   *    claim that you wrote the original software. If you use this software
   *    in a product, an acknowledgment in the product documentation would be
   *    appreciated but is not required.
   * 2. Altered source versions must be plainly marked as such, and must not be
   *    misrepresented as being the original software.
   * 3. This notice may not be removed or altered from any source distribution.
   */
  
  #include "setup/info.hpp"
  
  #include <cassert>
  #include <istream>
  
  #include <boost/foreach.hpp>
  
  #include "setup/component.hpp"
  #include "setup/data.hpp"
  #include "setup/delete.hpp"
  #include "setup/directory.hpp"
  #include "setup/file.hpp"
  #include "setup/icon.hpp"
  #include "setup/ini.hpp"
  #include "setup/item.hpp"
  #include "setup/language.hpp"
  #include "setup/message.hpp"
  #include "setup/permission.hpp"
  #include "setup/registry.hpp"
  #include "setup/run.hpp"
  #include "setup/task.hpp"
  #include "setup/type.hpp"
  #include "stream/block.hpp"
  #include "util/load.hpp"
  #include "util/log.hpp"
  
  namespace setup {
  
  namespace {
  
  struct no_arg { };
  
  template <class Entry, class Arg>
  static void load_entry(std::istream & is, const setup::version & version,
                         Entry & entity, Arg arg) {
  	entity.load(is, version, arg);
  }
  template <class Entry>
  static void load_entry(std::istream & is, const setup::version & version,
                                      Entry & entity, no_arg arg) {
  	(void)arg;
  	entity.load(is, version);
  }
  
  template <class Entry, class Arg>
  static void load_entries(std::istream & is, const setup::version & version,
                    info::entry_types entry_types, size_t count,
                    std::vector<Entry> & entries, info::entry_types::enum_type entry_type,
                    Arg arg = Arg()) {
  	
  	entries.clear();
  	if(entry_types & entry_type) {
  		entries.resize(count);
  		for(size_t i = 0; i < count; i++) {
  			Entry & entry = entries[i];
  			load_entry(is, version, entry, arg);
  		}
  	} else {
  		for(size_t i = 0; i < count; i++) {
  			Entry entry;
  			load_entry(is, version, entry, arg);
  		}
  	}
  }
  
  template <class Entry>
  static void load_entries(std::istream & is, const setup::version & version,
                    info::entry_types entry_types, size_t count,
                    std::vector<Entry> & entries, info::entry_types::enum_type entry_type) {
  	load_entries<Entry, no_arg>(is, version, entry_types, count, entries, entry_type);
  }
  
  static void load_wizard_and_decompressor(std::istream & is, const setup::version & version,
                                          const setup::header & header,
                                          setup::info & info, info::entry_types entries) {
  	
  	(void)entries;
  	
  	info.wizard_image.clear();
  	info.wizard_image_small.clear();
  	if(entries & (info::WizardImages | info::NoSkip)) {
  		is >> util::binary_string(info.wizard_image);
  		if(version >= INNO_VERSION(2, 0, 0)) {
  			is >> util::binary_string(info.wizard_image_small);
  		}
  	} else {
  		util::binary_string::skip(is);
  		if(version >= INNO_VERSION(2, 0, 0)) {
  			util::binary_string::skip(is);
  		}
  	}
  	
  	info.decompressor_dll.clear();
  	if(header.compression == stream::BZip2
  	   || (header.compression == stream::LZMA1 && version == INNO_VERSION(4, 1, 5))
  	   || (header.compression == stream::Zlib && version >= INNO_VERSION(4, 2, 6))) {
  		if(entries & (info::DecompressorDll | info::NoSkip)) {
  			is >> util::binary_string(info.decompressor_dll);
  		} else {
  			// decompressor dll - we don't need this
  			util::binary_string::skip(is);
  		}
  	}
  	
  	info.decrypt_dll.clear();
  	if(header.options & header::EncryptionUsed) {
  		if(entries & (info::DecryptDll | info::NoSkip)) {
  			is >> util::binary_string(info.decrypt_dll);
  		} else {
  			// decrypt dll - we don't need this
  			util::binary_string::skip(is);
  		}
  	}
  	
  }
  
  } // anonymous namespace
  
  static void check_is_end(stream::block_reader::pointer & is, const char * what) {
  	is->exceptions(std::ios_base::goodbit);
  	char dummy;
  	if(!is->get(dummy).eof()) {
  		throw std::ios_base::failure(what);
  	}
  }
  
  void info::load(std::istream & ifs, entry_types e, const setup::version & v) {
  	
  	if(e & (Messages | NoSkip)) {
  		e |= Languages;
  	}
  	
  	stream::block_reader::pointer is = stream::block_reader::get(ifs, v);
  	
  	header.load(*is, v);
  	
  	load_entries(*is, v, e, header.language_count, languages, Languages);
  	
  	if(v < INNO_VERSION(4, 0, 0)) {
  		load_wizard_and_decompressor(*is, v, header, *this, e);
  	}
  	
  	load_entries(*is, v, e, header.message_count, messages, Messages, languages);
  	load_entries(*is, v, e, header.permission_count, permissions, Permissions);
  	load_entries(*is, v, e, header.type_count, types, Types);
  	load_entries(*is, v, e, header.component_count, components, Components);
  	load_entries(*is, v, e, header.task_count, tasks, Tasks);
  	load_entries(*is, v, e, header.directory_count, directories, Directories);
  	load_entries(*is, v, e, header.file_count, files, Files);
  	load_entries(*is, v, e, header.icon_count, icons, Icons);
  	load_entries(*is, v, e, header.ini_entry_count, ini_entries, IniEntries);
  	load_entries(*is, v, e, header.registry_entry_count, registry_entries, RegistryEntries);
  	load_entries(*is, v, e, header.delete_entry_count, delete_entries, DeleteEntries);
  	load_entries(*is, v, e, header.uninstall_delete_entry_count, uninstall_delete_entries,
  	             UninstallDeleteEntries);
  	load_entries(*is, v, e, header.run_entry_count, run_entries, RunEntries);
  	load_entries(*is, v, e, header.uninstall_run_entry_count, uninstall_run_entries,
  	             UninstallRunEntries);
  	
  	if(v >= INNO_VERSION(4, 0, 0)) {
  		load_wizard_and_decompressor(*is, v, header, *this, e);
  	}
  	
  	// restart the compression stream
  	check_is_end(is, "unknown data at end of primary header stream");
  	is = stream::block_reader::get(ifs, v);
  	
  	load_entries(*is, v, e, header.data_entry_count, data_entries, DataEntries);
  	
  	check_is_end(is, "unknown data at end of secondary header stream");
  }
  
  void info::load(std::istream & is, entry_types entries) {
  	
  	version.load(is);
  	
  	if(!version.known) {
  		log_warning << "Unexpected setup data version: "
  		            << color::white << version << color::reset;
  	}
  	
  	version_constant listed_version = version.value;
  	
  	// Some setup versions didn't increment the data version number when they should have.
  	// To work around this, we try to parse the headers for both data versions.
  	bool ambiguous = version.is_ambiguous();
  	if(ambiguous) {
  		// Force parsing all headers so that we don't miss any errors.
  		entries |= NoSkip;
  	}
  	if(!version.known || ambiguous) {
  		std::ios_base::streampos start = is.tellg();
  		try {
  			load(is, entries, version);
  			return;
  		} catch(...) {
  			version.value = version.next();
  			if(!version) {
  				version.value = listed_version;
  				throw;
  			}
  			is.clear();
  			is.seekg(start);
  		}
  	}
  	
  	try {
  		load(is, entries, version);
  	} catch(...) {
  		version.value = listed_version;
  		throw;
  	}
  }
  
  info::info() { }
  info::~info() { }
  
  } // namespace setup
  