1// Copyright 2015 Brian J. Downs
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package openweathermap
16
17import (
18	"errors"
19	"net/http"
20)
21
22var errUnitUnavailable = errors.New("unit unavailable")
23var errLangUnavailable = errors.New("language unavailable")
24var errInvalidKey = errors.New("invalid api key")
25var errInvalidOption = errors.New("invalid option")
26var errInvalidHttpClient = errors.New("invalid http client")
27var errForecastUnavailable = errors.New("forecast unavailable")
28
29// DataUnits represents the character chosen to represent the temperature notation
30var DataUnits = map[string]string{"C": "metric", "F": "imperial", "K": "internal"}
31var (
32	baseURL        = "http://api.openweathermap.org/data/2.5/weather?%s"
33	iconURL        = "http://openweathermap.org/img/w/%s"
34	stationURL     = "http://api.openweathermap.org/data/2.5/station?id=%d"
35	forecast5Base  = "http://api.openweathermap.org/data/2.5/forecast?appid=%s&%s&mode=json&units=%s&lang=%s&cnt=%d"
36	forecast16Base = "http://api.openweathermap.org/data/2.5/forecast/daily?appid=%s&%s&mode=json&units=%s&lang=%s&cnt=%d"
37	historyURL     = "http://api.openweathermap.org/data/2.5/history/%s"
38	pollutionURL   = "http://api.openweathermap.org/pollution/v1/co/"
39	uvURL          = "http://api.openweathermap.org/data/2.5/"
40	dataPostURL    = "http://openweathermap.org/data/post"
41)
42
43// LangCodes holds all supported languages to be used
44// inspried and sourced from @bambocher (github.com/bambocher)
45var LangCodes = map[string]string{
46	"EN":    "English",
47	"RU":    "Russian",
48	"IT":    "Italian",
49	"ES":    "Spanish",
50	"SP":    "Spanish",
51	"UK":    "Ukrainian",
52	"UA":    "Ukrainian",
53	"DE":    "German",
54	"PT":    "Portuguese",
55	"RO":    "Romanian",
56	"PL":    "Polish",
57	"FI":    "Finnish",
58	"NL":    "Dutch",
59	"FR":    "French",
60	"BG":    "Bulgarian",
61	"SV":    "Swedish",
62	"SE":    "Swedish",
63	"TR":    "Turkish",
64	"HR":    "Croatian",
65	"CA":    "Catalan",
66	"ZH_TW": "Chinese Traditional",
67	"ZH":    "Chinese Simplified",
68	"ZH_CN": "Chinese Simplified",
69}
70
71// Config will hold default settings to be passed into the
72// "NewCurrent, NewForecast, etc}" functions.
73type Config struct {
74	Mode     string // user choice of JSON or XML
75	Unit     string // measurement for results to be displayed.  F, C, or K
76	Lang     string // should reference a key in the LangCodes map
77	APIKey   string // API Key for connecting to the OWM
78	Username string // Username for posting data
79	Password string // Pasword for posting data
80}
81
82// APIError returned on failed API calls.
83type APIError struct {
84	Message string `json:"message"`
85	COD     string `json:"cod"`
86}
87
88// Coordinates struct holds longitude and latitude data in returned
89// JSON or as parameter data for requests using longitude and latitude.
90type Coordinates struct {
91	Longitude float64 `json:"lon"`
92	Latitude  float64 `json:"lat"`
93}
94
95// Sys struct contains general information about the request
96// and the surrounding area for where the request was made.
97type Sys struct {
98	Type    int     `json:"type"`
99	ID      int     `json:"id"`
100	Message float64 `json:"message"`
101	Country string  `json:"country"`
102	Sunrise int     `json:"sunrise"`
103	Sunset  int     `json:"sunset"`
104}
105
106// Wind struct contains the speed and degree of the wind.
107type Wind struct {
108	Speed float64 `json:"speed"`
109	Deg   float64 `json:"deg"`
110}
111
112// Weather struct holds high-level, basic info on the returned
113// data.
114type Weather struct {
115	ID          int    `json:"id"`
116	Main        string `json:"main"`
117	Description string `json:"description"`
118	Icon        string `json:"icon"`
119}
120
121// Main struct contains the temperates, humidity, pressure for the request.
122type Main struct {
123	Temp      float64 `json:"temp"`
124	TempMin   float64 `json:"temp_min"`
125	TempMax   float64 `json:"temp_max"`
126	Pressure  float64 `json:"pressure"`
127	SeaLevel  float64 `json:"sea_level"`
128	GrndLevel float64 `json:"grnd_level"`
129	Humidity  int     `json:"humidity"`
130}
131
132// Clouds struct holds data regarding cloud cover.
133type Clouds struct {
134	All int `json:"all"`
135}
136
137// 	return key
138// }
139func setKey(key string) (string, error) {
140	if err := ValidAPIKey(key); err != nil {
141		return "", err
142	}
143	return key, nil
144}
145
146// ValidDataUnit makes sure the string passed in is an accepted
147// unit of measure to be used for the return data.
148func ValidDataUnit(u string) bool {
149	for d := range DataUnits {
150		if u == d {
151			return true
152		}
153	}
154	return false
155}
156
157// ValidLangCode makes sure the string passed in is an
158// acceptable lang code.
159func ValidLangCode(c string) bool {
160	for d := range LangCodes {
161		if c == d {
162			return true
163		}
164	}
165	return false
166}
167
168// ValidDataUnitSymbol makes sure the string passed in is an
169// acceptable data unit symbol.
170func ValidDataUnitSymbol(u string) bool {
171	for _, d := range DataUnits {
172		if u == d {
173			return true
174		}
175	}
176	return false
177}
178
179// ValidAPIKey makes sure that the key given is a valid one
180func ValidAPIKey(key string) error {
181	if len(key) != 32 {
182		return errors.New("invalid key")
183	}
184	return nil
185}
186
187// CheckAPIKeyExists will see if an API key has been set.
188func (c *Config) CheckAPIKeyExists() bool { return len(c.APIKey) > 1 }
189
190// Settings holds the client settings
191type Settings struct {
192	client *http.Client
193}
194
195// NewSettings returns a new Setting pointer with default http client.
196func NewSettings() *Settings {
197	return &Settings{
198		client: http.DefaultClient,
199	}
200}
201
202// Optional client settings
203type Option func(s *Settings) error
204
205// WithHttpClient sets custom http client when creating a new Client.
206func WithHttpClient(c *http.Client) Option {
207	return func(s *Settings) error {
208		if c == nil {
209			return errInvalidHttpClient
210		}
211		s.client = c
212		return nil
213	}
214}
215
216// setOptions sets Optional client settings to the Settings pointer
217func setOptions(settings *Settings, options []Option) error {
218	for _, option := range options {
219		if option == nil {
220			return errInvalidOption
221		}
222		err := option(settings)
223		if err != nil {
224			return err
225		}
226	}
227	return nil
228}
229