package swagger

import (
	"testing"
	"time"
)

type YesNo bool

func (y YesNo) MarshalJSON() ([]byte, error) {
	if y {
		return []byte("yes"), nil
	}
	return []byte("no"), nil
}

// clear && go test -v -test.run TestRef_Issue190 ...swagger
func TestRef_Issue190(t *testing.T) {
	type User struct {
		items []string
	}
	testJsonFromStruct(t, User{}, `{
  "swagger.User": {
   "id": "swagger.User",
   "required": [
    "items"
   ],
   "properties": {
    "items": {
     "type": "array",
     "items": {
      "type": "string"
     }
    }
   }
  }
 }`)
}

// clear && go test -v -test.run TestCustomMarshaller_Issue96 ...swagger
func TestCustomMarshaller_Issue96(t *testing.T) {
	type Vote struct {
		What YesNo
	}
	testJsonFromStruct(t, Vote{}, `{
  "swagger.Vote": {
   "id": "swagger.Vote",
   "required": [
    "What"
   ],
   "properties": {
    "What": {
     "type": "string"
    }
   }
  }
 }`)
}

// clear && go test -v -test.run TestPrimitiveTypes ...swagger
func TestPrimitiveTypes(t *testing.T) {
	type Prims struct {
		f float64
		t time.Time
	}
	testJsonFromStruct(t, Prims{}, `{
  "swagger.Prims": {
   "id": "swagger.Prims",
   "required": [
    "f",
    "t"
   ],
   "properties": {
    "f": {
     "type": "number",
     "format": "double"
    },
    "t": {
     "type": "string",
     "format": "date-time"
    }
   }
  }
 }`)
}

// clear && go test -v -test.run TestS1 ...swagger
func TestS1(t *testing.T) {
	type S1 struct {
		Id string
	}
	testJsonFromStruct(t, S1{}, `{
  "swagger.S1": {
   "id": "swagger.S1",
   "required": [
    "Id"
   ],
   "properties": {
    "Id": {
     "type": "string"
    }
   }
  }
 }`)
}

// clear && go test -v -test.run TestS2 ...swagger
func TestS2(t *testing.T) {
	type S2 struct {
		Ids []string
	}
	testJsonFromStruct(t, S2{}, `{
  "swagger.S2": {
   "id": "swagger.S2",
   "required": [
    "Ids"
   ],
   "properties": {
    "Ids": {
     "type": "array",
     "items": {
      "type": "string"
     }
    }
   }
  }
 }`)
}

// clear && go test -v -test.run TestS3 ...swagger
func TestS3(t *testing.T) {
	type NestedS3 struct {
		Id string
	}
	type S3 struct {
		Nested NestedS3
	}
	testJsonFromStruct(t, S3{}, `{
  "swagger.NestedS3": {
   "id": "swagger.NestedS3",
   "required": [
    "Id"
   ],
   "properties": {
    "Id": {
     "type": "string"
    }
   }
  },
  "swagger.S3": {
   "id": "swagger.S3",
   "required": [
    "Nested"
   ],
   "properties": {
    "Nested": {
     "$ref": "swagger.NestedS3"
    }
   }
  }
 }`)
}

type sample struct {
	id       string `swagger:"required"` // TODO
	items    []item
	rootItem item `json:"root" description:"root desc"`
}

type item struct {
	itemName string `json:"name"`
}

// clear && go test -v -test.run TestSampleToModelAsJson ...swagger
func TestSampleToModelAsJson(t *testing.T) {
	testJsonFromStruct(t, sample{items: []item{}}, `{
  "swagger.item": {
   "id": "swagger.item",
   "required": [
    "name"
   ],
   "properties": {
    "name": {
     "type": "string"
    }
   }
  },
  "swagger.sample": {
   "id": "swagger.sample",
   "required": [
    "id",
    "items",
    "root"
   ],
   "properties": {
    "id": {
     "type": "string"
    },
    "items": {
     "type": "array",
     "items": {
      "$ref": "swagger.item"
     }
    },
    "root": {
     "$ref": "swagger.item",
     "description": "root desc"
    }
   }
  }
 }`)
}

func TestJsonTags(t *testing.T) {
	type X struct {
		A string
		B string `json:"-"`
		C int    `json:",string"`
		D int    `json:","`
	}

	expected := `{
  "swagger.X": {
   "id": "swagger.X",
   "required": [
    "A",
    "C",
    "D"
   ],
   "properties": {
    "A": {
     "type": "string"
    },
    "C": {
     "type": "string"
    },
    "D": {
     "type": "integer",
     "format": "int32"
    }
   }
  }
 }`

	testJsonFromStruct(t, X{}, expected)
}

func TestJsonTagOmitempty(t *testing.T) {
	type X struct {
		A int `json:",omitempty"`
		B int `json:"C,omitempty"`
	}

	expected := `{
  "swagger.X": {
   "id": "swagger.X",
   "properties": {
    "A": {
     "type": "integer",
     "format": "int32"
    },
    "C": {
     "type": "integer",
     "format": "int32"
    }
   }
  }
 }`

	testJsonFromStruct(t, X{}, expected)
}

func TestJsonTagName(t *testing.T) {
	type X struct {
		A string `json:"B"`
	}

	expected := `{
  "swagger.X": {
   "id": "swagger.X",
   "required": [
    "B"
   ],
   "properties": {
    "B": {
     "type": "string"
    }
   }
  }
 }`

	testJsonFromStruct(t, X{}, expected)
}

func TestAnonymousStruct(t *testing.T) {
	type X struct {
		A struct {
			B int
		}
	}

	expected := `{
  "swagger.X": {
   "id": "swagger.X",
   "required": [
    "A"
   ],
   "properties": {
    "A": {
     "$ref": "swagger.X.A"
    }
   }
  },
  "swagger.X.A": {
   "id": "swagger.X.A",
   "required": [
    "B"
   ],
   "properties": {
    "B": {
     "type": "integer",
     "format": "int32"
    }
   }
  }
 }`

	testJsonFromStruct(t, X{}, expected)
}

func TestAnonymousPtrStruct(t *testing.T) {
	type X struct {
		A *struct {
			B int
		}
	}

	expected := `{
  "swagger.X": {
   "id": "swagger.X",
   "required": [
    "A"
   ],
   "properties": {
    "A": {
     "$ref": "swagger.X.A"
    }
   }
  },
  "swagger.X.A": {
   "id": "swagger.X.A",
   "required": [
    "B"
   ],
   "properties": {
    "B": {
     "type": "integer",
     "format": "int32"
    }
   }
  }
 }`

	testJsonFromStruct(t, X{}, expected)
}

func TestAnonymousArrayStruct(t *testing.T) {
	type X struct {
		A []struct {
			B int
		}
	}

	expected := `{
  "swagger.X": {
   "id": "swagger.X",
   "required": [
    "A"
   ],
   "properties": {
    "A": {
     "type": "array",
     "items": {
      "$ref": "swagger.X.A"
     }
    }
   }
  },
  "swagger.X.A": {
   "id": "swagger.X.A",
   "required": [
    "B"
   ],
   "properties": {
    "B": {
     "type": "integer",
     "format": "int32"
    }
   }
  }
 }`

	testJsonFromStruct(t, X{}, expected)
}

func TestAnonymousPtrArrayStruct(t *testing.T) {
	type X struct {
		A *[]struct {
			B int
		}
	}

	expected := `{
  "swagger.X": {
   "id": "swagger.X",
   "required": [
    "A"
   ],
   "properties": {
    "A": {
     "type": "array",
     "items": {
      "$ref": "swagger.X.A"
     }
    }
   }
  },
  "swagger.X.A": {
   "id": "swagger.X.A",
   "required": [
    "B"
   ],
   "properties": {
    "B": {
     "type": "integer",
     "format": "int32"
    }
   }
  }
 }`

	testJsonFromStruct(t, X{}, expected)
}

// go test -v -test.run TestEmbeddedStruct_Issue98 ...swagger
func TestEmbeddedStruct_Issue98(t *testing.T) {
	type Y struct {
		A int
	}
	type X struct {
		Y
	}
	testJsonFromStruct(t, X{}, `{
  "swagger.X": {
   "id": "swagger.X",
   "required": [
    "A"
   ],
   "properties": {
    "A": {
     "type": "integer",
     "format": "int32"
    }
   }
  }
 }`)
}

type Dataset struct {
	Names []string
}

// clear && go test -v -test.run TestIssue85 ...swagger
func TestIssue85(t *testing.T) {
	anon := struct{ Datasets []Dataset }{}
	testJsonFromStruct(t, anon, `{
  "struct { Datasets ||swagger.Dataset }": {
   "id": "struct { Datasets ||swagger.Dataset }",
   "required": [
    "Datasets"
   ],
   "properties": {
    "Datasets": {
     "type": "array",
     "items": {
      "$ref": "swagger.Dataset"
     }
    }
   }
  },
  "swagger.Dataset": {
   "id": "swagger.Dataset",
   "required": [
    "Names"
   ],
   "properties": {
    "Names": {
     "type": "array",
     "items": {
      "type": "string"
     }
    }
   }
  }
 }`)
}

type File struct {
	History     []File
	HistoryPtrs []*File
}

// go test -v -test.run TestRecursiveStructure ...swagger
func TestRecursiveStructure(t *testing.T) {
	testJsonFromStruct(t, File{}, `{
  "swagger.File": {
   "id": "swagger.File",
   "required": [
    "History",
    "HistoryPtrs"
   ],
   "properties": {
    "History": {
     "type": "array",
     "items": {
      "$ref": "swagger.File"
     }
    },
    "HistoryPtrs": {
     "type": "array",
     "items": {
      "$ref": "swagger.File"
     }
    }
   }
  }
 }`)
}

type A1 struct {
	B struct {
		Id      int
		Comment string `json:"comment,omitempty"`
	}
}

// go test -v -test.run TestEmbeddedStructA1 ...swagger
func TestEmbeddedStructA1(t *testing.T) {
	testJsonFromStruct(t, A1{}, `{
  "swagger.A1": {
   "id": "swagger.A1",
   "required": [
    "B"
   ],
   "properties": {
    "B": {
     "$ref": "swagger.A1.B"
    }
   }
  },
  "swagger.A1.B": {
   "id": "swagger.A1.B",
   "required": [
    "Id"
   ],
   "properties": {
    "Id": {
     "type": "integer",
     "format": "int32"
    },
    "comment": {
     "type": "string"
    }
   }
  }
 }`)
}

type A2 struct {
	C
}
type C struct {
	Id      int    `json:"B"`
	Comment string `json:"comment,omitempty"`
	Secure  bool   `json:"secure"`
}

// go test -v -test.run TestEmbeddedStructA2 ...swagger
func TestEmbeddedStructA2(t *testing.T) {
	testJsonFromStruct(t, A2{}, `{
  "swagger.A2": {
   "id": "swagger.A2",
   "required": [
    "B",
    "secure"
   ],
   "properties": {
    "B": {
     "type": "integer",
     "format": "int32"
    },
    "comment": {
     "type": "string"
    },
    "secure": {
     "type": "boolean"
    }
   }
  }
 }`)
}

type A3 struct {
	B D
}

type D struct {
	Id int
}

// clear && go test -v -test.run TestStructA3 ...swagger
func TestStructA3(t *testing.T) {
	testJsonFromStruct(t, A3{}, `{
  "swagger.A3": {
   "id": "swagger.A3",
   "required": [
    "B"
   ],
   "properties": {
    "B": {
     "$ref": "swagger.D"
    }
   }
  },
  "swagger.D": {
   "id": "swagger.D",
   "required": [
    "Id"
   ],
   "properties": {
    "Id": {
     "type": "integer",
     "format": "int32"
    }
   }
  }
 }`)
}

type ObjectId []byte

type Region struct {
	Id   ObjectId `bson:"_id" json:"id"`
	Name string   `bson:"name" json:"name"`
	Type string   `bson:"type" json:"type"`
}

// clear && go test -v -test.run TestRegion_Issue113 ...swagger
func TestRegion_Issue113(t *testing.T) {
	testJsonFromStruct(t, []Region{}, `{
  "integer": {
   "id": "integer",
   "properties": {}
  },
  "swagger.Region": {
   "id": "swagger.Region",
   "required": [
    "id",
    "name",
    "type"
   ],
   "properties": {
    "id": {
     "type": "array",
     "items": {
      "$ref": "integer"
     }
    },
    "name": {
     "type": "string"
    },
    "type": {
     "type": "string"
    }
   }
  },
  "||swagger.Region": {
   "id": "||swagger.Region",
   "properties": {}
  }
 }`)
}

// clear && go test -v -test.run TestIssue158 ...swagger
func TestIssue158(t *testing.T) {
	type Address struct {
		Country string `json:"country,omitempty"`
	}

	type Customer struct {
		Name    string  `json:"name"`
		Address Address `json:"address"`
	}
	expected := `{
  "swagger.Address": {
   "id": "swagger.Address",
   "properties": {
    "country": {
     "type": "string"
    }
   }
  },
  "swagger.Customer": {
   "id": "swagger.Customer",
   "required": [
    "name",
    "address"
   ],
   "properties": {
    "address": {
     "$ref": "swagger.Address"
    },
    "name": {
     "type": "string"
    }
   }
  }
 }`
	testJsonFromStruct(t, Customer{}, expected)
}

func TestSlices(t *testing.T) {
	type Address struct {
		Country string `json:"country,omitempty"`
	}
	expected := `{
  "swagger.Address": {
   "id": "swagger.Address",
   "properties": {
    "country": {
     "type": "string"
    }
   }
  },
  "swagger.Customer": {
   "id": "swagger.Customer",
   "required": [
    "name",
    "addresses"
   ],
   "properties": {
    "addresses": {
     "type": "array",
     "items": {
      "$ref": "swagger.Address"
     }
    },
    "name": {
     "type": "string"
    }
   }
  }
 }`
	// both slices (with pointer value and with type value) should have equal swagger representation
	{
		type Customer struct {
			Name      string    `json:"name"`
			Addresses []Address `json:"addresses"`
		}
		testJsonFromStruct(t, Customer{}, expected)
	}
	{
		type Customer struct {
			Name      string     `json:"name"`
			Addresses []*Address `json:"addresses"`
		}
		testJsonFromStruct(t, Customer{}, expected)
	}

}
