package configloader

import (
    "os"
    "encoding/json"
    "path/filepath"
    "io/ioutil"
)

type MDWebDavUploaderConfig struct {
    InstanceLink    string            `json:"instance"`
    Username        string            `json:"username"`
    Mail            string            `json:"mail"`
    WebDavAuthToken string            `json:"token"`
    InstitutionId   int               `json:"institution_id"`
    Parser          string            `json:"parser"`
    MetadataFolder  string            `json:"metadata_folder"`
    MediaFolder     string            `json:"media_folder"`
    PublishOnImport bool              `json:"visible"`
    Settings        map[string]string `json:"settings"`
}

// Returns the file path of the configuration file within the supplied
// directory. Moved to a dedicated function to provide a consistent
// filename.
func getConfigFileNameByDir(folder string) string {
    return filepath.Join(folder, "config.json")
}

// Returns a uniform directory for the configuration of this tool.
// To be compatible across operating systems, this will be a JSON
// file in the same directory as the current programm.
func getConfigFilepath() (string, error) {

    // Get the OS-dependent configuration directory.
    generalConfigDir, dirErr := os.UserConfigDir()
    if dirErr != nil {
        return "", dirErr
    }

    // Select a subdirectory of that directory to store application-specific
    // settings in. Attempt to create it and return the config filepath as
    // a file in that directory.
	configDir := filepath.Join(generalConfigDir, "museum-digital-uploader")
    generateConfigDirErr := os.Mkdir(configDir, 0700)

    // The config directory could be created, return path of the configuration
    // file in it.
    if generateConfigDirErr == nil {
        return getConfigFileNameByDir(configDir), nil // Use this to create files
    }

    // There has been an error creating the config directory.
    // This may be either that the directory already exists, which is alright
    // (and even expected on most runs). Or it might be, that the path is
    // already occupied with a file. Or something else. In those cases,
    // the error should be returned.

    // If the path is already occupied, inspect it more closely.
    if os.IsExist(generateConfigDirErr) {

        info, err := os.Stat(configDir)
        if err != nil {
            return "", err
        }

        // The directory already exists and is a directory. This is fine,
        // so we return the config filename in the existing config directory.
        if info.IsDir() {
            return getConfigFileNameByDir(configDir), nil // Use this to create files
        }

    }

    return "", generateConfigDirErr

}

// Wrapper around getConfigFilepath() allowing for an override for testing.
func getConfigFilepathOrOverride(overridePath string) (string, error) {

    // Override path is not validated, as it should only be used
    // in test settings
    if overridePath != "" {
        return overridePath, nil
    }

    configFilePath, err := getConfigFilepath()
    if err != nil {
        return "", err
    }

    return configFilePath, nil

}

// Validates each of the values of MDWebDavUploaderConfig.
func ValidateConfig(conf MDWebDavUploaderConfig) (MDWebDavUploaderConfig, error) {

    // Validate and clean instance link
    instanceLink, instanceErr := ValidateInstanceLink(conf.InstanceLink)
    if instanceErr != nil {
        return conf, instanceErr
    }
    conf.InstanceLink = instanceLink

    // Validate and clean mail
    mailLink, mailErr := ValidateMail(conf.Mail)
    if mailErr != nil {
        return conf, mailErr
    }
    conf.Mail = mailLink

    // Validate and clean institution ID
    institutionIdLink, institutionIdErr := ValidateInstitutionId(conf.InstitutionId, conf.InstanceLink)
    if institutionIdErr != nil {
        return conf, institutionIdErr
    }
    conf.InstitutionId = institutionIdLink

    // Validate and clean parser
    parserLink, parserErr := ValidateParser(conf.Parser)
    if parserErr != nil {
        return conf, parserErr
    }
    conf.Parser = parserLink

    // Validate and clean metadata folder
    metadataFolder, mFolderErr := ValidateUploadDir(conf.MetadataFolder)
    if mFolderErr != nil {
        return conf, mFolderErr
    }
    conf.MetadataFolder = metadataFolder

    // Validate and clean media folder
    mediaFolder, mediaFolderErr := ValidateUploadDir(conf.MediaFolder)
    if mediaFolderErr != nil {
        return conf, mediaFolderErr
    }
    conf.MediaFolder = mediaFolder

    return conf, nil

}

// Loads configuration from the configuration file (located using
// getConfigFilepath()).
// The function parameter overridePath is only useful for test settings.
// Outside those, set an empty string to use the default config path.
// Output parameters:
// 1: Config
// 2: Requirement for a re-run of the setup
// 3: Error
func LoadFromFile(overridePath string) (MDWebDavUploaderConfig, bool, error) {

    // Get config file path
    configFile, configFileErr := getConfigFilepathOrOverride(overridePath)
    if configFileErr != nil {
        return MDWebDavUploaderConfig{}, false, configFileErr
    }

    // Read the file
    configFileBytes, ioErr := ioutil.ReadFile(configFile)
    if ioErr != nil {
        return MDWebDavUploaderConfig{}, true, ioErr
    }

    // Parse the file into a configuration struct
    var data MDWebDavUploaderConfig
    unmarshalErr := json.Unmarshal(configFileBytes, &data)

    if unmarshalErr != nil {
        return MDWebDavUploaderConfig{}, true, ioErr
    }

    // Validate data - the configuration file may have been altered externally
    config, validationErr := ValidateConfig(data)
    if validationErr != nil {
        return MDWebDavUploaderConfig{}, true, validationErr
    }

    // Config could be parsed
    return config, false, nil

}

// Stores a config from MDWebDavUploaderConfig to the config file.
// As with LoadFromFile(), it is possible to provide an overridePath for testing.
func StoreConfigToFile(conf MDWebDavUploaderConfig, overridePath string) error {

    config, validationErr := ValidateConfig(conf)
    if validationErr != nil {
        return validationErr
    }

    configFilePath, configFileErr := getConfigFilepathOrOverride(overridePath)
    if configFileErr != nil {
        return configFileErr
    }

    configJson, encodeErr := json.Marshal(config)
    if encodeErr != nil {
        return encodeErr
    }

    writeErr := ioutil.WriteFile(configFilePath, configJson, 0644)
    if writeErr != nil {
        return writeErr
    }

    return nil

}
