/**
   * Create Fast Track Package.
   *
   * @param packageId The id of the package to start FastTrack
   * @param signers The signers to get the signing url
   * @return The signing url
   */
  public String startFastTrack(PackageId packageId, List<FastTrackSigner> signers) {
    String token = getFastTrackToken(packageId, true);
    String path =
        template.urlFor(UrlTemplate.START_FAST_TRACK_PATH).replace("{token}", token).build();

    List<FastTrackRole> roles = new ArrayList<FastTrackRole>();
    for (FastTrackSigner signer : signers) {
      FastTrackRole role =
          FastTrackRoleBuilder.newRoleWithId(signer.getId())
              .withName(signer.getId())
              .withSigner(signer)
              .build();
      roles.add(role);
    }

    String json = Serialization.toJson(roles);
    try {
      String response = client.post(path, json);
      SigningUrl signingUrl = Serialization.fromJson(response, SigningUrl.class);
      return signingUrl.getUrl();
    } catch (RequestException e) {
      throw new EslException("Could not start fast track.", e);
    } catch (Exception e) {
      throw new EslException("Could not start fast track." + " Exception: " + e.getMessage());
    }
  }
  /**
   * Creates a package with roles.
   *
   * @param aPackage
   * @return PackageId
   * @throws com.silanis.esl.sdk.EslException
   */
  public PackageId createPackage(Package aPackage) throws EslException {
    String path = template.urlFor(UrlTemplate.PACKAGE_PATH).build();
    String packageJson = Serialization.toJson(aPackage);

    try {
      String response = client.post(path, packageJson);
      return Serialization.fromJson(response, PackageId.class);
    } catch (RequestException e) {
      throw new EslServerException("Could not create a new package", e);
    } catch (Exception e) {
      throw new EslException("Could not create a new package", e);
    }
  }
  /**
   * Creates a package and uploads the documents in one step
   *
   * @param aPackage
   * @param documents
   * @return
   * @throws EslException
   */
  public PackageId createPackageOneStep(
      Package aPackage, Collection<com.silanis.esl.sdk.Document> documents) throws EslException {
    String path = template.urlFor(UrlTemplate.PACKAGE_PATH).build();
    String packageJson = Serialization.toJson(aPackage);

    try {
      String response = client.postMultipartPackage(path, documents, packageJson);
      return Serialization.fromJson(response, PackageId.class);

    } catch (RequestException e) {
      throw new EslServerException("Could not create a new package in one-step", e);
    } catch (Exception e) {
      throw new EslException("Could not create a new package in one-step", e);
    }
  }
  /**
   * Get the document's metadata from the package.
   *
   * @param documentPackage The DocumentPackage we want to get document from.
   * @param documentId Id of document to get.
   * @return the document's metadata
   */
  public com.silanis.esl.sdk.Document getDocumentMetadata(
      DocumentPackage documentPackage, String documentId) {
    String path =
        template
            .urlFor(UrlTemplate.DOCUMENT_ID_PATH)
            .replace("{packageId}", documentPackage.getId().getId())
            .replace("{documentId}", documentId)
            .build();

    try {
      String response = client.get(path);
      Document apilDocument = Serialization.fromJson(response, Document.class);

      // Wipe out the members not related to the metadata
      apilDocument.setApprovals(new ArrayList<Approval>());
      apilDocument.setFields(new ArrayList<Field>());
      apilDocument.setPages(new ArrayList<com.silanis.esl.api.model.Page>());

      return new DocumentConverter(
              apilDocument, new DocumentPackageConverter(documentPackage).toAPIPackage())
          .toSDKDocument();
    } catch (RequestException e) {
      throw new EslServerException("Could not get the document's metadata.", e);
    } catch (Exception e) {
      throw new EslException(
          "Could not get the document's metadata." + " Exception: " + e.getMessage());
    }
  }
  public com.silanis.esl.sdk.Document uploadApiDocument(
      String packageId, String fileName, byte[] fileBytes, Document document) {
    String path =
        template.urlFor(UrlTemplate.DOCUMENT_PATH).replace("{packageId}", packageId).build();

    String documentJson = Serialization.toJson(document);

    try {
      String response = client.postMultipartFile(path, fileName, fileBytes, documentJson);
      com.silanis.esl.api.model.Document uploadedDocument =
          Serialization.fromJson(response, com.silanis.esl.api.model.Document.class);
      return new DocumentConverter(uploadedDocument, getApiPackage(packageId)).toSDKDocument();
    } catch (RequestException e) {
      throw new EslServerException("Could not upload document to package.", e);
    } catch (Exception e) {
      throw new EslException("Could not upload document to package.", e);
    }
  }
  /**
   * Adds a signer to the specified package
   *
   * @param packageId The id of the package in which the signer will be added
   * @param signer The signer to be added
   * @return The role id of the signer
   */
  public String addSigner(PackageId packageId, com.silanis.esl.sdk.Signer signer) {
    Role apiPayload =
        new SignerConverter(signer).toAPIRole(UUID.randomUUID().toString().replace("-", ""));

    String path =
        template
            .urlFor(UrlTemplate.ADD_SIGNER_PATH)
            .replace("{packageId}", packageId.getId())
            .build();

    try {
      String json = Serialization.toJson(apiPayload);
      String response = client.post(path, json);
      Role apiRole = Serialization.fromJson(response, Role.class);
      return apiRole.getId();

    } catch (RequestException e) {
      throw new EslServerException("Could not add signer.", e);
    } catch (Exception e) {
      throw new EslException("Could not add signer." + " Exception: " + e.getMessage());
    }
  }
  /**
   * Create a new package based on an existing template.
   *
   * @param packageId
   * @param aPackage
   * @return PackageId
   */
  public PackageId createPackageFromTemplate(PackageId packageId, Package aPackage) {
    String path =
        template
            .urlFor(UrlTemplate.TEMPLATE_PATH)
            .replace("{packageId}", packageId.getId())
            .build();

    List<Role> roles = aPackage.getRoles();

    aPackage.setRoles(Collections.<Role>emptyList());

    String packageJson = Serialization.toJson(aPackage);
    PackageId newPackageId = null;
    try {

      String response = client.post(path, packageJson);

      newPackageId = Serialization.fromJson(response, PackageId.class);
    } catch (RequestException e) {
      throw new EslServerException("Could not create a new package", e);
    } catch (Exception e) {
      throw new EslException("Could not create a new package", e);
    }

    Package createdPackage = getApiPackage(newPackageId.getId());

    for (Role role : roles) {
      String roleUid = findRoleUidByName(createdPackage.getRoles(), role.getName());

      if (roleUid == null) {
        continue;
      }

      role.setId(roleUid);
      updateRole(newPackageId, role);
    }

    return newPackageId;
  }
  /**
   * Gets the package.
   *
   * @param packageId
   * @return Package
   * @throws EslException
   */
  public Package getApiPackage(String packageId) throws EslException {
    String path =
        template.urlFor(UrlTemplate.PACKAGE_ID_PATH).replace("{packageId}", packageId).build();
    String stringResponse;
    try {
      stringResponse = client.get(path);
    } catch (RequestException e) {
      throw new EslServerException("Could not get package.", e);
    } catch (Exception e) {
      throw new EslException("Could not get package.", e);
    }

    return Serialization.fromJson(stringResponse, Package.class);
  }
  /**
   * Adds a role to the package.
   *
   * @param packageId
   * @param role
   * @return The role added
   * @throws EslException
   */
  public Role addRole(PackageId packageId, Role role) throws EslException {
    String path =
        template.urlFor(UrlTemplate.ROLE_PATH).replace("{packageId}", packageId.getId()).build();

    String roleJson = JacksonUtil.serializeDirty(role);
    String stringResponse;
    try {
      stringResponse = client.post(path, roleJson);
    } catch (RequestException e) {
      throw new EslServerException("Could not add role.", e);
    } catch (Exception e) {
      throw new EslException("Could not add role.", e);
    }
    return Serialization.fromJson(stringResponse, Role.class);
  }
 /**
  * Gets the roles for a package.
  *
  * @param packageId
  * @return A list of the roles in the package
  * @throws EslException
  */
 public List<Role> getRoles(PackageId packageId) throws EslException {
   String path =
       template.urlFor(UrlTemplate.ROLE_PATH).replace("{packageId}", packageId.getId()).build();
   String stringResponse;
   try {
     stringResponse = client.get(path);
   } catch (RequestException e) {
     throw new EslServerException(
         "Could not retrieve list of roles for package with id " + packageId.getId(), e);
   } catch (Exception e) {
     throw new EslException(
         "Could not retrieve list of roles for package with id " + packageId.getId(), e);
   }
   return Serialization.fromJson(stringResponse, RoleList.class).getResults();
 }
  /**
   * Upload documents with external content to the package.
   *
   * @param packageId
   */
  public void addDocumentWithExternalContent(
      String packageId, List<com.silanis.esl.sdk.Document> providerDocuments) {
    String path =
        template.urlFor(UrlTemplate.DOCUMENT_PATH).replace("{packageId}", packageId).build();

    List<Document> apiDocuments = new ArrayList<Document>();
    for (com.silanis.esl.sdk.Document document : providerDocuments) {
      apiDocuments.add(new DocumentConverter(document).toAPIDocumentMetadata());
    }
    try {
      String json = Serialization.toJson(apiDocuments);
      client.post(path, json);
    } catch (RequestException e) {
      throw new EslServerException("Could not upload the documents.", e);
    } catch (Exception e) {
      throw new EslException("Could not upload the documents." + " Exception: " + e.getMessage());
    }
  }
  private String getFastTrackUrl(PackageId packageId, Boolean signing) {
    String path =
        template
            .urlFor(UrlTemplate.FAST_TRACK_URL_PATH)
            .replace("{packageId}", packageId.getId())
            .replace("{signing}", signing.toString())
            .build();

    try {
      String json = client.get(path);
      SigningUrl signingUrl = Serialization.fromJson(json, SigningUrl.class);
      return signingUrl.getUrl();
    } catch (RequestException e) {
      throw new EslException("Could not get a fastTrack url.", e);
    } catch (Exception e) {
      throw new EslException("Could not get a fastTrack url." + " Exception: " + e.getMessage());
    }
  }
  /**
   * Get thank you dialog content.
   *
   * @param packageId The id of the package to get thank you dialog content.
   * @return thank you dialog content
   */
  public String getThankYouDialogContent(PackageId packageId) {
    String path =
        template
            .urlFor(UrlTemplate.THANK_YOU_DIALOG_PATH)
            .replace("{packageId}", packageId.getId())
            .build();

    try {
      String json = client.get(path);
      Properties thankYouDialogContent = Serialization.fromJson(json, Properties.class);
      return thankYouDialogContent.getProperty("body");
    } catch (RequestException e) {
      throw new EslException("Could not get thank you dialog content.", e);
    } catch (Exception e) {
      throw new EslException(
          "Could not get thank you dialog content." + " Exception: " + e.getMessage());
    }
  }
  /**
   * Get a signer from the specified package
   *
   * @param packageId The id of the package in which to get the signer
   * @param signerId The id of signer to get
   * @return The signer
   */
  public com.silanis.esl.sdk.Signer getSigner(PackageId packageId, String signerId) {
    String path =
        template
            .urlFor(UrlTemplate.SIGNER_PATH)
            .replace("{packageId}", packageId.getId())
            .replace("{roleId}", signerId)
            .build();

    try {
      String response = client.get(path);
      Role apiRole = Serialization.fromJson(response, Role.class);
      return new SignerConverter(apiRole).toSDKSigner();

    } catch (RequestException e) {
      throw new EslServerException("Could not get signer.", e);
    } catch (Exception e) {
      throw new EslException("Could not get signer." + " Exception: " + e.getMessage());
    }
  }
  private String getSigningUrl(PackageId packageId, Role role) {

    String path =
        template
            .urlFor(UrlTemplate.SIGNER_URL_PATH)
            .replace("{packageId}", packageId.getId())
            .replace("{roleId}", role.getId())
            .build();

    try {
      String response = client.get(path);
      SigningUrl signingUrl = Serialization.fromJson(response, SigningUrl.class);
      return signingUrl.getUrl();
    } catch (RequestException e) {
      throw new EslException("Could not get a signing url.", e);
    } catch (Exception e) {
      throw new EslException("Could not get a signing url." + " Exception: " + e.getMessage());
    }
  }
  /**
   * Get package support configuration.
   *
   * @param packageId The id of the package to get package support configuration.
   * @return package support configuration
   */
  public SupportConfiguration getConfig(PackageId packageId) {
    String path =
        template
            .urlFor(UrlTemplate.PACKAGE_INFORMATION_CONFIG_PATH)
            .replace("{packageId}", packageId.getId())
            .build();

    try {
      String json = client.get(path);
      com.silanis.esl.api.model.SupportConfiguration apiSupportConfiguration =
          Serialization.fromJson(json, com.silanis.esl.api.model.SupportConfiguration.class);
      return new SupportConfigurationConverter(apiSupportConfiguration).toSDKSupportConfiguration();
    } catch (RequestException e) {
      throw new EslException("Could not get support configuration.", e);
    } catch (Exception e) {
      throw new EslException(
          "Could not get support configuration." + " Exception: " + e.getMessage());
    }
  }
  /**
   * Updates a signer's information from a package
   *
   * @param packageId The id of the package containing the signer to be updated
   * @param signer The signer with the updated information
   */
  public void updateSigner(PackageId packageId, com.silanis.esl.sdk.Signer signer) {
    Role apiPayload =
        new SignerConverter(signer).toAPIRole(UUID.randomUUID().toString().replace("-", ""));

    String path =
        template
            .urlFor(UrlTemplate.SIGNER_PATH)
            .replace("{packageId}", packageId.getId())
            .replace("{roleId}", signer.getId())
            .build();

    try {
      String json = Serialization.toJson(apiPayload);
      client.put(path, json);
    } catch (RequestException e) {
      throw new EslException("Could not update signer.", e);
    } catch (Exception e) {
      throw new EslException("Could not update signer." + " Exception: " + e.getMessage());
    }
  }
  /**
   * Updates the documents signing order
   *
   * @param documentPackage
   */
  public void orderDocuments(DocumentPackage documentPackage) {
    String path =
        template
            .urlFor(UrlTemplate.DOCUMENT_PATH)
            .replace("{packageId}", documentPackage.getId().getId())
            .build();

    List<Document> documents = new ArrayList<Document>();
    for (com.silanis.esl.sdk.Document document : documentPackage.getDocuments()) {
      documents.add(new DocumentConverter(document).toAPIDocumentMetadata());
    }
    try {
      String json = Serialization.toJson(documents);

      client.put(path, json);
    } catch (RequestException e) {
      throw new EslServerException("Could not order the documents.", e);
    } catch (Exception e) {
      throw new EslException("Could not order the documents." + " Exception: " + e.getMessage());
    }
  }
  /**
   * Updates the signer order in a package.
   *
   * @param documentPackage The id of the package to update signer order
   */
  public void orderSigners(DocumentPackage documentPackage) {
    String path =
        template
            .urlFor(UrlTemplate.ROLE_PATH)
            .replace("{packageId}", documentPackage.getId().getId())
            .build();

    List<Role> roles = new ArrayList<Role>();
    for (com.silanis.esl.sdk.Signer signer : documentPackage.getSigners()) {
      roles.add(new SignerConverter(signer).toAPIRole(signer.getId()));
    }

    try {
      String json = Serialization.toJson(roles);
      client.put(path, json);
    } catch (RequestException e) {
      throw new EslServerException("Could not order signers.", e);
    } catch (Exception e) {
      throw new EslException("Could not order signers." + " Exception: " + e.getMessage());
    }
  }
  /**
   * Updates the document's metadata from the package.
   *
   * @param documentPackage
   * @param document
   */
  public void updateDocumentMetadata(
      DocumentPackage documentPackage, com.silanis.esl.sdk.Document document) {
    String path =
        template
            .urlFor(UrlTemplate.DOCUMENT_ID_PATH)
            .replace("{packageId}", documentPackage.getId().getId())
            .replace("{documentId}", document.getId().toString())
            .build();

    Document internalDoc = new DocumentConverter(document).toAPIDocumentMetadata();

    try {
      String json = Serialization.toJson(internalDoc);

      client.put(path, json);
    } catch (RequestException e) {
      throw new EslServerException("Could not update the document's metadata.", e);
    } catch (Exception e) {
      throw new EslException(
          "Could not update the document's metadata." + " Exception: " + e.getMessage());
    }
  }
  /**
   * Updates the package's fields and roles.
   *
   * @param packageId
   * @param sdkPackage
   * @throws EslException
   */
  public void updatePackage(PackageId packageId, DocumentPackage sdkPackage) throws EslException {
    String path =
        template
            .urlFor(UrlTemplate.PACKAGE_ID_PATH)
            .replace("{packageId}", packageId.getId())
            .build();

    Package aPackage = new DocumentPackageConverter(sdkPackage).toAPIPackage();

    String packageJson = Serialization.toJson(aPackage);
    try {
      client.put(path, packageJson);
    } catch (RequestException e) {
      throw new EslServerException("Could not update the package.", e);
    } catch (Exception e) {
      throw new EslException("Could not update the package.", e);
    }
    // Update roles
    List<Role> roleList = aPackage.getRoles();
    for (Role role : roleList) {
      updateRole(packageId, role);
    }
  }