class CSVFileManager {

  public static CSVFileManager fromConfiguration() {
    checkState(Configuration.isInitialized(), "configuration not initialized");

    // csv file
    String filePath = Configuration.getString(FILEPATH, null).get();
    checkConfiguration(!filePath.isEmpty(), FILEPATH + " is required");
    Path csvFilePath = Paths.get(filePath);
    checkConfiguration(Files.exists(csvFilePath),
        "File does not exist: " + FILEPATH + "=" + filePath);

    // Charset for file
    Charset fileCharset = Configuration.getValue(FILE_ENCODING, defaultCharset(),
        value -> {
          try {
            return Charset.forName(value);
          } catch (IllegalCharsetNameException | UnsupportedCharsetException e) {
            throw new InvalidConfigurationException(
                "Invalid charset: " + FILE_ENCODING + "=" + value);
          }
        }
      ).get();

    Boolean skipHeader = Configuration.getBoolean(SKIP_HEADER, false).get();

    // Column names. If not set, the first row of the file is expected to contain column names.
    List<String> csvColumns =
        Configuration.getMultiValue(
            CSVCOLUMNS, Collections.emptyList(), Configuration.STRING_PARSER).get();

    // csv file format (Excel, etc.)
    String csvFormatName = Configuration.getString(CSV_FORMAT, "").get();
    CSVFormat csvFormat = createCsvFormat(csvFormatName, csvColumns, skipHeader);

    // The CSVFormat will contain the csvColumns, if set, otherwise read the header line
    // from the file.
    Set<String> headers;
    try (CSVParser parser = CSVParser.parse(csvFilePath.toFile(), fileCharset, csvFormat)) {
      headers = parser.getHeaderMap().keySet();
    } catch (IOException e) {
      throw new InvalidConfigurationException("Error reading " + csvFilePath, e);
    }

    // Columns to use to create the unique key, optional.
    // Verify that configured names are known column names.
    List<String> uniqueKeyColumns =
        Configuration.getMultiValue(
            UNIQUE_KEY_COLUMNS, Collections.emptyList(), Configuration.STRING_PARSER).get();
    verifyColumns(UNIQUE_KEY_COLUMNS, new HashSet<>(uniqueKeyColumns), "column headers", headers);

    // Verify that the columns expected for constructing the record URL are known.
    UrlBuilder urlBuilder = UrlBuilder.fromConfiguration();
    Set<String> missing = urlBuilder.getMissingColumns(headers);
    checkConfiguration(missing.isEmpty(),
        getMissingMessage(UrlBuilder.CONFIG_COLUMNS, missing, "column headers", headers));

    // Verify that any configured multi-value columns are known.
    List<String> multiValueColumns =
        Configuration.getMultiValue(
            MULTIVALUE_COLUMNS, Collections.emptyList(), Configuration.STRING_PARSER).get();
    verifyColumns(MULTIVALUE_COLUMNS, new HashSet<>(multiValueColumns), "column headers", headers);

    // Check any config properties of the form csv.multiValue.<columnName> and verify
    // the column name(s).
    Set<String> multiValueColumnsWithConfiguredSeparators = Configuration.getConfig()
        .keySet()
        .stream()
        .map(key -> key.toString())
        .filter(key -> key.startsWith("csv.multiValue."))
        .map(key -> key.substring("csv.multiValue.".length()))
        .collect(Collectors.toSet());
    checkConfiguration(
        multiValueColumns.size() > 0 || multiValueColumnsWithConfiguredSeparators.size() == 0,
        "Multi-value separators are configured but no multi-value columns are configured");
    verifyColumns("csv.multiValue.*",
        multiValueColumnsWithConfiguredSeparators, MULTIVALUE_COLUMNS,
        new HashSet<>(multiValueColumns));

    // Build a map from multi-value column name to delimiter.
    Map<String, String> columnsToDelimiter = new HashMap<>();
    for (String column : multiValueColumns) {
      String delimiter = Configuration
          .getString(String.format(MULTIVALUE_FORMAT_COLUMN, column), ",").get();
      columnsToDelimiter.put(column, delimiter);
    }

    return new Builder()
        .setFilePath(csvFilePath)
        .setFileCharset(fileCharset)
        .setUniqueKeyColumns(uniqueKeyColumns)
        .setCsvFormat(csvFormat)
        .setContentTemplate(ContentTemplate.fromConfiguration("csv"))
        .setColumnsToDelimiter(columnsToDelimiter)
        .setUrlBuilder(urlBuilder)
        .verify()
        .build();
  }

  private  CSVFileManager(Builder builder);

}