package config

import (
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"log"
	"os"
	"time"
)

type Config struct {
	CacheDir           string  `json:"cachedir"`
	DiffDir            string  `json:"diffdir"`
	Connection         string  `json:"connection"`
	MappingFile        string  `json:"mapping"`
	LimitTo            string  `json:"limitto"`
	LimitToCacheBuffer float64 `json:"limitto_cache_buffer"`
	Srid               int     `json:"srid"`
	Schemas            Schemas `json:"schemas"`
}

type Schemas struct {
	Import     string `json:"import"`
	Production string `json:"production"`
	Backup     string `json:"backup"`
}

const defaultSrid = 3857
const defaultCacheDir = "/tmp/imposm3"
const defaultSchemaImport = "import"
const defaultSchemaProduction = "public"
const defaultSchemaBackup = "backup"

var ImportFlags = flag.NewFlagSet("import", flag.ExitOnError)
var DiffFlags = flag.NewFlagSet("diff", flag.ExitOnError)

type _BaseOptions struct {
	Connection         string
	CacheDir           string
	DiffDir            string
	MappingFile        string
	Srid               int
	LimitTo            string
	LimitToCacheBuffer float64
	ConfigFile         string
	Httpprofile        string
	Quiet              bool
	Schemas            Schemas
}

func (o *_BaseOptions) updateFromConfig() error {
	conf := &Config{
		CacheDir: defaultCacheDir,
		Srid:     defaultSrid,
	}

	if o.ConfigFile != "" {
		f, err := os.Open(o.ConfigFile)
		if err != nil {
			return err
		}
		decoder := json.NewDecoder(f)

		err = decoder.Decode(&conf)
		if err != nil {
			return err
		}
	}

	if conf.Schemas.Import != "" && o.Schemas.Import == defaultSchemaImport {
		o.Schemas.Import = conf.Schemas.Import
	}
	if conf.Schemas.Production != "" && o.Schemas.Production == defaultSchemaProduction {
		o.Schemas.Production = conf.Schemas.Production
	}
	if conf.Schemas.Backup != "" && o.Schemas.Backup == defaultSchemaBackup {
		o.Schemas.Backup = conf.Schemas.Backup
	}

	if o.Connection == "" {
		o.Connection = conf.Connection
	}
	if conf.Srid == 0 {
		conf.Srid = defaultSrid
	}
	if o.Srid == defaultSrid {
		o.Srid = conf.Srid
	}
	if o.MappingFile == "" {
		o.MappingFile = conf.MappingFile
	}
	if o.LimitTo == "" {
		o.LimitTo = conf.LimitTo
	}
	if o.LimitTo == "NONE" {
		// allow overwrite from cmd line
		o.LimitTo = ""
	}
	if o.LimitToCacheBuffer == 0.0 {
		o.LimitToCacheBuffer = conf.LimitToCacheBuffer
	}
	if o.CacheDir == defaultCacheDir {
		o.CacheDir = conf.CacheDir
	}
	if o.DiffDir == "" {
		if conf.DiffDir == "" {
			// use CacheDir for backwards compatibility
			o.DiffDir = o.CacheDir
		} else {
			o.DiffDir = conf.DiffDir
		}
	}
	return nil
}

func (o *_BaseOptions) check() []error {
	errs := []error{}
	if o.Srid != 3857 && o.Srid != 4326 {
		errs = append(errs, errors.New("only -srid=3857 or -srid=4326 are supported"))
	}
	if o.MappingFile == "" {
		errs = append(errs, errors.New("missing mapping"))
	}
	return errs
}

type _ImportOptions struct {
	Overwritecache   bool
	Appendcache      bool
	Read             string
	Write            bool
	Optimize         bool
	Diff             bool
	DeployProduction bool
	RevertDeploy     bool
	RemoveBackup     bool
	DiffStateBefore  time.Duration
}

var BaseOptions = _BaseOptions{}
var ImportOptions = _ImportOptions{}

func addBaseFlags(flags *flag.FlagSet) {
	flags.StringVar(&BaseOptions.Connection, "connection", "", "connection parameters")
	flags.StringVar(&BaseOptions.CacheDir, "cachedir", defaultCacheDir, "cache directory")
	flags.StringVar(&BaseOptions.DiffDir, "diffdir", "", "diff directory for last.state.txt")
	flags.StringVar(&BaseOptions.MappingFile, "mapping", "", "mapping file")
	flags.IntVar(&BaseOptions.Srid, "srid", defaultSrid, "srs id")
	flags.StringVar(&BaseOptions.LimitTo, "limitto", "", "limit to geometries")
	flags.Float64Var(&BaseOptions.LimitToCacheBuffer, "limittocachebuffer", 0.0, "limit to buffer for cache")
	flags.StringVar(&BaseOptions.ConfigFile, "config", "", "config (json)")
	flags.StringVar(&BaseOptions.Httpprofile, "httpprofile", "", "bind address for profile server")
	flags.BoolVar(&BaseOptions.Quiet, "quiet", false, "quiet log output")
	flags.StringVar(&BaseOptions.Schemas.Import, "dbschema-import", defaultSchemaImport, "db schema for imports")
	flags.StringVar(&BaseOptions.Schemas.Production, "dbschema-production", defaultSchemaProduction, "db schema for production")
	flags.StringVar(&BaseOptions.Schemas.Backup, "dbschema-backup", defaultSchemaBackup, "db schema for backups")
}

func UsageImport() {
	fmt.Fprintf(os.Stderr, "Usage: %s %s [args]\n\n", os.Args[0], os.Args[1])
	ImportFlags.PrintDefaults()
	os.Exit(2)
}

func UsageDiff() {
	fmt.Fprintf(os.Stderr, "Usage: %s %s [args] [.osc.gz, ...]\n\n", os.Args[0], os.Args[1])
	DiffFlags.PrintDefaults()
	os.Exit(2)
}

func init() {
	ImportFlags.Usage = UsageImport
	DiffFlags.Usage = UsageDiff

	addBaseFlags(DiffFlags)
	addBaseFlags(ImportFlags)
	ImportFlags.BoolVar(&ImportOptions.Overwritecache, "overwritecache", false, "overwritecache")
	ImportFlags.BoolVar(&ImportOptions.Appendcache, "appendcache", false, "append cache")
	ImportFlags.StringVar(&ImportOptions.Read, "read", "", "read")
	ImportFlags.BoolVar(&ImportOptions.Write, "write", false, "write")
	ImportFlags.BoolVar(&ImportOptions.Optimize, "optimize", false, "optimize")
	ImportFlags.BoolVar(&ImportOptions.Diff, "diff", false, "enable diff support")
	ImportFlags.BoolVar(&ImportOptions.DeployProduction, "deployproduction", false, "deploy production")
	ImportFlags.BoolVar(&ImportOptions.RevertDeploy, "revertdeploy", false, "revert deploy to production")
	ImportFlags.BoolVar(&ImportOptions.RemoveBackup, "removebackup", false, "remove backups from deploy")
	ImportFlags.DurationVar(&ImportOptions.DiffStateBefore, "diff-state-before", 2*time.Hour, "set initial diff sequence before")
}

func ParseImport(args []string) {
	if len(args) == 0 {
		UsageImport()
	}
	err := ImportFlags.Parse(args)
	if err != nil {
		log.Fatal(err)
	}
	err = BaseOptions.updateFromConfig()
	if err != nil {
		log.Fatal(err)
	}
	errs := BaseOptions.check()
	if len(errs) != 0 {
		reportErrors(errs)
		UsageImport()
	}
}

func ParseDiffImport(args []string) {
	if len(args) == 0 {
		UsageDiff()
	}
	err := DiffFlags.Parse(args)
	if err != nil {
		log.Fatal(err)
	}

	err = BaseOptions.updateFromConfig()
	if err != nil {
		log.Fatal(err)
	}

	errs := BaseOptions.check()
	if len(errs) != 0 {
		reportErrors(errs)
		UsageDiff()
	}
}

func reportErrors(errs []error) {
	fmt.Println("errors in config/options:")
	for _, err := range errs {
		fmt.Printf("\t%s\n", err)
	}
	os.Exit(1)
}
