#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software Foundation,

# Convert a generic mesh to its dual. With open meshes it can get some wired   #
# effect on the borders.                                                       #

import bpy
from bpy.types import Operator
from bpy.props import (
        BoolProperty,
        EnumProperty,
        )
import bmesh
from .utils import *

class dual_mesh_tessellated(Operator):
    bl_idname = "object.dual_mesh_tessellated"
    bl_label = "Dual Mesh"
    bl_description = ("Generate a polygonal mesh using Tessellate. (Non-destructive)")
    bl_options = {'REGISTER', 'UNDO'}

    apply_modifiers : BoolProperty(
        name="Apply Modifiers",
        default=True,
        description="Apply object's modifiers"
        )

    source_faces : EnumProperty(
            items=[
                ('QUAD', 'Quad Faces', ''),
                ('TRI', 'Triangles', '')],
            name="Source Faces",
            description="Source polygons",
            default="QUAD",
            options={'LIBRARY_EDITABLE'}
            )

    def execute(self, context):
        auto_layer_collection()
        ob0 = context.object
        name1 = "DualMesh_{}_Component".format(self.source_faces)
        # Generate component
        if self.source_faces == 'QUAD':
            verts = [(0.0, 0.0, 0.0), (0.0, 0.5, 0.0),
                    (0.0, 1.0, 0.0), (0.5, 1.0, 0.0),
                    (1.0, 1.0, 0.0), (1.0, 0.5, 0.0),
                    (1.0, 0.0, 0.0), (0.5, 0.0, 0.0),
                    (1/3, 1/3, 0.0), (2/3, 2/3, 0.0)]
            edges = [(0,1), (1,2), (2,3), (3,4), (4,5), (5,6), (6,7),
                        (7,0), (1,8), (8,7), (3,9), (9,5), (8,9)]
            faces = [(7,8,1,0), (8,9,3,2,1), (9,5,4,3), (9,8,7,6,5)]
        else:
            verts = [(0.0,0.0,0.0), (0.5,0.0,0.0), (1.0,0.0,0.0), (0.0,1.0,0.0), (0.5,1.0,0.0), (1.0,1.0,0.0)]
            edges = [(0,1), (1,2), (2,5), (5,4), (4,3), (3,0), (1,4)]
            faces = [(0,1,4,3), (1,2,5,4)]

        # check pre-existing component
        try:
            _verts = [0]*len(verts)*3
            __verts = [c for co in verts for c in co]
            ob1 = bpy.data.objects[name1]
            ob1.data.vertices.foreach_get("co",_verts)
            for a, b in zip(_verts, __verts):
                if abs(a-b) > 0.0001:
                    raise ValueError
        except:
            me = bpy.data.meshes.new("Dual-Mesh")  # add a new mesh
            me.from_pydata(verts, edges, faces)
            me.update(calc_edges=True, calc_edges_loose=True)
            if self.source_faces == 'QUAD': n_seams = 8
            else: n_seams = 6
            for i in range(n_seams): me.edges[i].use_seam = True
            ob1 = bpy.data.objects.new(name1, me)
            bpy.context.collection.objects.link(ob1)
            # fix visualization issue
            bpy.context.view_layer.objects.active = ob1
            ob1.select_set(True)
            bpy.ops.object.editmode_toggle()
            bpy.ops.object.editmode_toggle()
            ob1.select_set(False)
            # hide component
            ob1.hide_select = True
            ob1.hide_render = True
            ob1.hide_viewport = True
        ob = convert_object_to_mesh(ob0,False,False)

        #bpy.context.view_layer.objects.active = ob
        ob.tissue_tessellate.component = ob1
        ob.tissue_tessellate.generator = ob0
        ob.tissue_tessellate.gen_modifiers = self.apply_modifiers
        ob.tissue_tessellate.merge = True
        ob.tissue_tessellate.bool_dissolve_seams = True
        if self.source_faces == 'TRI': ob.tissue_tessellate.fill_mode = 'FAN'
        bpy.ops.object.update_tessellate()
        ob.location = ob0.location
        ob.matrix_world = ob0.matrix_world
        return {'FINISHED'}

class dual_mesh(Operator):
    bl_idname = "object.dual_mesh"
    bl_label = "Convert to Dual Mesh"
    bl_description = ("Convert a generic mesh into a polygonal mesh. (Destructive)")
    bl_options = {'REGISTER', 'UNDO'}

    quad_method : EnumProperty(
            items=[('BEAUTY', 'Beauty',
                    'Split the quads in nice triangles, slower method'),
                    ('FIXED', 'Fixed',
                    'Split the quads on the 1st and 3rd vertices'),
                    ('FIXED_ALTERNATE', 'Fixed Alternate',
                    'Split the quads on the 2nd and 4th vertices'),
                    ('SHORTEST_DIAGONAL', 'Shortest Diagonal',
                    'Split the quads based on the distance between the vertices')
                    ],
            name="Quad Method",
            description="Method for splitting the quads into triangles",
            default="FIXED",
            options={'LIBRARY_EDITABLE'}
            )
    polygon_method : EnumProperty(
            items=[
                ('BEAUTY', 'Beauty', 'Arrange the new triangles evenly'),
                ('CLIP', 'Clip',
                 'Split the polygons with an ear clipping algorithm')],
            name="Polygon Method",
            description="Method for splitting the polygons into triangles",
            default="BEAUTY",
            options={'LIBRARY_EDITABLE'}
            )
    preserve_borders : BoolProperty(
            name="Preserve Borders",
            default=True,
            description="Preserve original borders"
            )
    apply_modifiers : BoolProperty(
            name="Apply Modifiers",
            default=True,
            description="Apply object's modifiers"
            )

    def execute(self, context):
        mode = context.mode
        if mode == 'EDIT_MESH':
            mode = 'EDIT'
        act = bpy.context.active_object
        if mode != 'OBJECT':
            sel = [act]
            bpy.ops.object.mode_set(mode='OBJECT')
        else:
            sel = bpy.context.selected_objects
        doneMeshes = []

        for ob0 in sel:
            if ob0.type != 'MESH':
                continue
            if ob0.data.name in doneMeshes:
                continue
            ob = ob0
            mesh_name = ob0.data.name

            # store linked objects
            clones = []
            n_users = ob0.data.users
            count = 0
            for o in bpy.data.objects:
                if o.type != 'MESH':
                    continue
                if o.data.name == mesh_name:
                    count += 1
                    clones.append(o)
                if count == n_users:
                    break

            if self.apply_modifiers:
                bpy.ops.object.convert(target='MESH')
            ob.data = ob.data.copy()
            bpy.ops.object.select_all(action='DESELECT')
            ob.select_set(True)
            bpy.context.view_layer.objects.active = ob0
            bpy.ops.object.mode_set(mode='EDIT')

            # prevent borders erosion
            bpy.ops.mesh.select_mode(
                    use_extend=False, use_expand=False, type='EDGE'
                    )
            bpy.ops.mesh.select_non_manifold(
                    extend=False, use_wire=False, use_boundary=True,
                    use_multi_face=False, use_non_contiguous=False,
                    use_verts=False
                    )
            bpy.ops.mesh.extrude_region_move(
                    MESH_OT_extrude_region={"mirror": False},
                    TRANSFORM_OT_translate={"value": (0, 0, 0)}
                    )

            bpy.ops.mesh.select_mode(
                    use_extend=False, use_expand=False, type='VERT',
                    action='TOGGLE'
                    )
            bpy.ops.mesh.select_all(action='SELECT')
            bpy.ops.mesh.quads_convert_to_tris(
                    quad_method=self.quad_method, ngon_method=self.polygon_method
                    )
            bpy.ops.mesh.select_all(action='DESELECT')
            bpy.ops.object.mode_set(mode='OBJECT')
            bpy.ops.object.modifier_add(type='SUBSURF')
            ob.modifiers[-1].name = "dual_mesh_subsurf"
            while True:
                bpy.ops.object.modifier_move_up(modifier="dual_mesh_subsurf")
                if ob.modifiers[0].name == "dual_mesh_subsurf":
                    break

            bpy.ops.object.modifier_apply(modifier='dual_mesh_subsurf')

            bpy.ops.object.mode_set(mode='EDIT')
            bpy.ops.mesh.select_all(action='DESELECT')

            verts = ob.data.vertices

            bpy.ops.object.mode_set(mode='OBJECT')
            verts[-1].select = True
            bpy.ops.object.mode_set(mode='EDIT')
            bpy.ops.mesh.select_more(use_face_step=False)

            bpy.ops.mesh.select_similar(
                type='EDGE', compare='EQUAL', threshold=0.01)
            bpy.ops.mesh.select_all(action='INVERT')

            bpy.ops.mesh.dissolve_verts()
            bpy.ops.mesh.select_all(action='DESELECT')

            bpy.ops.mesh.select_non_manifold(
                extend=False, use_wire=False, use_boundary=True,
                use_multi_face=False, use_non_contiguous=False, use_verts=False)
            bpy.ops.mesh.select_more()

            # find boundaries
            bpy.ops.object.mode_set(mode='OBJECT')
            bound_v = [v.index for v in ob.data.vertices if v.select]
            bound_e = [e.index for e in ob.data.edges if e.select]
            bound_p = [p.index for p in ob.data.polygons if p.select]
            bpy.ops.object.mode_set(mode='EDIT')

            # select quad faces
            bpy.context.tool_settings.mesh_select_mode = (False, False, True)
            bpy.ops.mesh.select_face_by_sides(number=4, extend=False)

            # deselect boundaries
            bpy.ops.object.mode_set(mode='OBJECT')
            for i in bound_v:
                bpy.context.active_object.data.vertices[i].select = False
            for i in bound_e:
                bpy.context.active_object.data.edges[i].select = False
            for i in bound_p:
                bpy.context.active_object.data.polygons[i].select = False

            bpy.ops.object.mode_set(mode='EDIT')

            bpy.context.tool_settings.mesh_select_mode = (False, False, True)
            bpy.ops.mesh.edge_face_add()
            bpy.context.tool_settings.mesh_select_mode = (True, False, False)
            bpy.ops.mesh.select_all(action='DESELECT')

            # delete boundaries
            bpy.ops.mesh.select_non_manifold(
                    extend=False, use_wire=True, use_boundary=True,
                    use_multi_face=False, use_non_contiguous=False, use_verts=True
                    )
            bpy.ops.mesh.delete(type='VERT')

            # remove middle vertices
            bm = bmesh.from_edit_mesh(ob.data)
            for v in bm.verts:
                if len(v.link_edges) == 2 and len(v.link_faces) < 3:
                    v.select = True

            # dissolve
            bpy.ops.mesh.dissolve_verts()
            bpy.ops.mesh.select_all(action='DESELECT')

            # remove border faces
            if not self.preserve_borders:
                bpy.ops.mesh.select_non_manifold(
                    extend=False, use_wire=False, use_boundary=True,
                    use_multi_face=False, use_non_contiguous=False, use_verts=False
                    )
                bpy.ops.mesh.select_more()
                bpy.ops.mesh.delete(type='FACE')

            # clean wires
            bpy.ops.mesh.select_non_manifold(
                    extend=False, use_wire=True, use_boundary=False,
                    use_multi_face=False, use_non_contiguous=False, use_verts=False
                    )
            bpy.ops.mesh.delete(type='EDGE')

            bpy.ops.object.mode_set(mode='OBJECT')
            ob0.data.name = mesh_name
            doneMeshes.append(mesh_name)

            for o in clones:
                o.data = ob.data

        for o in sel:
            o.select_set(True)

        bpy.context.view_layer.objects.active = act
        bpy.ops.object.mode_set(mode=mode)

        return {'FINISHED'}


while True:
                bpy.ops.object.modifier_move_up(modifier="dual_mesh_subsurf")
                if ob.modifiers[0].name == "dual_mesh_subsurf":
                    break

#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software Foundation,

# Convert a generic mesh to its dual. With open meshes it can get some wired   #
# effect on the borders.                                                       #

import bpy
from bpy.types import Operator
from bpy.props import (
        BoolProperty,
        EnumProperty,
        )
import bmesh
from .utils import *

class dual_mesh_tessellated(Operator):
    bl_idname = "object.dual_mesh_tessellated"
    bl_label = "Dual Mesh"
    bl_description = ("Generate a polygonal mesh using Tessellate. (Non-destructive)")
    bl_options = {'REGISTER', 'UNDO'}

    apply_modifiers : BoolProperty(
        name="Apply Modifiers",
        default=True,
        description="Apply object's modifiers"
        )

    source_faces : EnumProperty(
            items=[
                ('QUAD', 'Quad Faces', ''),
                ('TRI', 'Triangles', '')],
            name="Source Faces",
            description="Source polygons",
            default="QUAD",
            options={'LIBRARY_EDITABLE'}
            )

    def execute(self, context):
        auto_layer_collection()
        ob0 = context.object
        name1 = "DualMesh_{}_Component".format(self.source_faces)
        # Generate component
        if self.source_faces == 'QUAD':
            verts = [(0.0, 0.0, 0.0), (0.0, 0.5, 0.0),
                    (0.0, 1.0, 0.0), (0.5, 1.0, 0.0),
                    (1.0, 1.0, 0.0), (1.0, 0.5, 0.0),
                    (1.0, 0.0, 0.0), (0.5, 0.0, 0.0),
                    (1/3, 1/3, 0.0), (2/3, 2/3, 0.0)]
            edges = [(0,1), (1,2), (2,3), (3,4), (4,5), (5,6), (6,7),
                        (7,0), (1,8), (8,7), (3,9), (9,5), (8,9)]
            faces = [(7,8,1,0), (8,9,3,2,1), (9,5,4,3), (9,8,7,6,5)]
        else:
            verts = [(0.0,0.0,0.0), (0.5,0.0,0.0), (1.0,0.0,0.0), (0.0,1.0,0.0), (0.5,1.0,0.0), (1.0,1.0,0.0)]
            edges = [(0,1), (1,2), (2,5), (5,4), (4,3), (3,0), (1,4)]
            faces = [(0,1,4,3), (1,2,5,4)]

        # check pre-existing component
        try:
            _verts = [0]*len(verts)*3
            __verts = [c for co in verts for c in co]
            ob1 = bpy.data.objects[name1]
            ob1.data.vertices.foreach_get("co",_verts)
            for a, b in zip(_verts, __verts):
                if abs(a-b) > 0.0001:
                    raise ValueError
        except:
            me = bpy.data.meshes.new("Dual-Mesh")  # add a new mesh
            me.from_pydata(verts, edges, faces)
            me.update(calc_edges=True, calc_edges_loose=True)
            if self.source_faces == 'QUAD': n_seams = 8
            else: n_seams = 6
            for i in range(n_seams): me.edges[i].use_seam = True
            ob1 = bpy.data.objects.new(name1, me)
            bpy.context.collection.objects.link(ob1)
            # fix visualization issue
            bpy.context.view_layer.objects.active = ob1
            ob1.select_set(True)
            bpy.ops.object.editmode_toggle()
            bpy.ops.object.editmode_toggle()
            ob1.select_set(False)
            # hide component
            ob1.hide_select = True
            ob1.hide_render = True
            ob1.hide_viewport = True
        ob = convert_object_to_mesh(ob0,False,False)
        ob.name = 'DualMesh'
        #bpy.context.view_layer.objects.active = ob
        ob.tissue_tessellate.component = ob1
        ob.tissue_tessellate.generator = ob0
        ob.tissue_tessellate.gen_modifiers = self.apply_modifiers
        ob.tissue_tessellate.merge = True
        ob.tissue_tessellate.bool_dissolve_seams = True
        if self.source_faces == 'TRI': ob.tissue_tessellate.fill_mode = 'FAN'
        bpy.ops.object.update_tessellate()
        ob.location = ob0.location
        ob.matrix_world = ob0.matrix_world
        return {'FINISHED'}

class dual_mesh(Operator):
    bl_idname = "object.dual_mesh"
    bl_label = "Convert to Dual Mesh"
    bl_description = ("Convert a generic mesh into a polygonal mesh. (Destructive)")
    bl_options = {'REGISTER', 'UNDO'}

    quad_method : EnumProperty(
            items=[('BEAUTY', 'Beauty',
                    'Split the quads in nice triangles, slower method'),
                    ('FIXED', 'Fixed',
                    'Split the quads on the 1st and 3rd vertices'),
                    ('FIXED_ALTERNATE', 'Fixed Alternate',
                    'Split the quads on the 2nd and 4th vertices'),
                    ('SHORTEST_DIAGONAL', 'Shortest Diagonal',
                    'Split the quads based on the distance between the vertices')
                    ],
            name="Quad Method",
            description="Method for splitting the quads into triangles",
            default="FIXED",
            options={'LIBRARY_EDITABLE'}
            )
    polygon_method : EnumProperty(
            items=[
                ('BEAUTY', 'Beauty', 'Arrange the new triangles evenly'),
                ('CLIP', 'Clip',
                 'Split the polygons with an ear clipping algorithm')],
            name="Polygon Method",
            description="Method for splitting the polygons into triangles",
            default="BEAUTY",
            options={'LIBRARY_EDITABLE'}
            )
    preserve_borders : BoolProperty(
            name="Preserve Borders",
            default=True,
            description="Preserve original borders"
            )
    apply_modifiers : BoolProperty(
            name="Apply Modifiers",
            default=True,
            description="Apply object's modifiers"
            )

    def execute(self, context):
        mode = context.mode
        if mode == 'EDIT_MESH':
            mode = 'EDIT'
        act = bpy.context.active_object
        if mode != 'OBJECT':
            sel = [act]
            bpy.ops.object.mode_set(mode='OBJECT')
        else:
            sel = bpy.context.selected_objects
        doneMeshes = []

        for ob0 in sel:
            if ob0.type != 'MESH':
                continue
            if ob0.data.name in doneMeshes:
                continue
            ob = ob0
            mesh_name = ob0.data.name

            # store linked objects
            clones = []
            n_users = ob0.data.users
            count = 0
            for o in bpy.data.objects:
                if o.type != 'MESH':
                    continue
                if o.data.name == mesh_name:
                    count += 1
                    clones.append(o)
                if count == n_users:
                    break

            if self.apply_modifiers:
                bpy.ops.object.convert(target='MESH')
            ob.data = ob.data.copy()
            bpy.ops.object.select_all(action='DESELECT')
            ob.select_set(True)
            bpy.context.view_layer.objects.active = ob0
            bpy.ops.object.mode_set(mode='EDIT')

            # prevent borders erosion
            bpy.ops.mesh.select_mode(
                    use_extend=False, use_expand=False, type='EDGE'
                    )
            bpy.ops.mesh.select_non_manifold(
                    extend=False, use_wire=False, use_boundary=True,
                    use_multi_face=False, use_non_contiguous=False,
                    use_verts=False
                    )
            bpy.ops.mesh.extrude_region_move(
                    MESH_OT_extrude_region={"mirror": False},
                    TRANSFORM_OT_translate={"value": (0, 0, 0)}
                    )

            bpy.ops.mesh.select_mode(
                    use_extend=False, use_expand=False, type='VERT',
                    action='TOGGLE'
                    )
            bpy.ops.mesh.select_all(action='SELECT')
            bpy.ops.mesh.quads_convert_to_tris(
                    quad_method=self.quad_method, ngon_method=self.polygon_method
                    )
            bpy.ops.mesh.select_all(action='DESELECT')
            bpy.ops.object.mode_set(mode='OBJECT')
            bpy.ops.object.modifier_add(type='SUBSURF')
            ob.modifiers[-1].name = "dual_mesh_subsurf"

            bpy.ops.object.modifier_apply(modifier='dual_mesh_subsurf')

            bpy.ops.object.mode_set(mode='EDIT')
            bpy.ops.mesh.select_all(action='DESELECT')

            verts = ob.data.vertices

            bpy.ops.object.mode_set(mode='OBJECT')
            verts[-1].select = True
            bpy.ops.object.mode_set(mode='EDIT')
            bpy.ops.mesh.select_more(use_face_step=False)

            bpy.ops.mesh.select_similar(
                type='EDGE', compare='EQUAL', threshold=0.01)
            bpy.ops.mesh.select_all(action='INVERT')

            bpy.ops.mesh.dissolve_verts()
            bpy.ops.mesh.select_all(action='DESELECT')

            bpy.ops.mesh.select_non_manifold(
                extend=False, use_wire=False, use_boundary=True,
                use_multi_face=False, use_non_contiguous=False, use_verts=False)
            bpy.ops.mesh.select_more()

            # find boundaries
            bpy.ops.object.mode_set(mode='OBJECT')
            bound_v = [v.index for v in ob.data.vertices if v.select]
            bound_e = [e.index for e in ob.data.edges if e.select]
            bound_p = [p.index for p in ob.data.polygons if p.select]
            bpy.ops.object.mode_set(mode='EDIT')

            # select quad faces
            bpy.context.tool_settings.mesh_select_mode = (False, False, True)
            bpy.ops.mesh.select_face_by_sides(number=4, extend=False)

            # deselect boundaries
            bpy.ops.object.mode_set(mode='OBJECT')
            for i in bound_v:
                bpy.context.active_object.data.vertices[i].select = False
            for i in bound_e:
                bpy.context.active_object.data.edges[i].select = False
            for i in bound_p:
                bpy.context.active_object.data.polygons[i].select = False

            bpy.ops.object.mode_set(mode='EDIT')

            bpy.context.tool_settings.mesh_select_mode = (False, False, True)
            bpy.ops.mesh.edge_face_add()
            bpy.context.tool_settings.mesh_select_mode = (True, False, False)
            bpy.ops.mesh.select_all(action='DESELECT')

            # delete boundaries
            bpy.ops.mesh.select_non_manifold(
                    extend=False, use_wire=True, use_boundary=True,
                    use_multi_face=False, use_non_contiguous=False, use_verts=True
                    )
            bpy.ops.mesh.delete(type='VERT')

            # remove middle vertices
            bm = bmesh.from_edit_mesh(ob.data)
            for v in bm.verts:
                if len(v.link_edges) == 2 and len(v.link_faces) < 3:
                    v.select = True

            # dissolve
            bpy.ops.mesh.dissolve_verts()
            bpy.ops.mesh.select_all(action='DESELECT')

            # remove border faces
            if not self.preserve_borders:
                bpy.ops.mesh.select_non_manifold(
                    extend=False, use_wire=False, use_boundary=True,
                    use_multi_face=False, use_non_contiguous=False, use_verts=False
                    )
                bpy.ops.mesh.select_more()
                bpy.ops.mesh.delete(type='FACE')

            # clean wires
            bpy.ops.mesh.select_non_manifold(
                    extend=False, use_wire=True, use_boundary=False,
                    use_multi_face=False, use_non_contiguous=False, use_verts=False
                    )
            bpy.ops.mesh.delete(type='EDGE')

            bpy.ops.object.mode_set(mode='OBJECT')
            ob0.data.name = mesh_name
            doneMeshes.append(mesh_name)

            for o in clones:
                o.data = ob.data

        for o in sel:
            o.select_set(True)

        bpy.context.view_layer.objects.active = act
        bpy.ops.object.mode_set(mode=mode)

        return {'FINISHED'}


"""
Import des pdf depuis le cadastre (http://cadastre.gouv.fr)

ATTENTION: l'utilisation des données du cadastre n'est pas libre, et ce script doit
donc être utilisé exclusivement pour contribuer à OpenStreetMap, voire
http://wiki.openstreetmap.org/wiki/Cadastre_Fran%C3%A7ais/Conditions_d%27utilisation

Ce script est inspiré du programme Qadastre de Pierre Ducroquet
(http://example.com/)
et du script import-bati.sh
(http://example.com)

"""

import re
import sys
import os.path
import time

from cadastre import CadastreWebsite
from mytools import write_string_to_file
from mytools import write_stream_to_file
from cadastre import command_line_open_cadastre
from pdf_vers_osm_housenumbers import BoundingBox, OSMToCadastreTransform

PDF_DOWNALOD_WAIT_SECONDS = 2
# Nombre de pixels / unite projection cadastre des PDF exportés
PDF_DOWNLOAD_PIXELS_RATIO = 4.5
# Mode de découpage des pdf: "NB": pour nombre fixe, "SIZE": pour taille fixe:
PDF_DOWNLOAD_SPLIT_MODE = "SIZE"
# Si MODE="SIZE", Taille dans la projection cadastrale (~ mètres) des PDF exportés:
PDF_DOWNLOAD_SPLIT_SIZE = 2000
# Si MODE="NB", nombre par lequelle la taille du pdf sera découpée (en
# largeur et en hauteur):
PDF_DOWNLOAD_SPLIT_NB = 2

BBOX_OPTION_FORMAT = re.compile("^(-?[0-9]*(\\.[0-9]*)?,){3}-?[0-9]*(\\.[0-9]*)?$")

def decoupage_bbox_cadastre_forced(bbox, nb_x, x_bbox_size, x_pixels_ratio, nb_y, y_bbox_size, y_pixels_ratio):
  sys.stdout.write((u"Découpe la bbox en %d * %d [%d pdfs]\n" % (nb_x,nb_y,nb_x*nb_y)).encode("utf-8"))
  sys.stdout.flush()
  xmin, ymin, xmax, ymax = bbox
  for i in xrange(nb_x):
    x1 = xmin + i * x_bbox_size
    x2 = min(x1 + x_bbox_size, xmax)
    largeur_px = int((x2-x1) * x_pixels_ratio)
    for j in xrange(nb_y):
      y1 = ymin + j * y_bbox_size
      y2 = min(y1 + y_bbox_size, ymax)
      hauteur_px = int((y2-y1) * y_pixels_ratio)
      yield ((i,j),(x1,y1,x2,y2),(largeur_px,hauteur_px))
      if (y2 == ymax): break
    if (x2 == xmax): break

def decoupage_bbox_cadastre_size(bbox, max_size, pixels_ratio):
  """Génère un découpage de la bbox en m*n sous bbox, de taille maximale
     (max_size, max_size)
     Retourne des tuples ( (i,j), sous_bbox, (largeur_px,hauteur_px) )
     correspondant à la sous bbox d'indice i,j dans le découpage m*n.
     Cette sous bbox ayant une taille en pixels size*pixels_ratio
  """
  xmin,ymin,xmax,ymax = bbox
  assert(xmin < xmax)
  assert(ymin < ymax)
  xmin = xmin - 10
  xmax = xmax + 10
  ymin = ymin - 10
  ymax = ymax + 10
  nb_x = int((xmax - xmin - 1) / max_size) + 1
  nb_y = int((ymax - ymin - 1) / max_size) + 1
  return decoupage_bbox_cadastre_forced((xmin,ymin,xmax,ymax), nb_x, max_size, pixels_ratio, nb_y, max_size, pixels_ratio)

def decoupage_bbox_cadastre_nb(bbox, nb, pixels_ratio):
  """Génère un découpage de la bbox en nb*nb sous bbox, de taille moindre.
     Retourne des tuples ( (i,j), sous_bbox, (largeur_px,hauteur_px) )
     correspondant à la sous bbox d'indice i,j dans le découpage nb*nb.
     Cette sous bbox ayant une taille en pixels size*pixels_ratio
  """
  xmin,ymin,xmax,ymax = bbox
  assert(xmin < xmax)
  assert(ymin < ymax)
  xmin = xmin - 10
  xmax = xmax + 10
  ymin = ymin - 10
  ymax = ymax + 10
  x_bbox_size = (xmax - xmin) / nb
  y_bbox_size = (ymax - ymin) / nb
  return decoupage_bbox_cadastre_forced((xmin,ymin,xmax,ymax), nb, x_bbox_size, pixels_ratio, nb, y_bbox_size, pixels_ratio)

def iter_download_pdfs(cadastreWebsite, code_departement, code_commune, ratio=PDF_DOWNLOAD_PIXELS_RATIO, mode=PDF_DOWNLOAD_SPLIT_MODE, nb=PDF_DOWNLOAD_SPLIT_NB, size=PDF_DOWNLOAD_SPLIT_SIZE, wait=PDF_DOWNALOD_WAIT_SECONDS,force_bbox=None):
    cadastreWebsite.set_departement(code_departement)
    cadastreWebsite.set_commune(code_commune)
    projection = cadastreWebsite.get_projection()
    bbox = cadastreWebsite.get_bbox()
    write_string_to_file(projection + ":%f,%f,%f,%f" % bbox, code_commune + ".bbox")
    if force_bbox:
        bbox = OSMToCadastreTransform(projection).transform_bbox(
            BoundingBox(*force_bbox))
    if mode=="SIZE":
        liste = decoupage_bbox_cadastre_size(bbox, size, ratio)
    else:
        liste = decoupage_bbox_cadastre_nb(bbox, nb, ratio)
    for ((i,j), sous_bbox, (largeur,hauteur)) in liste:
        pdf_filename = code_commune + ("-%d-%d" % (i,j)) + ".pdf"
        bbox_filename = code_commune + ("-%d-%d" % (i,j)) + ".bbox"
        sous_bbox_str = projection + (":%f,%f,%f,%f" % sous_bbox)
        write_string_to_file(sous_bbox_str,  bbox_filename)
        if not (os.path.exists(pdf_filename) and os.path.exists(pdf_filename + ".ok")):
            if os.path.exists(pdf_filename + ".ok"): os.remove(pdf_filename + ".ok")
            write_stream_to_file(
              cadastreWebsite.open_pdf(sous_bbox, largeur, hauteur),
              pdf_filename)
            open(pdf_filename + ".ok", 'a').close()
            time.sleep(wait)
        yield pdf_filename

def print_help():
    programme = sys.argv[0]
    spaces = " " * len(programme)
    sys.stdout.write((u"Téléchargement de PDF du cadastre" + "\n").encode("utf-8"))
    sys.stdout.write((u"OPTIONS:" + "\n").encode("utf-8"))
    sys.stdout.write((u"    -nb <int>      : découpage par un nombre fixe" + "\n").encode("utf-8"))
    sys.stdout.write((u"    -size <int>    : découpage par une taille fixe (en mètres)" + "\n").encode("utf-8"))
    sys.stdout.write((u"    -ratio <float> : Nombre de pixels / mètre des PDF exportés" + "\n").encode("utf-8"))
    sys.stdout.write((u"    -wait <seconds>: attente en seconde entre chaque téléchargement" + "\n").encode("utf-8"))
    sys.stdout.write((u"    -bbox lon1,lat1,lon2,lat2: restreint la zone a extraire\n").encode("utf-8"))
    sys.stdout.write((u"USAGE:" + "\n").encode("utf-8"))
    sys.stdout.write((u"%s  DEPARTEMENT COMMUNE" % programme + "\n").encode("utf-8"))
    sys.stdout.write((u"           télécharge les export PDFs du cadastre d'une commune.\n").encode("utf-8"))
    sys.stdout.write((u"%s  " % programme + "\n").encode("utf-8"))
    sys.stdout.write((u"           liste les départements" + "\n").encode("utf-8"))
    sys.stdout.write((u"%s  DEPARTEMENT" % programme + "\n").encode("utf-8"))
    sys.stdout.write((u"           liste les communes d'un département" + "\n").encode("utf-8"))

sys.stdout.write(("ERREUR: " + message + "\n").encode("utf-8"))
    if help: print_help()

def cadastre_vers_pdfs(argv):
  i = 1
  ratio=PDF_DOWNLOAD_PIXELS_RATIO
  mode=PDF_DOWNLOAD_SPLIT_MODE
  nb=PDF_DOWNLOAD_SPLIT_NB
  size=PDF_DOWNLOAD_SPLIT_SIZE
  wait=PDF_DOWNALOD_WAIT_SECONDS
  bbox=None
  while i < len(argv):
      if argv[i].startswith("-"):
          if argv[i] in ["-h", "-help","--help"]:
              print_help()
              return
          elif argv[i] in ["-r", "-ratio","--ratio"]:
              ratio = float(argv[i+1])
              del(argv[i:i+2])
          elif argv[i] in ["-s", "-size","--size"]:
              size = int(argv[i+1])
              mode = "SIZE"
              del(argv[i:i+2])
          elif argv[i] in ["-n", "-nb","--nb"]:
              nb = int(argv[i+1])
              mode = "NB"
              del(argv[i:i+2])
          elif argv[i] in ["-w", "-wait","--wait"]:
              wait = float(argv[i+1])
              del(argv[i:i+2])
          elif argv[i] in ["-b", "-bbox","--bbox"]:
              bbox = argv[i+1]
              if not BBOX_OPTION_FORMAT.match(bbox):
                command_line_error(u"paramètre bbox invalide: " + bbox)
                return
              bbox = map(float,bbox.split(","))
              del(argv[i:i+2])
          else:
              command_line_error(u"option invalide: " + argv[i])
              return
      else:
          i = i + 1
  if len(argv) == 1:
      command_line_open_cadastre(argv)
  elif len(argv) == 2:
      error = command_line_open_cadastre(argv)
      if error: command_line_error(error)
  elif len(argv) > 3:
      command_line_error(u"trop d'arguments")
  else:
      cadastreWebsite = command_line_open_cadastre(argv)
      if type(cadastreWebsite) in [str, unicode]:
          command_line_error(cadastreWebsite, help=False)
      else:
          code_departement = cadastreWebsite.code_departement
          code_commune = cadastreWebsite.code_commune
          nom_commune = cadastreWebsite.communes[code_commune]
          sys.stdout.write((u"Teléchargement des PDFs de la commune " + code_commune + " : " + nom_commune + "\n").encode("utf-8"))
          sys.stdout.flush()
          write_string_to_file("", code_commune + "-" + nom_commune + ".txt")
          return list(iter_download_pdfs(cadastreWebsite, code_departement, code_commune,mode=mode,size=size,nb=nb,ratio=ratio,wait=wait,force_bbox=bbox))

if __name__ == '__main__':
    cadastre_vers_pdfs(sys.argv)


sys.stdout.write(("ERREUR: " + message + "\n").encode("utf-8"))
    if help: print_help()

"""
Import des pdf depuis le cadastre (http://cadastre.gouv.fr)

ATTENTION: l'utilisation des données du cadastre n'est pas libre, et ce script doit
donc être utilisé exclusivement pour contribuer à OpenStreetMap, voire
http://wiki.openstreetmap.org/wiki/Cadastre_Fran%C3%A7ais/Conditions_d%27utilisation

Ce script est inspiré du programme Qadastre de Pierre Ducroquet
(http://example.com/)
et du script import-bati.sh
(http://example.com)

"""

import re
import sys
import os.path
import time

from cadastre import CadastreWebsite
from mytools import write_string_to_file
from mytools import write_stream_to_file
from cadastre import command_line_open_cadastre
from pdf_vers_osm_housenumbers import BoundingBox, OSMToCadastreTransform

PDF_DOWNALOD_WAIT_SECONDS = 2
# Nombre de pixels / unite projection cadastre des PDF exportés
PDF_DOWNLOAD_PIXELS_RATIO = 4.5
# Mode de découpage des pdf: "NB": pour nombre fixe, "SIZE": pour taille fixe:
PDF_DOWNLOAD_SPLIT_MODE = "SIZE"
# Si MODE="SIZE", Taille dans la projection cadastrale (~ mètres) des PDF exportés:
PDF_DOWNLOAD_SPLIT_SIZE = 2000
# Si MODE="NB", nombre par lequelle la taille du pdf sera découpée (en
# largeur et en hauteur):
PDF_DOWNLOAD_SPLIT_NB = 2

BBOX_OPTION_FORMAT = re.compile("^(-?[0-9]*(\\.[0-9]*)?,){3}-?[0-9]*(\\.[0-9]*)?$")

def decoupage_bbox_cadastre_forced(bbox, nb_x, x_bbox_size, x_pixels_ratio, nb_y, y_bbox_size, y_pixels_ratio):
  sys.stdout.write((u"Découpe la bbox en %d * %d [%d pdfs]\n" % (nb_x,nb_y,nb_x*nb_y)).encode("utf-8"))
  sys.stdout.flush()
  xmin, ymin, xmax, ymax = bbox
  for i in xrange(nb_x):
    x1 = xmin + i * x_bbox_size
    x2 = min(x1 + x_bbox_size, xmax)
    largeur_px = int((x2-x1) * x_pixels_ratio)
    for j in xrange(nb_y):
      y1 = ymin + j * y_bbox_size
      y2 = min(y1 + y_bbox_size, ymax)
      hauteur_px = int((y2-y1) * y_pixels_ratio)
      yield ((i,j),(x1,y1,x2,y2),(largeur_px,hauteur_px))
      if (y2 == ymax): break
    if (x2 == xmax): break

def decoupage_bbox_cadastre_size(bbox, max_size, pixels_ratio):
  """Génère un découpage de la bbox en m*n sous bbox, de taille maximale
     (max_size, max_size)
     Retourne des tuples ( (i,j), sous_bbox, (largeur_px,hauteur_px) )
     correspondant à la sous bbox d'indice i,j dans le découpage m*n.
     Cette sous bbox ayant une taille en pixels size*pixels_ratio
  """
  xmin,ymin,xmax,ymax = bbox
  assert(xmin < xmax)
  assert(ymin < ymax)
  xmin = xmin - 10
  xmax = xmax + 10
  ymin = ymin - 10
  ymax = ymax + 10
  nb_x = int((xmax - xmin - 1) / max_size) + 1
  nb_y = int((ymax - ymin - 1) / max_size) + 1
  return decoupage_bbox_cadastre_forced((xmin,ymin,xmax,ymax), nb_x, max_size, pixels_ratio, nb_y, max_size, pixels_ratio)

def decoupage_bbox_cadastre_nb(bbox, nb, pixels_ratio):
  """Génère un découpage de la bbox en nb*nb sous bbox, de taille moindre.
     Retourne des tuples ( (i,j), sous_bbox, (largeur_px,hauteur_px) )
     correspondant à la sous bbox d'indice i,j dans le découpage nb*nb.
     Cette sous bbox ayant une taille en pixels size*pixels_ratio
  """
  xmin,ymin,xmax,ymax = bbox
  assert(xmin < xmax)
  assert(ymin < ymax)
  xmin = xmin - 10
  xmax = xmax + 10
  ymin = ymin - 10
  ymax = ymax + 10
  x_bbox_size = (xmax - xmin) / nb
  y_bbox_size = (ymax - ymin) / nb
  return decoupage_bbox_cadastre_forced((xmin,ymin,xmax,ymax), nb, x_bbox_size, pixels_ratio, nb, y_bbox_size, pixels_ratio)

def iter_download_pdfs(cadastreWebsite, code_departement, code_commune, ratio=PDF_DOWNLOAD_PIXELS_RATIO, mode=PDF_DOWNLOAD_SPLIT_MODE, nb=PDF_DOWNLOAD_SPLIT_NB, size=PDF_DOWNLOAD_SPLIT_SIZE, wait=PDF_DOWNALOD_WAIT_SECONDS,force_bbox=None):
    cadastreWebsite.set_departement(code_departement)
    cadastreWebsite.set_commune(code_commune)
    projection = cadastreWebsite.get_projection()
    bbox = cadastreWebsite.get_bbox()
    write_string_to_file(projection + ":%f,%f,%f,%f" % bbox, code_commune + ".bbox")
    if force_bbox:
        bbox = OSMToCadastreTransform(projection).transform_bbox(
            BoundingBox(*force_bbox))
    if mode=="SIZE":
        liste = decoupage_bbox_cadastre_size(bbox, size, ratio)
    else:
        liste = decoupage_bbox_cadastre_nb(bbox, nb, ratio)
    for ((i,j), sous_bbox, (largeur,hauteur)) in liste:
        pdf_filename = code_commune + ("-%d-%d" % (i,j)) + ".pdf"
        bbox_filename = code_commune + ("-%d-%d" % (i,j)) + ".bbox"
        sous_bbox_str = projection + (":%f,%f,%f,%f" % sous_bbox)
        write_string_to_file(sous_bbox_str,  bbox_filename)
        if not (os.path.exists(pdf_filename) and os.path.exists(pdf_filename + ".ok")):
            if os.path.exists(pdf_filename + ".ok"): os.remove(pdf_filename + ".ok")
            write_stream_to_file(
              cadastreWebsite.open_pdf(sous_bbox, largeur, hauteur),
              pdf_filename)
            open(pdf_filename + ".ok", 'a').close()
            time.sleep(wait)
        yield pdf_filename

def print_help():
    programme = sys.argv[0]
    spaces = " " * len(programme)
    sys.stdout.write((u"Téléchargement de PDF du cadastre" + "\n").encode("utf-8"))
    sys.stdout.write((u"OPTIONS:" + "\n").encode("utf-8"))
    sys.stdout.write((u"    -nb <int>      : découpage par un nombre fixe" + "\n").encode("utf-8"))
    sys.stdout.write((u"    -size <int>    : découpage par une taille fixe (en mètres)" + "\n").encode("utf-8"))
    sys.stdout.write((u"    -ratio <float> : Nombre de pixels / mètre des PDF exportés" + "\n").encode("utf-8"))
    sys.stdout.write((u"    -wait <seconds>: attente en seconde entre chaque téléchargement" + "\n").encode("utf-8"))
    sys.stdout.write((u"    -bbox lon1,lat1,lon2,lat2: restreint la zone a extraire\n").encode("utf-8"))
    sys.stdout.write((u"USAGE:" + "\n").encode("utf-8"))
    sys.stdout.write((u"%s  DEPARTEMENT COMMUNE" % programme + "\n").encode("utf-8"))
    sys.stdout.write((u"           télécharge les export PDFs du cadastre d'une commune.\n").encode("utf-8"))
    sys.stdout.write((u"%s  " % programme + "\n").encode("utf-8"))
    sys.stdout.write((u"           liste les départements" + "\n").encode("utf-8"))
    sys.stdout.write((u"%s  DEPARTEMENT" % programme + "\n").encode("utf-8"))
    sys.stdout.write((u"           liste les communes d'un département" + "\n").encode("utf-8"))

def command_line_error(message, help=False):

def cadastre_vers_pdfs(argv):
  i = 1
  ratio=PDF_DOWNLOAD_PIXELS_RATIO
  mode=PDF_DOWNLOAD_SPLIT_MODE
  nb=PDF_DOWNLOAD_SPLIT_NB
  size=PDF_DOWNLOAD_SPLIT_SIZE
  wait=PDF_DOWNALOD_WAIT_SECONDS
  bbox=None
  while i < len(argv):
      if argv[i].startswith("-"):
          if argv[i] in ["-h", "-help","--help"]:
              print_help()
              return
          elif argv[i] in ["-r", "-ratio","--ratio"]:
              ratio = float(argv[i+1])
              del(argv[i:i+2])
          elif argv[i] in ["-s", "-size","--size"]:
              size = int(argv[i+1])
              mode = "SIZE"
              del(argv[i:i+2])
          elif argv[i] in ["-n", "-nb","--nb"]:
              nb = int(argv[i+1])
              mode = "NB"
              del(argv[i:i+2])
          elif argv[i] in ["-w", "-wait","--wait"]:
              wait = float(argv[i+1])
              del(argv[i:i+2])
          elif argv[i] in ["-b", "-bbox","--bbox"]:
              bbox = argv[i+1]
              if not BBOX_OPTION_FORMAT.match(bbox):
                command_line_error(u"paramètre bbox invalide: " + bbox)
                return
              bbox = map(float,bbox.split(","))
              del(argv[i:i+2])
          else:
              command_line_error(u"option invalide: " + argv[i])
              return
      else:
          i = i + 1
  if len(argv) == 1:
      command_line_open_cadastre(argv)
  elif len(argv) == 2:
      error = command_line_open_cadastre(argv)
      if error: command_line_error(error)
  elif len(argv) > 3:
      command_line_error(u"trop d'arguments")
  else:
      cadastreWebsite = command_line_open_cadastre(argv)
      if type(cadastreWebsite) in [str, unicode]:
          command_line_error(cadastreWebsite, help=False)
      else:
          code_departement = cadastreWebsite.code_departement
          code_commune = cadastreWebsite.code_commune
          nom_commune = cadastreWebsite.communes[code_commune]
          sys.stdout.write((u"Teléchargement des PDFs de la commune " + code_commune + " : " + nom_commune + "\n").encode("utf-8"))
          sys.stdout.flush()
          write_string_to_file("", code_commune + "-" + nom_commune + ".txt")
          return list(iter_download_pdfs(cadastreWebsite, code_departement, code_commune,mode=mode,size=size,nb=nb,ratio=ratio,wait=wait,force_bbox=bbox))

if __name__ == '__main__':
    cadastre_vers_pdfs(sys.argv)


r un nombre fixe" + "\n").encode("utf-8"))

"""
Import des pdf depuis le cadastre (http://cadastre.gouv.fr)

ATTENTION: l'utilisation des données du cadastre n'est pas libre, et ce script doit
donc être utilisé exclusivement pour contribuer à OpenStreetMap, voire
http://wiki.openstreetmap.org/wiki/Cadastre_Fran%C3%A7ais/Conditions_d%27utilisation

Ce script est inspiré du programme Qadastre de Pierre Ducroquet
(http://example.com/)
et du script import-bati.sh
(http://example.com)

"""

import re
import sys
import os.path
import time

from cadastre import CadastreWebsite
from mytools import write_string_to_file
from mytools import write_stream_to_file
from cadastre import command_line_open_cadastre
from pdf_vers_osm_housenumbers import BoundingBox, OSMToCadastreTransform

PDF_DOWNALOD_WAIT_SECONDS = 2
# Nombre de pixels / unite projection cadastre des PDF exportés
PDF_DOWNLOAD_PIXELS_RATIO = 4.5
# Mode de découpage des pdf: "NB": pour nombre fixe, "SIZE": pour taille fixe:
PDF_DOWNLOAD_SPLIT_MODE = "SIZE"
# Si MODE="SIZE", Taille dans la projection cadastrale (~ mètres) des PDF exportés:
PDF_DOWNLOAD_SPLIT_SIZE = 2000
# Si MODE="NB", nombre par lequelle la taille du pdf sera découpée (en
# largeur et en hauteur):
PDF_DOWNLOAD_SPLIT_NB = 2

BBOX_OPTION_FORMAT = re.compile("^(-?[0-9]*(\\.[0-9]*)?,){3}-?[0-9]*(\\.[0-9]*)?$")

def decoupage_bbox_cadastre_forced(bbox, nb_x, x_bbox_size, x_pixels_ratio, nb_y, y_bbox_size, y_pixels_ratio):
  sys.stdout.write((u"Découpe la bbox en %d * %d [%d pdfs]\n" % (nb_x,nb_y,nb_x*nb_y)).encode("utf-8"))
  sys.stdout.flush()
  xmin, ymin, xmax, ymax = bbox
  for i in xrange(nb_x):
    x1 = xmin + i * x_bbox_size
    x2 = min(x1 + x_bbox_size, xmax)
    largeur_px = int((x2-x1) * x_pixels_ratio)
    for j in xrange(nb_y):
      y1 = ymin + j * y_bbox_size
      y2 = min(y1 + y_bbox_size, ymax)
      hauteur_px = int((y2-y1) * y_pixels_ratio)
      yield ((i,j),(x1,y1,x2,y2),(largeur_px,hauteur_px))
      if (y2 == ymax): break
    if (x2 == xmax): break

def decoupage_bbox_cadastre_size(bbox, max_size, pixels_ratio):
  """Génère un découpage de la bbox en m*n sous bbox, de taille maximale
     (max_size, max_size)
     Retourne des tuples ( (i,j), sous_bbox, (largeur_px,hauteur_px) )
     correspondant à la sous bbox d'indice i,j dans le découpage m*n.
     Cette sous bbox ayant une taille en pixels size*pixels_ratio
  """
  xmin,ymin,xmax,ymax = bbox
  assert(xmin < xmax)
  assert(ymin < ymax)
  xmin = xmin - 10
  xmax = xmax + 10
  ymin = ymin - 10
  ymax = ymax + 10
  nb_x = int((xmax - xmin - 1) / max_size) + 1
  nb_y = int((ymax - ymin - 1) / max_size) + 1
  return decoupage_bbox_cadastre_forced((xmin,ymin,xmax,ymax), nb_x, max_size, pixels_ratio, nb_y, max_size, pixels_ratio)

def decoupage_bbox_cadastre_nb(bbox, nb, pixels_ratio):
  """Génère un découpage de la bbox en nb*nb sous bbox, de taille moindre.
     Retourne des tuples ( (i,j), sous_bbox, (largeur_px,hauteur_px) )
     correspondant à la sous bbox d'indice i,j dans le découpage nb*nb.
     Cette sous bbox ayant une taille en pixels size*pixels_ratio
  """
  xmin,ymin,xmax,ymax = bbox
  assert(xmin < xmax)
  assert(ymin < ymax)
  xmin = xmin - 10
  xmax = xmax + 10
  ymin = ymin - 10
  ymax = ymax + 10
  x_bbox_size = (xmax - xmin) / nb
  y_bbox_size = (ymax - ymin) / nb
  return decoupage_bbox_cadastre_forced((xmin,ymin,xmax,ymax), nb, x_bbox_size, pixels_ratio, nb, y_bbox_size, pixels_ratio)

def iter_download_pdfs(cadastreWebsite, code_departement, code_commune, ratio=PDF_DOWNLOAD_PIXELS_RATIO, mode=PDF_DOWNLOAD_SPLIT_MODE, nb=PDF_DOWNLOAD_SPLIT_NB, size=PDF_DOWNLOAD_SPLIT_SIZE, wait=PDF_DOWNALOD_WAIT_SECONDS,force_bbox=None):
    cadastreWebsite.set_departement(code_departement)
    cadastreWebsite.set_commune(code_commune)
    projection = cadastreWebsite.get_projection()
    bbox = cadastreWebsite.get_bbox()
    write_string_to_file(projection + ":%f,%f,%f,%f" % bbox, code_commune + ".bbox")
    if force_bbox:
        bbox = OSMToCadastreTransform(projection).transform_bbox(
            BoundingBox(*force_bbox))
    if mode=="SIZE":
        liste = decoupage_bbox_cadastre_size(bbox, size, ratio)
    else:
        liste = decoupage_bbox_cadastre_nb(bbox, nb, ratio)
    for ((i,j), sous_bbox, (largeur,hauteur)) in liste:
        pdf_filename = code_commune + ("-%d-%d" % (i,j)) + ".pdf"
        bbox_filename = code_commune + ("-%d-%d" % (i,j)) + ".bbox"
        sous_bbox_str = projection + (":%f,%f,%f,%f" % sous_bbox)
        write_string_to_file(sous_bbox_str,  bbox_filename)
        if not (os.path.exists(pdf_filename) and os.path.exists(pdf_filename + ".ok")):
            if os.path.exists(pdf_filename + ".ok"): os.remove(pdf_filename + ".ok")
            write_stream_to_file(
              cadastreWebsite.open_pdf(sous_bbox, largeur, hauteur),
              pdf_filename)
            open(pdf_filename + ".ok", 'a').close()
            time.sleep(wait)
        yield pdf_filename

def print_help():
    programme = sys.argv[0]
    spaces = " " * len(programme)
    sys.stdout.write((u"Téléchargement de PDF du cadastre" + "\n").encode("utf-8"))
    sys.stdout.write((u"OPTIONS:" + "\n").encode("utf-8"))
    sys.stdout.write((u"    -nb <int>      : découpage pa

    sys.stdout.write((u"    -size <int>    : découpage par une taille fixe (en mètres)" + "\n").encode("utf-8"))
    sys.stdout.write((u"    -ratio <float> : Nombre de pixels / mètre des PDF exportés" + "\n").encode("utf-8"))
    sys.stdout.write((u"    -wait <seconds>: attente en seconde entre chaque téléchargement" + "\n").encode("utf-8"))
    sys.stdout.write((u"    -bbox lon1,lat1,lon2,lat2: restreint la zone a extraire\n").encode("utf-8"))
    sys.stdout.write((u"USAGE:" + "\n").encode("utf-8"))
    sys.stdout.write((u"%s  DEPARTEMENT COMMUNE" % programme + "\n").encode("utf-8"))
    sys.stdout.write((u"           télécharge les export PDFs du cadastre d'une commune.\n").encode("utf-8"))
    sys.stdout.write((u"%s  " % programme + "\n").encode("utf-8"))
    sys.stdout.write((u"           liste les départements" + "\n").encode("utf-8"))
    sys.stdout.write((u"%s  DEPARTEMENT" % programme + "\n").encode("utf-8"))
    sys.stdout.write((u"           liste les communes d'un département" + "\n").encode("utf-8"))

def command_line_error(message, help=False):
    sys.stdout.write(("ERREUR: " + message + "\n").encode("utf-8"))
    if help: print_help()

def cadastre_vers_pdfs(argv):
  i = 1
  ratio=PDF_DOWNLOAD_PIXELS_RATIO
  mode=PDF_DOWNLOAD_SPLIT_MODE
  nb=PDF_DOWNLOAD_SPLIT_NB
  size=PDF_DOWNLOAD_SPLIT_SIZE
  wait=PDF_DOWNALOD_WAIT_SECONDS
  bbox=None
  while i < len(argv):
      if argv[i].startswith("-"):
          if argv[i] in ["-h", "-help","--help"]:
              print_help()
              return
          elif argv[i] in ["-r", "-ratio","--ratio"]:
              ratio = float(argv[i+1])
              del(argv[i:i+2])
          elif argv[i] in ["-s", "-size","--size"]:
              size = int(argv[i+1])
              mode = "SIZE"
              del(argv[i:i+2])
          elif argv[i] in ["-n", "-nb","--nb"]:
              nb = int(argv[i+1])
              mode = "NB"
              del(argv[i:i+2])
          elif argv[i] in ["-w", "-wait","--wait"]:
              wait = float(argv[i+1])
              del(argv[i:i+2])
          elif argv[i] in ["-b", "-bbox","--bbox"]:
              bbox = argv[i+1]
              if not BBOX_OPTION_FORMAT.match(bbox):
                command_line_error(u"paramètre bbox invalide: " + bbox)
                return
              bbox = map(float,bbox.split(","))
              del(argv[i:i+2])
          else:
              command_line_error(u"option invalide: " + argv[i])
              return
      else:
          i = i + 1
  if len(argv) == 1:
      command_line_open_cadastre(argv)
  elif len(argv) == 2:
      error = command_line_open_cadastre(argv)
      if error: command_line_error(error)
  elif len(argv) > 3:
      command_line_error(u"trop d'arguments")
  else:
      cadastreWebsite = command_line_open_cadastre(argv)
      if type(cadastreWebsite) in [str, unicode]:
          command_line_error(cadastreWebsite, help=False)
      else:
          code_departement = cadastreWebsite.code_departement
          code_commune = cadastreWebsite.code_commune
          nom_commune = cadastreWebsite.communes[code_commune]
          sys.stdout.write((u"Teléchargement des PDFs de la commune " + code_commune + " : " + nom_commune + "\n").encode("utf-8"))
          sys.stdout.flush()
          write_string_to_file("", code_commune + "-" + nom_commune + ".txt")
          return list(iter_download_pdfs(cadastreWebsite, code_departement, code_commune,mode=mode,size=size,nb=nb,ratio=ratio,wait=wait,force_bbox=bbox))

if __name__ == '__main__':
    cadastre_vers_pdfs(sys.argv)


  xmin,ymin,xmax,ymax = bbox
  assert(xmin < xmax)
  assert(ymin < ymax)
  xmin = xmin - 10
  xmax = xmax + 10
  ymin = ymin - 10
  ymax = ymax + 10
  x_bbox_size = (xmax - xmin) / nb
  y_bbox_size = (ymax - ymin) / nb
  return decoupage_bbox_cadastre_forced((xmin,ymin,xmax,ymax), nb, x_bbox_size, pixels_ratio, nb, y_bbox_size, pixels_ratio)

"""
Import des pdf depuis le cadastre (http://cadastre.gouv.fr)

ATTENTION: l'utilisation des données du cadastre n'est pas libre, et ce script doit
donc être utilisé exclusivement pour contribuer à OpenStreetMap, voire
http://wiki.openstreetmap.org/wiki/Cadastre_Fran%C3%A7ais/Conditions_d%27utilisation

Ce script est inspiré du programme Qadastre de Pierre Ducroquet
(http://example.com/)
et du script import-bati.sh
(http://example.com)

"""

import re
import sys
import os.path
import time

from cadastre import CadastreWebsite
from mytools import write_string_to_file
from mytools import write_stream_to_file
from cadastre import command_line_open_cadastre
from pdf_vers_osm_housenumbers import BoundingBox, OSMToCadastreTransform

PDF_DOWNALOD_WAIT_SECONDS = 2
# Nombre de pixels / unite projection cadastre des PDF exportés
PDF_DOWNLOAD_PIXELS_RATIO = 4.5
# Mode de découpage des pdf: "NB": pour nombre fixe, "SIZE": pour taille fixe:
PDF_DOWNLOAD_SPLIT_MODE = "SIZE"
# Si MODE="SIZE", Taille dans la projection cadastrale (~ mètres) des PDF exportés:
PDF_DOWNLOAD_SPLIT_SIZE = 2000
# Si MODE="NB", nombre par lequelle la taille du pdf sera découpée (en
# largeur et en hauteur):
PDF_DOWNLOAD_SPLIT_NB = 2

BBOX_OPTION_FORMAT = re.compile("^(-?[0-9]*(\\.[0-9]*)?,){3}-?[0-9]*(\\.[0-9]*)?$")

def decoupage_bbox_cadastre_forced(bbox, nb_x, x_bbox_size, x_pixels_ratio, nb_y, y_bbox_size, y_pixels_ratio):
  sys.stdout.write((u"Découpe la bbox en %d * %d [%d pdfs]\n" % (nb_x,nb_y,nb_x*nb_y)).encode("utf-8"))
  sys.stdout.flush()
  xmin, ymin, xmax, ymax = bbox
  for i in xrange(nb_x):
    x1 = xmin + i * x_bbox_size
    x2 = min(x1 + x_bbox_size, xmax)
    largeur_px = int((x2-x1) * x_pixels_ratio)
    for j in xrange(nb_y):
      y1 = ymin + j * y_bbox_size
      y2 = min(y1 + y_bbox_size, ymax)
      hauteur_px = int((y2-y1) * y_pixels_ratio)
      yield ((i,j),(x1,y1,x2,y2),(largeur_px,hauteur_px))
      if (y2 == ymax): break
    if (x2 == xmax): break

def decoupage_bbox_cadastre_size(bbox, max_size, pixels_ratio):
  """Génère un découpage de la bbox en m*n sous bbox, de taille maximale
     (max_size, max_size)
     Retourne des tuples ( (i,j), sous_bbox, (largeur_px,hauteur_px) )
     correspondant à la sous bbox d'indice i,j dans le découpage m*n.
     Cette sous bbox ayant une taille en pixels size*pixels_ratio
  """
  xmin,ymin,xmax,ymax = bbox
  assert(xmin < xmax)
  assert(ymin < ymax)
  xmin = xmin - 10
  xmax = xmax + 10
  ymin = ymin - 10
  ymax = ymax + 10
  nb_x = int((xmax - xmin - 1) / max_size) + 1
  nb_y = int((ymax - ymin - 1) / max_size) + 1
  return decoupage_bbox_cadastre_forced((xmin,ymin,xmax,ymax), nb_x, max_size, pixels_ratio, nb_y, max_size, pixels_ratio)

def decoupage_bbox_cadastre_nb(bbox, nb, pixels_ratio):
  """Génère un découpage de la bbox en nb*nb sous bbox, de taille moindre.
     Retourne des tuples ( (i,j), sous_bbox, (largeur_px,hauteur_px) )
     correspondant à la sous bbox d'indice i,j dans le découpage nb*nb.
     Cette sous bbox ayant une taille en pixels size*pixels_ratio
  """

def iter_download_pdfs(cadastreWebsite, code_departement, code_commune, ratio=PDF_DOWNLOAD_PIXELS_RATIO, mode=PDF_DOWNLOAD_SPLIT_MODE, nb=PDF_DOWNLOAD_SPLIT_NB, size=PDF_DOWNLOAD_SPLIT_SIZE, wait=PDF_DOWNALOD_WAIT_SECONDS,force_bbox=None):
    cadastreWebsite.set_departement(code_departement)
    cadastreWebsite.set_commune(code_commune)
    projection = cadastreWebsite.get_projection()
    bbox = cadastreWebsite.get_bbox()
    write_string_to_file(projection + ":%f,%f,%f,%f" % bbox, code_commune + ".bbox")
    if force_bbox:
        bbox = OSMToCadastreTransform(projection).transform_bbox(
            BoundingBox(*force_bbox))
    if mode=="SIZE":
        liste = decoupage_bbox_cadastre_size(bbox, size, ratio)
    else:
        liste = decoupage_bbox_cadastre_nb(bbox, nb, ratio)
    for ((i,j), sous_bbox, (largeur,hauteur)) in liste:
        pdf_filename = code_commune + ("-%d-%d" % (i,j)) + ".pdf"
        bbox_filename = code_commune + ("-%d-%d" % (i,j)) + ".bbox"
        sous_bbox_str = projection + (":%f,%f,%f,%f" % sous_bbox)
        write_string_to_file(sous_bbox_str,  bbox_filename)
        if not (os.path.exists(pdf_filename) and os.path.exists(pdf_filename + ".ok")):
            if os.path.exists(pdf_filename + ".ok"): os.remove(pdf_filename + ".ok")
            write_stream_to_file(
              cadastreWebsite.open_pdf(sous_bbox, largeur, hauteur),
              pdf_filename)
            open(pdf_filename + ".ok", 'a').close()
            time.sleep(wait)
        yield pdf_filename

def print_help():
    programme = sys.argv[0]
    spaces = " " * len(programme)
    sys.stdout.write((u"Téléchargement de PDF du cadastre" + "\n").encode("utf-8"))
    sys.stdout.write((u"OPTIONS:" + "\n").encode("utf-8"))
    sys.stdout.write((u"    -nb <int>      : découpage par un nombre fixe" + "\n").encode("utf-8"))
    sys.stdout.write((u"    -size <int>    : découpage par une taille fixe (en mètres)" + "\n").encode("utf-8"))
    sys.stdout.write((u"    -ratio <float> : Nombre de pixels / mètre des PDF exportés" + "\n").encode("utf-8"))
    sys.stdout.write((u"    -wait <seconds>: attente en seconde entre chaque téléchargement" + "\n").encode("utf-8"))
    sys.stdout.write((u"    -bbox lon1,lat1,lon2,lat2: restreint la zone a extraire\n").encode("utf-8"))
    sys.stdout.write((u"USAGE:" + "\n").encode("utf-8"))
    sys.stdout.write((u"%s  DEPARTEMENT COMMUNE" % programme + "\n").encode("utf-8"))
    sys.stdout.write((u"           télécharge les export PDFs du cadastre d'une commune.\n").encode("utf-8"))
    sys.stdout.write((u"%s  " % programme + "\n").encode("utf-8"))
    sys.stdout.write((u"           liste les départements" + "\n").encode("utf-8"))
    sys.stdout.write((u"%s  DEPARTEMENT" % programme + "\n").encode("utf-8"))
    sys.stdout.write((u"           liste les communes d'un département" + "\n").encode("utf-8"))

def command_line_error(message, help=False):
    sys.stdout.write(("ERREUR: " + message + "\n").encode("utf-8"))
    if help: print_help()

def cadastre_vers_pdfs(argv):
  i = 1
  ratio=PDF_DOWNLOAD_PIXELS_RATIO
  mode=PDF_DOWNLOAD_SPLIT_MODE
  nb=PDF_DOWNLOAD_SPLIT_NB
  size=PDF_DOWNLOAD_SPLIT_SIZE
  wait=PDF_DOWNALOD_WAIT_SECONDS
  bbox=None
  while i < len(argv):
      if argv[i].startswith("-"):
          if argv[i] in ["-h", "-help","--help"]:
              print_help()
              return
          elif argv[i] in ["-r", "-ratio","--ratio"]:
              ratio = float(argv[i+1])
              del(argv[i:i+2])
          elif argv[i] in ["-s", "-size","--size"]:
              size = int(argv[i+1])
              mode = "SIZE"
              del(argv[i:i+2])
          elif argv[i] in ["-n", "-nb","--nb"]:
              nb = int(argv[i+1])
              mode = "NB"
              del(argv[i:i+2])
          elif argv[i] in ["-w", "-wait","--wait"]:
              wait = float(argv[i+1])
              del(argv[i:i+2])
          elif argv[i] in ["-b", "-bbox","--bbox"]:
              bbox = argv[i+1]
              if not BBOX_OPTION_FORMAT.match(bbox):
                command_line_error(u"paramètre bbox invalide: " + bbox)
                return
              bbox = map(float,bbox.split(","))
              del(argv[i:i+2])
          else:
              command_line_error(u"option invalide: " + argv[i])
              return
      else:
          i = i + 1
  if len(argv) == 1:
      command_line_open_cadastre(argv)
  elif len(argv) == 2:
      error = command_line_open_cadastre(argv)
      if error: command_line_error(error)
  elif len(argv) > 3:
      command_line_error(u"trop d'arguments")
  else:
      cadastreWebsite = command_line_open_cadastre(argv)
      if type(cadastreWebsite) in [str, unicode]:
          command_line_error(cadastreWebsite, help=False)
      else:
          code_departement = cadastreWebsite.code_departement
          code_commune = cadastreWebsite.code_commune
          nom_commune = cadastreWebsite.communes[code_commune]
          sys.stdout.write((u"Teléchargement des PDFs de la commune " + code_commune + " : " + nom_commune + "\n").encode("utf-8"))
          sys.stdout.flush()
          write_string_to_file("", code_commune + "-" + nom_commune + ".txt")
          return list(iter_download_pdfs(cadastreWebsite, code_departement, code_commune,mode=mode,size=size,nb=nb,ratio=ratio,wait=wait,force_bbox=bbox))

if __name__ == '__main__':
    cadastre_vers_pdfs(sys.argv)


#     3. The name of AURA and its representatives may not be used to
#        endorse or promote products derived from this software without
#        specific prior written permission.

from distutils import core
from distutils import errors
import logging
import os
import sys
import warnings

from setuptools import dist

from pbr import util

_saved_core_distribution = core.Distribution

def _monkeypatch_distribution():
    core.Distribution = dist._get_unpatched(core.Distribution)

core.Distribution = _saved_core_distribution

if sys.version_info[0] == 3:
    string_type = str
    integer_types = (int,)
else:
    string_type = basestring
    integer_types = (int, long)

def pbr(dist, attr, value):
    """Implements the actual pbr setup() keyword.  When used, this should be
    the only keyword in your setup() aside from `setup_requires`.

    If given as a string, the value of pbr is assumed to be the relative path
    to the setup.cfg file to use.  Otherwise, if it evaluates to true, it
    simply assumes that pbr should be used, and the default 'setup.cfg' is
    used.

    This works by reading the setup.cfg file, parsing out the supported
    metadata and command options, and using them to rebuild the
    `DistributionMetadata` object and set the newly added command options.

    The reason for doing things this way is that a custom `Distribution` class
    will not play nicely with setup_requires; however, this implementation may
    not work well with distributions that do use a `Distribution` subclass.
    """

    try:
        _monkeypatch_distribution()
        if not value:
            return
        if isinstance(value, string_type):
            path = os.path.abspath(value)
        else:
            path = os.path.abspath('setup.cfg')
        if not os.path.exists(path):
            raise errors.DistutilsFileError(
                'The setup.cfg file %s does not exist.' % path)

        # Converts the setup.cfg file to setup() arguments
        try:
            attrs = util.cfg_to_args(path)
        except Exception:
            e = sys.exc_info()[1]
            # NB: This will output to the console if no explicit logging has
            # been setup - but thats fine, this is a fatal distutils error, so
            logging.exception('Error parsing')
            raise errors.DistutilsSetupError(
                'Error parsing %s: %s: %s' % (path, e.__class__.__name__, e))

        # Repeat some of the Distribution initialization code with the newly
        # provided attrs
        if attrs:
            # Skips 'options' and 'licence' support which are rarely used; may
            # add back in later if demanded
            for key, val in attrs.items():
                if hasattr(dist.metadata, 'set_' + key):
                    getattr(dist.metadata, 'set_' + key)(val)
                elif hasattr(dist.metadata, key):
                    setattr(dist.metadata, key, val)
                elif hasattr(dist, key):
                    setattr(dist, key, val)
                else:
                    msg = 'Unknown distribution option: %s' % repr(key)
                    warnings.warn(msg)

        # Re-finalize the underlying Distribution
        core.Distribution.finalize_options(dist)

        # This bit comes out of distribute/setuptools
        if isinstance(dist.metadata.version, integer_types + (float,)):
            # Some people apparently take "version number" too literally :)
            dist.metadata.version = str(dist.metadata.version)

        # This bit of hackery is necessary so that the Distribution will ignore
        # normally unsupport command options (namely pre-hooks and post-hooks).
        # dist.command_options is normally a dict mapping command names to
        # dicts of their options.  Now it will be a defaultdict that returns
        # IgnoreDicts for the each command's options so we can pass through the
        # unsupported options
        ignore = ['pre_hook.*', 'post_hook.*']
        dist.command_options = util.DefaultGetDict(
            lambda: util.IgnoreDict(ignore)
        )
    finally:
        _restore_distribution_monkeypatch()


#     3. The name of AURA and its representatives may not be used to
#        endorse or promote products derived from this software without
#        specific prior written permission.

from distutils import core
from distutils import errors
import logging
import os
import sys
import warnings

from setuptools import dist

from pbr import util

_saved_core_distribution = core.Distribution

def _monkeypatch_distribution():

def _restore_distribution_monkeypatch():
    core.Distribution = _saved_core_distribution

if sys.version_info[0] == 3:
    string_type = str
    integer_types = (int,)
else:
    string_type = basestring
    integer_types = (int, long)

def pbr(dist, attr, value):
    """Implements the actual pbr setup() keyword.  When used, this should be
    the only keyword in your setup() aside from `setup_requires`.

    If given as a string, the value of pbr is assumed to be the relative path
    to the setup.cfg file to use.  Otherwise, if it evaluates to true, it
    simply assumes that pbr should be used, and the default 'setup.cfg' is
    used.

    This works by reading the setup.cfg file, parsing out the supported
    metadata and command options, and using them to rebuild the
    `DistributionMetadata` object and set the newly added command options.

    The reason for doing things this way is that a custom `Distribution` class
    will not play nicely with setup_requires; however, this implementation may
    not work well with distributions that do use a `Distribution` subclass.
    """

    try:
        _monkeypatch_distribution()
        if not value:
            return
        if isinstance(value, string_type):
            path = os.path.abspath(value)
        else:
            path = os.path.abspath('setup.cfg')
        if not os.path.exists(path):
            raise errors.DistutilsFileError(
                'The setup.cfg file %s does not exist.' % path)

        # Converts the setup.cfg file to setup() arguments
        try:
            attrs = util.cfg_to_args(path)
        except Exception:
            e = sys.exc_info()[1]
            # NB: This will output to the console if no explicit logging has
            # been setup - but thats fine, this is a fatal distutils error, so
            logging.exception('Error parsing')
            raise errors.DistutilsSetupError(
                'Error parsing %s: %s: %s' % (path, e.__class__.__name__, e))

        # Repeat some of the Distribution initialization code with the newly
        # provided attrs
        if attrs:
            # Skips 'options' and 'licence' support which are rarely used; may
            # add back in later if demanded
            for key, val in attrs.items():
                if hasattr(dist.metadata, 'set_' + key):
                    getattr(dist.metadata, 'set_' + key)(val)
                elif hasattr(dist.metadata, key):
                    setattr(dist.metadata, key, val)
                elif hasattr(dist, key):
                    setattr(dist, key, val)
                else:
                    msg = 'Unknown distribution option: %s' % repr(key)
                    warnings.warn(msg)

        # Re-finalize the underlying Distribution
        core.Distribution.finalize_options(dist)

        # This bit comes out of distribute/setuptools
        if isinstance(dist.metadata.version, integer_types + (float,)):
            # Some people apparently take "version number" too literally :)
            dist.metadata.version = str(dist.metadata.version)

        # This bit of hackery is necessary so that the Distribution will ignore
        # normally unsupport command options (namely pre-hooks and post-hooks).
        # dist.command_options is normally a dict mapping command names to
        # dicts of their options.  Now it will be a defaultdict that returns
        # IgnoreDicts for the each command's options so we can pass through the
        # unsupported options
        ignore = ['pre_hook.*', 'post_hook.*']
        dist.command_options = util.DefaultGetDict(
            lambda: util.IgnoreDict(ignore)
        )
    finally:
        _restore_distribution_monkeypatch()


#     3. The name of AURA and its representatives may not be used to
#        endorse or promote products derived from this software without
#        specific prior written permission.

from distutils import core
from distutils import errors
import logging
import os
import sys
import warnings

from setuptools import dist

from pbr import util

_saved_core_distribution = core.Distribution

def _monkeypatch_distribution():
    core.Distribution = dist._get_unpatched(core.Distribution)

def _restore_distribution_monkeypatch():
    core.Distributio

if sys.version_info[0] == 3:
    string_type = str
    integer_types = (int,)
else:
    string_type = basestring
    integer_types = (int, long)

def pbr(dist, attr, value):
    """Implements the actual pbr setup() keyword.  When used, this should be
    the only keyword in your setup() aside from `setup_requires`.

    If given as a string, the value of pbr is assumed to be the relative path
    to the setup.cfg file to use.  Otherwise, if it evaluates to true, it
    simply assumes that pbr should be used, and the default 'setup.cfg' is
    used.

    This works by reading the setup.cfg file, parsing out the supported
    metadata and command options, and using them to rebuild the
    `DistributionMetadata` object and set the newly added command options.

    The reason for doing things this way is that a custom `Distribution` class
    will not play nicely with setup_requires; however, this implementation may
    not work well with distributions that do use a `Distribution` subclass.
    """

    try:
        _monkeypatch_distribution()
        if not value:
            return
        if isinstance(value, string_type):
            path = os.path.abspath(value)
        else:
            path = os.path.abspath('setup.cfg')
        if not os.path.exists(path):
            raise errors.DistutilsFileError(
                'The setup.cfg file %s does not exist.' % path)

        # Converts the setup.cfg file to setup() arguments
        try:
            attrs = util.cfg_to_args(path)
        except Exception:
            e = sys.exc_info()[1]
            # NB: This will output to the console if no explicit logging has
            # been setup - but thats fine, this is a fatal distutils error, so
            logging.exception('Error parsing')
            raise errors.DistutilsSetupError(
                'Error parsing %s: %s: %s' % (path, e.__class__.__name__, e))

        # Repeat some of the Distribution initialization code with the newly
        # provided attrs
        if attrs:
            # Skips 'options' and 'licence' support which are rarely used; may
            # add back in later if demanded
            for key, val in attrs.items():
                if hasattr(dist.metadata, 'set_' + key):
                    getattr(dist.metadata, 'set_' + key)(val)
                elif hasattr(dist.metadata, key):
                    setattr(dist.metadata, key, val)
                elif hasattr(dist, key):
                    setattr(dist, key, val)
                else:
                    msg = 'Unknown distribution option: %s' % repr(key)
                    warnings.warn(msg)

        # Re-finalize the underlying Distribution
        core.Distribution.finalize_options(dist)

        # This bit comes out of distribute/setuptools
        if isinstance(dist.metadata.version, integer_types + (float,)):
            # Some people apparently take "version number" too literally :)
            dist.metadata.version = str(dist.metadata.version)

        # This bit of hackery is necessary so that the Distribution will ignore
        # normally unsupport command options (namely pre-hooks and post-hooks).
        # dist.command_options is normally a dict mapping command names to
        # dicts of their options.  Now it will be a defaultdict that returns
        # IgnoreDicts for the each command's options so we can pass through the
        # unsupported options
        ignore = ['pre_hook.*', 'post_hook.*']
        dist.command_options = util.DefaultGetDict(
            lambda: util.IgnoreDict(ignore)
        )
    finally:
        _restore_distribution_monkeypatch()


try:
            attrs = util.cfg_to_args(path)
        except Exception:
            e = sys.exc_info()[1]
            # NB: This will output to the console if no explicit logging has
            # been setup - but thats fine, this is a fatal distutils error, so
            logging.exception('Error parsing')
            raise errors.DistutilsSetupError(
                'Error parsing %s: %s: %s' % (path, e.__class__.__name__, e))

#     3. The name of AURA and its representatives may not be used to
#        endorse or promote products derived from this software without
#        specific prior written permission.

from distutils import core
from distutils import errors
import logging
import os
import sys
import warnings

from setuptools import dist

from pbr import util

_saved_core_distribution = core.Distribution

def _monkeypatch_distribution():
    core.Distribution = dist._get_unpatched(core.Distribution)

def _restore_distribution_monkeypatch():
    core.Distribution = _saved_core_distribution

if sys.version_info[0] == 3:
    string_type = str
    integer_types = (int,)
else:
    string_type = basestring
    integer_types = (int, long)

def pbr(dist, attr, value):
    """Implements the actual pbr setup() keyword.  When used, this should be
    the only keyword in your setup() aside from `setup_requires`.

    If given as a string, the value of pbr is assumed to be the relative path
    to the setup.cfg file to use.  Otherwise, if it evaluates to true, it
    simply assumes that pbr should be used, and the default 'setup.cfg' is
    used.

    This works by reading the setup.cfg file, parsing out the supported
    metadata and command options, and using them to rebuild the
    `DistributionMetadata` object and set the newly added command options.

    The reason for doing things this way is that a custom `Distribution` class
    will not play nicely with setup_requires; however, this implementation may
    not work well with distributions that do use a `Distribution` subclass.
    """

    try:
        _monkeypatch_distribution()
        if not value:
            return
        if isinstance(value, string_type):
            path = os.path.abspath(value)
        else:
            path = os.path.abspath('setup.cfg')
        if not os.path.exists(path):
            raise errors.DistutilsFileError(
                'The setup.cfg file %s does not exist.' % path)

        # Converts the setup.cfg file to setup() arguments

        # Repeat some of the Distribution initialization code with the newly
        # provided attrs
        if attrs:
            # Skips 'options' and 'licence' support which are rarely used; may
            # add back in later if demanded
            for key, val in attrs.items():
                if hasattr(dist.metadata, 'set_' + key):
                    getattr(dist.metadata, 'set_' + key)(val)
                elif hasattr(dist.metadata, key):
                    setattr(dist.metadata, key, val)
                elif hasattr(dist, key):
                    setattr(dist, key, val)
                else:
                    msg = 'Unknown distribution option: %s' % repr(key)
                    warnings.warn(msg)

        # Re-finalize the underlying Distribution
        core.Distribution.finalize_options(dist)

        # This bit comes out of distribute/setuptools
        if isinstance(dist.metadata.version, integer_types + (float,)):
            # Some people apparently take "version number" too literally :)
            dist.metadata.version = str(dist.metadata.version)

        # This bit of hackery is necessary so that the Distribution will ignore
        # normally unsupport command options (namely pre-hooks and post-hooks).
        # dist.command_options is normally a dict mapping command names to
        # dicts of their options.  Now it will be a defaultdict that returns
        # IgnoreDicts for the each command's options so we can pass through the
        # unsupported options
        ignore = ['pre_hook.*', 'post_hook.*']
        dist.command_options = util.DefaultGetDict(
            lambda: util.IgnoreDict(ignore)
        )
    finally:
        _restore_distribution_monkeypatch()


from __future__ import unicode_literals

from decimal import Decimal

from django.contrib.auth.models import AnonymousUser
from django.db import transaction
from django.test import TransactionTestCase, RequestFactory, TestCase

import waffle
from waffle.models import Switch, Sample
from waffle.testutils import override_switch, override_flag, override_sample

class OverrideSwitchMixin:
    def test_switch_existed_and_was_active(self):
        Switch.objects.create(name='foo', active=True)

        with override_switch('foo', active=True):
            assert waffle.switch_is_active('foo')

        with override_switch('foo', active=False):
            assert not waffle.switch_is_active('foo')

        # make sure it didn't change 'active' value
        assert Switch.objects.get(name='foo').active

    def test_switch_existed_and_was_NOT_active(self):
        Switch.objects.create(name='foo', active=False)

        with override_switch('foo', active=True):
            assert waffle.switch_is_active('foo')

        with override_switch('foo', active=False):
            assert not waffle.switch_is_active('foo')

        # make sure it didn't change 'active' value
        assert not Switch.objects.get(name='foo').active

    def test_new_switch(self):
        assert not Switch.objects.filter(name='foo').exists()

        with override_switch('foo', active=True):
            assert waffle.switch_is_active('foo')

        with override_switch('foo', active=False):
            assert not waffle.switch_is_active('foo')

        assert not Switch.objects.filter(name='foo').exists()

    def test_as_decorator(self):
        assert not Switch.objects.filter(name='foo').exists()

        @override_switch('foo', active=True)
        def test_enabled():
            assert waffle.switch_is_active('foo')

        test_enabled()

        @override_switch('foo', active=False)
        def test_disabled():
            assert not waffle.switch_is_active('foo')

        test_disabled()

        assert not Switch.objects.filter(name='foo').exists()

    def test_restores_after_exception(self):
        Switch.objects.create(name='foo', active=True)

        def inner():
            with override_switch('foo', active=False):
                raise RuntimeError("Trying to break")

        with self.assertRaises(RuntimeError):
            inner()

        assert Switch.objects.get(name='foo').active

    def test_restores_after_exception_in_decorator(self):
        Switch.objects.create(name='foo', active=True)

        @override_switch('foo', active=False)
        def inner():
            raise RuntimeError("Trying to break")

        with self.assertRaises(RuntimeError):
            inner()

        assert Switch.objects.get(name='foo').active

Switch.objects.create(name='foo', active=True)

        with transaction.atomic():
            with override_switch('foo', active=True):
                assert waffle.switch_is_active('foo')

            with override_switch('foo', active=False):
                assert not waffle.switch_is_active('foo')

        assert waffle.switch_is_active('foo')

class OverrideSwitchTestCase(OverrideSwitchMixin, TestCase):
    """
    Run tests with Django TestCase
    """

class OverrideSwitchTransactionTestCase(OverrideSwitchMixin, TransactionTestCase):
    """
    Run tests with Django TransactionTestCase
    """

def req():
    r = RequestFactory().get('/')
    r.user = AnonymousUser()
    return r

class OverrideFlagTestsMixin:
    def test_flag_existed_and_was_active(self):
        waffle.get_waffle_flag_model().objects.create(name='foo', everyone=True)

        with override_flag('foo', active=True):
            assert waffle.flag_is_active(req(), 'foo')

        with override_flag('foo', active=False):
            assert not waffle.flag_is_active(req(), 'foo')

        assert waffle.get_waffle_flag_model().objects.get(name='foo').everyone

    def test_flag_existed_and_was_inactive(self):
        waffle.get_waffle_flag_model().objects.create(name='foo', everyone=False)

        with override_flag('foo', active=True):
            assert waffle.flag_is_active(req(), 'foo')

        with override_flag('foo', active=False):
            assert not waffle.flag_is_active(req(), 'foo')

        assert waffle.get_waffle_flag_model().objects.get(name='foo').everyone is False

    def test_flag_existed_and_was_null(self):
        waffle.get_waffle_flag_model().objects.create(name='foo', everyone=None)

        with override_flag('foo', active=True):
            assert waffle.flag_is_active(req(), 'foo')

        with override_flag('foo', active=False):
            assert not waffle.flag_is_active(req(), 'foo')

        assert waffle.get_waffle_flag_model().objects.get(name='foo').everyone is None

    def test_flag_did_not_exist(self):
        assert not waffle.get_waffle_flag_model().objects.filter(name='foo').exists()

        with override_flag('foo', active=True):
            assert waffle.flag_is_active(req(), 'foo')

        with override_flag('foo', active=False):
            assert not waffle.flag_is_active(req(), 'foo')

        assert not waffle.get_waffle_flag_model().objects.filter(name='foo').exists()

    def test_cache_is_flushed_by_testutils_even_in_transaction(self):
        waffle.get_waffle_flag_model().objects.create(name='foo', everyone=True)

        with transaction.atomic():
            with override_flag('foo', active=True):
                assert waffle.flag_is_active(req(), 'foo')

            with override_flag('foo', active=False):
                assert not waffle.flag_is_active(req(), 'foo')

        assert waffle.flag_is_active(req(), 'foo')

class OverrideFlagsTestCase(OverrideFlagTestsMixin, TestCase):
    """
    Run tests with Django TestCase
    """

class OverrideFlagsTransactionTestCase(OverrideFlagTestsMixin, TransactionTestCase):
    """
    Run tests with Django TransactionTestCase
    """

class OverrideSampleTestsMixin:
    def test_sample_existed_and_was_100(self):
        Sample.objects.create(name='foo', percent='100.0')

        with override_sample('foo', active=True):
            assert waffle.sample_is_active('foo')

        with override_sample('foo', active=False):
            assert not waffle.sample_is_active('foo')

        self.assertEquals(Decimal('100.0'),
                          Sample.objects.get(name='foo').percent)

    def test_sample_existed_and_was_0(self):
        Sample.objects.create(name='foo', percent='0.0')

        with override_sample('foo', active=True):
            assert waffle.sample_is_active('foo')

        with override_sample('foo', active=False):
            assert not waffle.sample_is_active('foo')

        self.assertEquals(Decimal('0.0'),
                          Sample.objects.get(name='foo').percent)

    def test_sample_existed_and_was_50(self):
        Sample.objects.create(name='foo', percent='50.0')

        with override_sample('foo', active=True):
            assert waffle.sample_is_active('foo')

        with override_sample('foo', active=False):
            assert not waffle.sample_is_active('foo')

        self.assertEquals(Decimal('50.0'),
                          Sample.objects.get(name='foo').percent)

    def test_sample_did_not_exist(self):
        assert not Sample.objects.filter(name='foo').exists()

        with override_sample('foo', active=True):
            assert waffle.sample_is_active('foo')

        with override_sample('foo', active=False):
            assert not waffle.sample_is_active('foo')

        assert not Sample.objects.filter(name='foo').exists()

    def test_cache_is_flushed_by_testutils_even_in_transaction(self):
        Sample.objects.create(name='foo', percent='100.0')

        with transaction.atomic():
            with override_sample('foo', active=True):
                assert waffle.sample_is_active('foo')

            with override_sample('foo', active=False):
                assert not waffle.sample_is_active('foo')

        assert waffle.sample_is_active('foo')

class OverrideSampleTestCase(OverrideSampleTestsMixin, TestCase):
    """
    Run tests with Django TestCase
    """

class OverrideSampleTransactionTestCase(OverrideSampleTestsMixin, TransactionTestCase):
    """
    Run tests with Django TransactionTestCase
    """

class OverrideSwitchOnClassTestsMixin(object):
    @classmethod
    def setUpClass(cls):
        super(OverrideSwitchOnClassTestsMixin, cls).setUpClass()
        assert not Switch.objects.filter(name='foo').exists()
        Switch.objects.create(name='foo', active=True)

    def test_undecorated_method_is_set_properly_for_switch(self):
        self.assertFalse(waffle.switch_is_active('foo'))

@override_switch('foo', active=False)
class OverrideSwitchOnClassTestCase(OverrideSwitchOnClassTestsMixin,
                                    TestCase):
    """
    Run tests with Django TestCase
    """

@override_switch('foo', active=False)
class OverrideSwitchOnClassTransactionTestCase(OverrideSwitchOnClassTestsMixin,
                                               TransactionTestCase):
    """
    Run tests with Django TransactionTestCase
    """

class OverrideFlagOnClassTestsMixin(object):
    @classmethod
    def setUpClass(cls):
        super(OverrideFlagOnClassTestsMixin, cls).setUpClass()
        assert not waffle.get_waffle_flag_model().objects.filter(name='foo').exists()
        waffle.get_waffle_flag_model().objects.create(name='foo', everyone=True)

    def test_undecorated_method_is_set_properly_for_flag(self):
        self.assertFalse(waffle.flag_is_active(req(), 'foo'))

@override_flag('foo', active=False)
class OverrideFlagOnClassTestCase(OverrideFlagOnClassTestsMixin,
                                  TestCase):
    """
    Run tests with Django TestCase
    """

@override_flag('foo', active=False)
class OverrideFlagOnClassTransactionTestCase(OverrideFlagOnClassTestsMixin,
                                             TransactionTestCase):
    """
    Run tests with Django TransactionTestCase
    """

class OverrideSampleOnClassTestsMixin(object):
    @classmethod
    def setUpClass(cls):
        super(OverrideSampleOnClassTestsMixin, cls).setUpClass()
        assert not Sample.objects.filter(name='foo').exists()
        Sample.objects.create(name='foo', percent='100.0')

    def test_undecorated_method_is_set_properly_for_sample(self):
        self.assertFalse(waffle.sample_is_active('foo'))

@override_sample('foo', active=False)
class OverrideSampleOnClassTestCase(OverrideSampleOnClassTestsMixin,
                                    TestCase):
    """
    Run tests with Django TestCase
    """

@override_sample('foo', active=False)
class OverrideSampleOnClassTransactionTestCase(OverrideSampleOnClassTestsMixin,
                                               TransactionTestCase):
    """
    Run tests with Django TransactionTestCase
    """

class InheritanceOverrideSwitchOnClassTests(OverrideSwitchOnClassTestCase):
    """
    Extend ``OverrideSwitchOnClassTestCase``
    and make sure ``override_switch`` change still works.
    """

    def test_child_undecorated_method_is_set_properly_for_switch(self):
        self.assertFalse(waffle.switch_is_active('foo'))

class InheritanceOverrideFlagOnClassTests(OverrideFlagOnClassTestCase):
    """
    Extend ``OverrideFlagOnClassTestCase``
    and make sure ``override_flag`` change still works.
    """

    def test_child_undecorated_method_is_set_properly_for_flag(self):
        self.assertFalse(waffle.flag_is_active(req(), 'foo'))

class InheritanceOverrideSampleOnClassTests(OverrideSampleOnClassTestCase):
    """
    Extend ``OverrideSampleOnClassTestCase``
    and make sure ``override_sample`` change still works.
    """

    def test_child_undecorated_method_is_set_properly_for_sample(self):
        self.assertFalse(waffle.sample_is_active('foo'))