// Copyright (c) quickfixengine.org  All rights reserved.
//
// This file may be distributed under the terms of the quickfixengine.org
// license as defined by quickfixengine.org and appearing in the file
// LICENSE included in the packaging of this file.
//
// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
// PARTICULAR PURPOSE.
//
// See http://www.quickfixengine.org/LICENSE for licensing information.
//
// Contact ask@quickfixengine.org if any conditions of this licensing
// are not clear to you.

package quickfix

import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"regexp"

	"quantex.com/qfixdpl/quickfix/config"
)

// The Settings type represents a collection of global and session settings.
type Settings struct {
	globalSettings  *SessionSettings
	sessionSettings map[SessionID]*SessionSettings
}

// Init initializes or resets a Settings instance.
func (s *Settings) Init() {
	s.globalSettings = NewSessionSettings()
	s.sessionSettings = make(map[SessionID]*SessionSettings)
}

func (s *Settings) lazyInit() {
	if s.globalSettings == nil {
		s.Init()
	}
}

// NewSettings creates a Settings instance.
func NewSettings() *Settings {
	s := &Settings{}
	s.Init()
	return s
}

func sessionIDFromSessionSettings(globalSettings *SessionSettings, sessionSettings *SessionSettings) SessionID {
	sessionID := SessionID{}

	for _, settings := range []*SessionSettings{globalSettings, sessionSettings} {
		if settings.HasSetting(config.BeginString) {
			sessionID.BeginString, _ = settings.Setting(config.BeginString)
		}

		if settings.HasSetting(config.TargetCompID) {
			sessionID.TargetCompID, _ = settings.Setting(config.TargetCompID)
		}

		if settings.HasSetting(config.TargetSubID) {
			sessionID.TargetSubID, _ = settings.Setting(config.TargetSubID)
		}

		if settings.HasSetting(config.TargetLocationID) {
			sessionID.TargetLocationID, _ = settings.Setting(config.TargetLocationID)
		}

		if settings.HasSetting(config.SenderCompID) {
			sessionID.SenderCompID, _ = settings.Setting(config.SenderCompID)
		}

		if settings.HasSetting(config.SenderSubID) {
			sessionID.SenderSubID, _ = settings.Setting(config.SenderSubID)
		}

		if settings.HasSetting(config.SenderLocationID) {
			sessionID.SenderLocationID, _ = settings.Setting(config.SenderLocationID)
		}

		if settings.HasSetting(config.SessionQualifier) {
			sessionID.Qualifier, _ = settings.Setting(config.SessionQualifier)
		}
	}

	return sessionID
}

// ParseSettings creates and initializes a Settings instance with config parsed from a Reader.
// Returns error if the config is has parse errors.
func ParseSettings(reader io.Reader) (*Settings, error) {
	s := NewSettings()

	scanner := bufio.NewScanner(reader)
	blankRegEx := regexp.MustCompile(`^\s*$`)
	commentRegEx := regexp.MustCompile(`^#.*`)
	defaultRegEx := regexp.MustCompile(`^\[(?i)DEFAULT\]\s*$`)
	sessionRegEx := regexp.MustCompile(`^\[(?i)SESSION\]\s*$`)
	settingRegEx := regexp.MustCompile(`^([^=]*)=(.*)$`)

	var settings *SessionSettings

	lineNumber := 0
	for scanner.Scan() {
		lineNumber++
		line := scanner.Text()

		switch {
		case commentRegEx.MatchString(line) || blankRegEx.MatchString(line):
			continue

		case defaultRegEx.MatchString(line):
			settings = s.GlobalSettings()

		case sessionRegEx.MatchString(line):
			if settings != nil && settings != s.GlobalSettings() {
				if _, err := s.AddSession(settings); err != nil {
					return nil, err
				}
			}
			settings = NewSessionSettings()

		case settingRegEx.MatchString(line):
			parts := settingRegEx.FindStringSubmatch(line)
			settings.Set(parts[1], parts[2])

		default:
			return s, fmt.Errorf("error parsing line %v", lineNumber)
		}
	}

	if err := scanner.Err(); err != nil {
		return s, err
	}

	if settings == nil || settings == s.GlobalSettings() {
		return s, fmt.Errorf("no sessions declared")
	}
	_, err := s.AddSession(settings)

	return s, err
}

// GlobalSettings are default setting inherited by all session settings.
func (s *Settings) GlobalSettings() *SessionSettings {
	s.lazyInit()
	return s.globalSettings
}

// SessionSettings return all session settings overlaying globalsettings.
func (s *Settings) SessionSettings() map[SessionID]*SessionSettings {
	allSessionSettings := make(map[SessionID]*SessionSettings)

	for sessionID, settings := range s.sessionSettings {
		cloneSettings := s.globalSettings.clone()
		cloneSettings.overlay(settings)
		allSessionSettings[sessionID] = cloneSettings
	}

	return allSessionSettings
}

// AddSession adds Session Settings to Settings instance. Returns an error if session settings with duplicate sessionID has already been added.
func (s *Settings) AddSession(sessionSettings *SessionSettings) (SessionID, error) {
	s.lazyInit()

	sessionID := sessionIDFromSessionSettings(s.GlobalSettings(), sessionSettings)

	switch sessionID.BeginString {
	case BeginStringFIX40:
	case BeginStringFIX41:
	case BeginStringFIX42:
	case BeginStringFIX43:
	case BeginStringFIX44:
	case BeginStringFIXT11:
	default:
		return sessionID, errors.New("BeginString must be FIX.4.0 to FIX.4.4 or FIXT.1.1")
	}

	if _, dup := s.sessionSettings[sessionID]; dup {
		return sessionID, fmt.Errorf("duplicate session configured for %v", sessionID)
	}

	s.sessionSettings[sessionID] = sessionSettings

	return sessionID, nil
}
